import Data.Array.MArray
import Data.Array.IO
size = 10
(!) = readArray
main :: IO ()
main = do
-- 1. Declare the array
arr <- newArray ((1,1), (size,size)) undefined
let _ = arr :: IOArray (Int,Int) Integer
-- 2. Initialize the array to 0
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ writeArray arr (i, j) 0
-- 3. Set the diagonal to 1
sequence_ $ do
i <- [1..size]
return $ writeArray arr (i, i) 1
-- 4. Print the array
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ do
arr_i_j <- arr ! (i,j)
putChar $ if arr_i_j == 0
then '0'
else '1'
if j == size
then putChar '\n'
else return ()
> main
1000000000
0100000000
0010000000
0001000000
0000100000
0000010000
0000001000
0000000100
0000000010
0000000001
I've no idea why the blog post is written in such long winded style. Mutation in Haskell is not difficult!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.
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.
writeArray arr (i,j) =<< ((+) <$> readArray arr (i,j-1) <*> readArray arr (i-1,j)) 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.
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.
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")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.
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. 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")
endThere is indeed a side of the language and culture focused on elegance and there's also a side focused on pragmatism. Elegance and pragmatism aren't mutually exclusive but generally we use it as a type-safe getting things done language.
Imho its strength is in the type system even if that means starting your project out entirely in the IO monad with heavy let binding style, you'll still benefit from the type system. You can then, over time, decouple things, test things, etc.
I hope you don't give up on it because in the realm of pragmatic, unexciting, just trying to do my job software, Haskell really shines. It's just hard to see sometimes because not many production users are talking about that kind of use, you're usually seeing the library author's view which is going to be very different.
[Edit] I also think Rust is very promising for occasions in which you need the low level performance. If I can though, I will use haskell because its type system does such an amazing job at protecting myself from me and six months ago me.
Doesn't every language do this? Often I spend more time trying to decipher a C program than I do actually writing C code. With all of the variables, pointers, and mutation happening it can feel like a complicated puzzle and I get really put off.
What often happens is that the Haskell you read about online is far more theoretical than what you actually need to get practical work done using the language. It's often called that "Haskell Pyramid" or some such.
The only way I know of to "learn to like Haskell," is to not try but simply do it. Start from the beginning. Think of yourself like an empty jar. Put your mind back into the place where you first started programming and go from there. It will be frustrating at first re-learning many concepts you feel that you know but it's the best way IMO.
I had to do this in martial arts once... unlearn all the ways my body was used to moving and simply accept that I was going to start over and learn a completely new way. From the beginning. No excuses. No short-cuts.
> whose runtime performance I'm uncertain of
Reasoning about the run-time performance of Haskell programs is much more tricky, in my experience, than a strictly-evaluated language.
At least at first.
Like most things in Haskell once you are comfortable with the fundamentals you learn how the run-time works and ways to write strictly-evaluated code when performance is a concern.
Loops are (in the general case) hard to reason about and provide few run-time guarantees. Predicate transformers with recurrence-relations are mostly intractable and a giant PITA. Variable assignment (or register overwrites) are hard to reason about, and loops (well... single entry/exit in lieu of goto, but whatever) were introduced to make it easier on the minds eye to follow the evaluation of programs and still they fail regularily.
Looping is not a simple task, it has real externalities wrt. program correctness (however you define it) and programming languages are providing more and more tools to avoid them and provide more run-time guarantees.
The idea behind this article is that somebody who asks the question is probably confused about a lot of the core concepts in Haskell. So I write the same code in 20 different ways so that they can use their understanding of earlier concepts to understand later ones. Which hopefully makes them less confused.
That's a different goal than selling people on Haskell, or giving them the best solution and moving on. There is definitely a learning curve before you can hack out code without thinking too much about it.
In many languages: arr[idx] = nevalue
In Haskell: a blog post or two, telling me how great all the new ways are because of the type system.
I know I'm sounding very contrarian, but it really is a frustration I have with the language. I've heard several times that once you manage to use it a lot you find these unambiguous universal methods. I just feel like getting there first requires digesting all of these blog posts.
(//) is a function that takes an array and a list of pairs of indexes and values and returns an array with those indexes set to those values.
What's wrong with something like "set arr idx newvalue"?
Nothing. See here: https://news.ycombinator.com/item?id=15017853
It's called "writeArray".
Prelude> let x = [1, 2, 3]
Prelude> x
[1,2,3]
Prelude> x // [(2,42)]
<interactive>:5:3:
Not in scope: `//'
Perhaps you meant one of these:
`/' (imported from Prelude), `/=' (imported from Prelude)
So, how canonical is this if it's not in Prelude?This makes it a little harder to get started but considering the kinds of things programmers put up with on a daily basis is extremely mild.
Data.Array and // are also described in the Haskell 2010 report
https://www.haskell.org/onlinereport/haskell2010/haskellch14...
λ :m Data.Array
λ let x = array (1,3) [(1,1), (2,2), (3,3)]
x :: (Num e, Num i, Ix i) => Array i e
λ x
array (1,3) [(1,1),(2,2),(3,3)]
it :: (Ix i, Num i, Num e) => Array i e
λ x // [(2,42)]
array (1,3) [(1,1),(2,42),(3,3)]
it :: (Num i, Num e, Ix i) => Array i e
λ x
array (1,3) [(1,1),(2,2),(3,3)]
it :: (Ix i, Num i, Num e) => Array i e
Note however that this isn't mutating x directly, to do mutation would require state or a monad.The thing you have to keep in mind is that this kind of naked mutation is probably the main source of bugs in programming today. One of the main advantages of OO was that it demarcated which functions could modify a "global" variable. But if I get a weird value in one of my fields, I still can't trivially tell how it got there, I can only narrow down to methods of the class (and possibly the inheritance tree, depending on variable visibility).
Due to that, there is more ceremony in modifying things in Haskell. The language works best if you write most of your code in a way that doesn't mutate anything and then limit mutation to a small area using the advanced techniques developed in the language. But doing all this requires a pretty substantial investment so you probably need something to convince you that the end will be worth it before you start. I don't know what you tell you, what got me interested was something like:
> fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
and
> max = head . sort
but I understand that's not going to motivate most people to completely change how they approach programming as a discipline.
You have trained your brain to think about programming in a certain way. Haskell does things largely differently built on a set of completely different fundamentals and abstractions. For most of my non-CS friends taking a CS class in college: using a loop or basic recursion was a daunting task for them.
New ideas and abstractions require concentrated effort. I find it easy to forget the harder concepts I needed to grasp as I was learning programming (back when I was 11-12), but it's quite an important thing to remember while trying to understand something as different as Haskell.
Another vote for Common Lisp here. You can do functional programming but you can also "step outside" and do OOP, imperative (and other paradigms) if you need.
Took a few seconds to bang out an immutable solution to the original problem: https://news.ycombinator.com/item?id=15019685
Of course, there's no mutation in Elixir (or the BEAM VM)... At all.
He starts by teaching statically typed functional programming with an emphasis on understanding semantics and idioms; then in the following modules he covers Racket and Ruby to compare and contrast dynamically typed homoiconic programming and object-oriented programming.