For a second there, I honestly thought you were being sarcastic. I'd consider the example you just gave to be extremely "long winded" and "difficult" for what it does. If I ported that code line-for-line to Ruby, for example, the whole thing would look like this:
size = 10
# Initialize the array to 0
arr = Array.new(size) { Array.new(size, 0) }
# Set the diagonal to 1
(0...size).each{|i| arr[i][i] = 1 }
# Print the array
puts arr.map{|row| row.join}.join("\n")On the other hand...
main = mapM_ (putStrLn . foldMap show) [bool 0 1 . (==x) <$> [1..size]| x<-[1..size]]
Or x .* y = replicate y x
main = mapM_ (putStrLn . foldMap show) [0 .* x ++ 1 : 0 .* (size-x)| x<-[0..size-1]]
Or if you really do want mutation shorthand (because you're a crazy person) just build it: a .// l = mapM_ (uncurry $ writeArray a) l
pickFn f g (b,v) | b = g v | otherwise = f v
main = do
arr <- newArray ((1,1), (size,size)) 0
arr .// [((x,x),1)|x<-[1..size]]
mapM_ (pickFn putStr putStrLn . (((==size).snd) *** show)) $ getAssocs arr
It's not pretty, but it's not supposed to be. Do you know what is pretty? Doing things the right way. sudo apt-get install haskell-stack
stack setup
stack ghci --package matrix
import Data.Matrix
print $ identity 10
And of course there's similar for Ruby.Do you genuinely believe I wrote the code the way that I did because it's simplest way of initialising an identity matrix in Haskell?
size = 10
main = do
-- Initialise the array to 0
arr <- newArray ((1,1), (size, size)) 0
let _ = arr :: IOArray (Int, Int) Int
-- Set the diagonal to 1
forM_ [1..size] $ \i -> writeArray arr (i,i) 1
-- Print the array
forM_ [1..size] $ \i -> do
forM_ [1..size] $ \j -> do
a <- readArray arr (i, j)
putChar (if a == 0 then '0' else '1')
when (j == size) (putChar '\n')As an aside, Ruby doesn't have a built-in function for printing arrays either (unless you count .inspect or .to_s, which let you print _anything_). The code I gave formatted the array as a string using some simple data manipulation functions before printing it; it was intended to match the output of the program you provided.
v <- readArray arr (i,j-1)
w <- readArray arr (i-1,j)
writeArray arr (i,j) (v+w)
instead of arr[i][j] = arr[i][j-1] + arr[i-1][j];
No way you're doing that over and over again for any substantial performance-oriented code (where you absolutely need mutable arrays).Could be nice to have sugar like expr[!x] that expand to: x >>= \genName -> .. expr[genName] ..
So you could write: writeArr arr !(readArr ..) !(readArr ..)
Then if you add an operator to do the reading it becomes quite reasonable.
arr .~ (i,j) .= arr ! (a,b) + arr ! (x,y)
You make orphan instances for Indexed and Num, and then .~ is just a slight tweak on the adjust function that Indexed provides and .= is literally a direct synonym for $ that just looks better in this context. This is actually more flexible than most languages, because now (arr .~ (i,j)) or (.~ (i,j)) can be named and applied to multiple things should that be desirable for some reason. Also note that this code is very weird, as arr is not an array, but an IO action describing how to produce one. Operations on it actually produce diffing instructions to be applied elsewhere. I have also not tested the performance.These exact things are not in base because they are discouraged and not supposed to be easy. Note that the lens library provides operators more or less just like this for a wide variety of data types, in a safe and composable way.
writeArray arr (i,j) =<< ((+) <$> readArray arr (i,j-1) <*> readArray arr (i-1,j))For me, I want a big language, not more correct assembly or C. An identity matrix in Racket is three lines:
#lang racket
(require math/matrix)
(identity-matrix 10)
Or more iteratively: #lang racket
(require math/matrix)
(diagonal-matrix
(build-list 10 (lambda (x) (values 1))))
Or more generically: #lang racket
(require math/matrix)
(build-matrix 10 10
(lambda (x y)
(if (= x y)
(values 1)
(values 0))))
The choice three different abstractions in fewer lines of code than the single Haskell abstraction. For all its type safety the Haskell abstraction is down and dirty Von Neumann style sequential memory access. Looping over the elements of a matrix is pretty far away from the useful abstractions of matrices.To put it another way, i's and j's are good variable names for loops and bad variable names for matrices...If I have to iterate over elements of a matrix, I really want m's and n's. But that conflicts with semantics of a conventional looping construct.
> The choice three different abstractions in fewer lines of code than the single Haskell abstraction
These two parts of your comment don't seem to mesh :)
Some languages like Python default to explicitly referencing the module, that is the happy easy path leads to writing "math.floor(x)," not "floor(x)." That's even after I write the mandatory "import math." C is probably close to one end of the spectrum, and at the other end of the spectrum are languages like Wolfram, where:
GeoDistance
[Entity["City", {"NewYork", "NewYork", "UnitedStates"}],
Entity["City", {"LosAngeles", "California", "UnitedStates"}]]
Racket is probably closer to Wolfram, and Haskell closer to C. I think the C end is more likely the more a language tries to solve problems that programs don't have before they are written...e.g. speed and type errors. The Wolfram end tries to solve problems that programmers have before programs are written...e.g. what do I want to say.Wishful thinking is often Abelson's starting point in the SICP lectures. That's a bit at odds with the philosophy of some languages.
I'll make a note that for arrays specifically, someone took the trouble to make a mutable version that's usable in IO. Nobody would ever read and write references to an immutable version over using just MArray, but my post might mislead them into doing just that.
one_or_zero = fn(x,y) -> if x==y, do: "1", else: "0" end
for i <- 1..10 do
for j <- 1..10 do
one_or_zero.(i,j) |> IO.write
end
IO.write("\n")
end