5 ms·
It's a shame you feel that way. Modifying an element of an array in Haskell is easy and the blog post gives almost exactly correct code, but for some reason pl
by tome 9y ago
It's a shame you feel that way. Modifying an element of an array in Haskell is easy and the blog post gives almost exactly correct code, but for some reason places it under the title "we run into trouble when we realize there’s no built-in mutating assignment". But we don't run into trouble! Mutable arrays are provided in Haskell! Haskell has mutation! Here's the code
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!
- deleted 9y ago[deleted]
- MichaelBurge 9y agoYou're probably right that the presentation here is misleading. Most common things like arrays, dictionaries, sets, and more don't really present any trouble in practice. You only use the techniques in my article when structuring your program more broadly, and arrays were just an example. 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.
- jstimpfle 9y agoBut IO (or ST) style is also unidiomatic and simply too cumbersome. You are just not going to write "writeArray arr (i,j) k" instead of "arr[i][j] = k", and more importantly, 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).
- sseveran 9y agoI don't think IO is unidiomatic at all, although 7 years ago I might have agreed. First if we really need to we can write haskell that looks a lot like C. Secondly if I find myself in a situation where I need to write a little block of code that just doesn't lend itself to being written in Haskell I have no problem writing it in C. If I was implementing some numerical algorithm this is what I would do. Indeed haskell bindings to things like BLAS to just this. The benefit of marking all side effects with IO is an easy to read codebase, easy refactoring and of course Software Transactional Memory!
- tome 9y agoIt's not unidiomatic at all, and if you need to do it, you'll do it.
- Peaker 9y agoDoing this kind of effect sequencing in Haskell is annoying indeed. A radical library can mitigate that but that would be really unidiomatic. 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.
- tome 9y agoOK, I know this isn't nice but it's already part of the way there writeArray arr (i,j) =<< ((+) <$> readArray arr (i,j-1) <*> readArray arr (i-1,j))
- brudgers 9y ago{caveat: I am pretty sure that there are other ways to write the Haskell code at a higher level. I wrote this not as a rant about Haskell, but because I have been thinking about 'big versus small' languages, recently.} 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.
- tome 9y ago> caveat: I am pretty sure that there are other ways to write the Haskell code at a higher level. I wrote this not as a rant about Haskell, but because I have been thinking about 'big versus small' languages, recently > 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 :)
- brudgers 9y agoIn 'big languages' I am thinking more about how much 'talking about' domain specific topics looks like 'talking about' the topics covered in the core language. Or maybe about how easy it is to make 'talking about' domain topics look like 'talking about' core topics. Maybe it's the degree to which the reductionist process of dividing up code into files is typically reflected when writing code. 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.
- Ajedi32 9y ago> I've no idea why the blog post is written in such long winded style. Mutation in Haskell is not difficult! 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")
- tome 9y agoPop quiz: 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?
- tome 9y agoFWIW. (Haskell doesn't have a convenient built-in function for printing arrays. Guilty as charged!) 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')
- Ajedi32 9y agoThanks, that looks much nicer and more in-line with what I'd expect from an example intended to demonstrate how easy a simple task is in a specific language. 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.
- WarDaft 9y ago
- pmarreck 9y agonot that this is code golf, and not that the following is even mutation, but I couldn't help trying out the "for" syntax in Elixir, which this seemed like a good job for: 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