5 ms·
> One of the major elements that sets Elixir apart from most other programming languages is immutability. It's interesting to compare Elixir to that other immu
by tromp 2y ago
> One of the major elements that sets Elixir apart from most other programming languages is immutability.
It's interesting to compare Elixir to that other immutable programming language: Haskell.
In Elixir, a binding
counter = counter + 1
binds counter to the old value of counter, plus 1.
In Haskell it instead binds counter to the new value plus 1.
Of course that doesn't make sense, and indeed this causes an infinite loop when Haskell tries to evaluate counter.
BUT it does make sense for certain recursive data structures, like an infinite list of 1s:
ones = 1 : ones
We can check this by taking some finite prefix:
ghci> take 5 ones
[1,1,1,1,1]
Another example is making a list of all primes, where you don't need to decide in advance how many elements to limit yourself to.
Can you define such lazy infinite data structures in Elixir?
- chucky_z 2y agohttps://hexdocs.pm/elixir/Stream.html https://hexdocs.pm/elixir/Stream.html Although I don’t think it’ll be quite as elegant as the Haskell code.
- finder83 2y agoInfinite, yes, but I would say it's not quite as core to the language as it is in Haskell where everything's lazy. Infinite streams are quite simple though: Stream.iterate(1, fn(x) -> x end) |> Enum.take(5) [1, 1, 1, 1, 1]
- tromp 2y agoHow do you use that for lists that do not simply iterate, like ghci> fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs) ghci> take 10 fibs [0,1,1,2,3,5,8,13,21,34] ?
- darcien 2y agoYou can use Stream.unfold/2: Stream.unfold({0,1}, fn {a,b} -> {a,{b,a+b}} end) |> Enum.take(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] https://rosettacode.org/wiki/Fibonacci_sequence#Elixir https://rosettacode.org/wiki/Fibonacci_sequence#Elixir
- torginus 2y agoI'm a bit confused - isn't this how all Static Single Assignment representations in compilers work? And those are used in things like LLVM IR to represent C and C++ code. Is C++ immutable now?
- kqr 2y agoThe difference at the high level is that assigning a variable creates a new scope. E.g. in C I would expect to be able to int i = 0; while (i < 5) { i = i+1; printf("i: %d\n", i); } whereas in Haskell I could hypothetically something like let i = 0 in whileM (pure (i < 5)) $ let i = i + 1 in printf "i: %d\n" i but the inner assignment would not have any effect on the variable referenced by the condition in the while loop – it would only affect what's inside the block it opens. (And as GP points out, i=i+1 is an infinite loop in Haskell. But even if it was used to build a lazy structure, it would just keep running the same iteration over and over because when the block is entered, i still has the value that was set outside.)
- eru 2y agoBtw, Haskell also supports mutable re-assignment of variables. But it's not something that's built into the language, you get mutable variables via a library. Just like you can get loops in Haskell via a library.
- kqr 2y agoOh, yeah, it's right there in the standard library. But one has to be a little more explicit about the fact that one is accessing truly mutable variables. For example, given the helper utilities checkIORef r p = fmap p (readIORef r) usingIORef r a = readIORef r >>= a the example can be written as main = do i <- newIORef (0 :: Int) whileM_ (checkIORef i (< 5)) $ do modifyIORef i (+1) usingIORef i (printf "i: %d\n") That said, even if one actually needs to mix mutable variables with I/O actions like printing, I'm not sure I would recommend using IORefs for it. But opening the can of MonadIO m => StateT Int m () is for another day.
- deleted 2y ago[deleted]