18 ms·
How do I modify a variable in Haskell?
- jordigh 9y agoI've tried to like Haskell, but when simple tasks start to look like complicated puzzles with many possible solutions, I get really put off. I want to get stuff done, not feel clever for managing to contort my algorithm into a different form whose runtime performance I'm uncertain of, all done merely in the name of avoiding a for loop.
- RodericDay 9y agoI'm doing a little project in Haskell and I agree. Python has that whole "one, and only one way to do it" (that isn't true for basic stuff like string formatting, but at least they try). Meanwhile in Haskell everyone's super proud to point out how this one line of code is just another way of writing this other line of code.
- MichaelBurge 9y agoThanks for reading! 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.
- jordigh 9y agoI wish there was a canonical, common, simple, straightforward and nearly unambiguous and universal way to modify an element in an array. 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.
- mej10 9y agoarr // [(idx,newvalue)] (//) 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.
- _polymer_ 9y agoI assumed there must be something like this, but that notation is a pain. What's wrong with something like "set arr idx newvalue"?
- tome 9y ago> What's wrong with something like "set arr idx newvalue"? Nothing. See here: https://news.ycombinator.com/item?id=15017853 https://news.ycombinator.com/item?id=15017853 It's called "writeArray".
- jordigh 9y agoPrelude> 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?
- mej10 9y agoI understand the source of your question -- not all languages provide arrays in the included-by-default namespace. Python for example requires you to import an array module. These are part of the standard library in both cases. 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.
- berdario 9y agoThe Prelude is just what's visible in the default namespace, it's not the whole language Data.Array and // are also described in the Haskell 2010 report https://www.haskell.org/onlinereport/haskell2010/haskellch14.html https://www.haskell.org/onlinereport/haskell2010/haskellch14...
- Ixiaus 9y agoIt depends on the task, of course, but I generally think Haskell is a very productive language for getting simple things done. Let me explain why. There 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.
- norea-armozel 9y agoI always suggest people to learn Common Lisp or OCaml since they're easier to get into (OCaml is the easiest imo) if you want to get your head around the basic ideas of functional programming.
- marvel_boy 9y agoDo you recommend F#?
- flavio81 9y agoF# is a ML familt language so it should go well with the "ocaml" recommendation. 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.
- pmarreck 9y agoI've had a ball with Elixir as my first functional lang, FWIW. Took a few seconds to bang out an immutable solution to the original problem: https://news.ycombinator.com/item?id=15019685 https://news.ycombinator.com/item?id=15019685 Of course, there's no mutation in Elixir (or the BEAM VM)... At all.
- yawaramin 9y agoI recommend something similar ... Prof. Dan Grossman's Programming Languages course in Coursera, taught using Standard ML: https://www.coursera.org/learn/programming-languages https://www.coursera.org/learn/programming-languages 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.
- lmm 9y agoI think you have to embrace it to appreciate it; Haskell's advantages wouldn't be there without its disadvantages. (Indeed I suspect I'd never have been able to appreciate Haskell if I hadn't learned Scala first, because Scala gave me a path from traditional imperative/OO code to Haskell-like code that doesn't exist in Haskell itself - but now I find myself wishing for a Scala without those things that I at one point needed). Stop worrying about the runtime performance, stop trying to write [previous programming language] in Haskell. Write expressions for the values that you want, then see if the performance is good enough for your use cases (and if not, have a quick go at profiling for common pitfalls rather than immediately jumping to writing it the imperative way); you might be pleasantly surprised (or you might find Haskell is genuinely unsuitable for your use case, though that's pretty rare IME).
- tome 9y agoIt'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).
- SirensOfTitan 9y agoI ride a horse. I've tried driving a car, but it seems like a complicated puzzle with many buttons compared to a horse. 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.
- enord 9y agoIf you want to shovel bytes around in memory (or more likely, the memory model provided by your OS) that's your perogative, but there's nothing necessarily clever or contorted about relegating the responsibility of memory-management to your dependencies (including the compiler). Avoiding for loops is mainstream today, with your stream APIs, map/reduce, LINQ and what have you. 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.
- agentultra 9y ago> when simple tasks start to look like complicated puzzles with many possible solutions, I get really put off. 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.
- beaconstudios 9y agoisn't the whole point that you write code completely differently in functional languages? rather than writing loop code to populate an array, you'd do something like the following (in JS as I'm not a haskell programmer): const row = (len, onPositions) => { return (new Array(len)).map((item, index) => onPositions.includes(index) ? 1 : 0); }; const matrixWithDiagonal = (len) => { return (new Array(len)).map((r, index) => row(len, [index])); } the point being, that the code generates the result directly, rather than making space for values and then looping through toggling the positions that should be set to on. note: you wouldn't really use the (new Array()) construct generally speaking (and it doesn't work with .map anyway) but it's easier than including underscore or ramda in the example.
- jordigh 9y agoThe original example is just to try to find something where you need to modify an array. Sure, you can create identity matrices without modifying an array, but that's just changing the parameters of the problem. If you really do need to change an array, how do you do it in Haskell? Your solution is the typical thing you do in Haskell. You might know how to perform an algorithm by modifying an array, but now you suddenly have your hands tied and you can't do that anymore. Now you have to come up with an alternative way. It's like someone really wants to implement bubblesort in Haskell but instead the solution ends up being to implement quicksort because the bubbling is difficult or unusual in Haskell.
- beaconstudios 9y agoa fair point, but I think it would at least be worth mentioning that if you're trying to modify a variable in Haskell you need to step back and figure out if what you're doing makes sense.
- chongli 9y agoIn Haskell you usually don't want to modify stuff in memory. That's a manual optimization which is beneath the level of abstraction for typical use. Instead, you write high level code describing how to generate new arrays (or vectors or matrices or what have you) from inputs and let the underlying implementation of the library and compiler deal with optimization. For example, the Haskell vector package [0], implements a ton of optimizations for looping, including stream fusion. Instead of manually trying to loop over an array the way you would in C, Haskell encourages you to use a library like vector to hide those low-level details and let you focus on only the task you want to accomplish. This method is extremely powerful because the underlying implementation has been completely hidden. This lets you swap out the underlying implementation often with a single line change to an import statement. [0] http://hackage.haskell.org/package/vector http://hackage.haskell.org/package/vector
- nihils 9y agoIf you want immutable data structures that simulate the behavior of mutable data structures look into Zippers. In this case, a list zipper.
- nihils 9y agoFor example, this problem of modifying the diagonal of a matrix can be solved quite easily: type Zipper a = ([a],[a]) cursorOnDiagonal :: [[Int]] -> [Zipper Int] cursorOnDiagonal matrix = map (\(n,x) -> splitAt n x) (zip [0..(length matrix)-1] matrix) flipToOneAtCursor :: [Zipper Int] -> [Zipper Int] flipToOneAtCursor = map (\(ys,x:xs) -> (ys,1:xs)) backToList :: [Zipper Int] -> [[Int]] backToList = map (\(ys,xs) -> ys ++ xs) If matrix == [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]], then: backToList . flipToOneAtCursor . cursorOnDiagonal $ matrix == [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
- mrcwinn 9y agoFor those with access to another language, an alternate solution would be something like: a = 1; a = 2; Kidding. Really interesting write-up!
- jamesbrock 9y agoMr. Burge has written a nicely thought-out blog post showing an evolution of different ideas about how to write to an array in Haskell. I don't want people trying to follow the evolution to get the impression that iterating and writing to an array is really hard in Haskell, so here is a complete Haskell program which 1. Initializes a 10x10 mutable array to 0. 2. Iterates in a for-loop from 0 to 9, setting the diagonal to 1. 3. Freezes the mutable array to an immutable array and prints it. module Main where import Foreign.C.Types (CInt) import Control.Monad (forM_) import Numeric.LinearAlgebra (toLists) -- http://hackage.haskell.org/package/hmatrix-0.18.1.0/docs/Numeric-LinearAlgebra-Data.html import Numeric.LinearAlgebra.Devel (runSTMatrix, newMatrix, writeMatrix) -- http://hackage.haskell.org/package/hmatrix-0.18.1.0/docs/Numeric-LinearAlgebra-Devel.html main = do putStrLn $ unlines $ fmap unwords $ fmap (fmap show) $ toLists aImmutable where aImmutable = runSTMatrix $ do a <- newMatrix (0::CInt) 10 10 forM_ [0..9] $ \i -> writeMatrix a i i 1 return a Output: 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1