Hacker News new | ask | show | jobs
by shirogane86x 2114 days ago
I personally haven't had the same experience as you (I personally find the Haskell community quite pleasant, especially in the functional-programming slack server), but I'll try and answer (probably in an incomplete way) your question about updates.

Haskell can, in fact, update variables. Mostly through 2 mechanisms:

- ST (a computation containing local, mutable state, that cannot escape its scope) [1]

- IORef (mutable, thread-safe variables that only work in IO). [2]

The other (and usually more common way) of doing "mutable" state in Haskell is through State, which technically doesn't update the variable in-place, but simply modifies the variable and passes a copy to the rest of the computation (although, as far as I'm aware, a lot of the time this step gets optimized away).

That said, if your main field of expertise is simulation programs where performance and space efficiency are very important, then Haskell is probably not a great fit (cause not only is it based on a GC, it's also lazy, which can sometimes mess with the performance of your code, not speed-wise but memory usage-wise). Hopefully this could be mitigated in some way in the coming years when Linear types become viable for efficient resource usage (Linear types ideally could grant us some sort of Rust-like resource management)

[1] https://hackage.haskell.org/package/base-4.14.0.0/docs/Contr...

[2] https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-...

2 comments

As one who wrote a couple hardware simulators in Haskell and otherwise, I have to disagree with you.

The very immutability of Haskell values means one has to use transactional logic - given current situation, compute situation at next step. C++ and many other languages tend to update values in-place and that brought me a lot of bugs to debug and, subsequently, made me use Haskell.

Laziness plays a critical role in free composition of parts of the system.

Let me present you some examples.

Single clock domain computing hardware can be thought as computations that compute values for every raising edge of a clock. It can be described as a function from infinite list of inputs to infinite list of outputs. The adder, for example, is just like this:

  adder :: Num a => [a] -> [a] -> [a]
  adder = zipWith (+)
The register is a thing that produces values remembered from the previous clock cycle. The very first value comes from reset:

  register :: a -> [a] -> [a]
  register = (:)
Mealy machine allows you to apply a function that transforms input and internal state into output and next internal state. Mealy machine can be used for description of all kinds of things, from register files upwards.

  mealy :: ((input, state) -> (output, state)) -> state -> [input] -> [output]
  mealy f resetState inputs = outputs
    where
      -- here comes shortcircuiting that relies on laziness:
      outputsAndStates = map f (zip inputs (register resetState states))
      outputs = map fst outputsAndStates
      states = map snd outputsAndStates
That's it!

Using regular map and other list functions and these two additions, one can simulate single clock domain hardware which amounts to almost anything that computes on silicon - within bounds of approximation; basically, one need to add delays for slower hardware somehow.

The trick with shortcircuiting above allows one to freely compose hardware simulation from different parts. You just put blocks there and they start to work. The function in Mealy machine is pure and total and can be tested (or verified) thoroughly in standard simple way.

From what I read here:

https://stackoverflow.com/questions/57489844/how-does-readio...

IORef does not allow a value to mutate, it allows a pointer to a value to mutate.

Am I correct?

Does the ST Monad work in the same way? or does it truly allow values to be updated in place?

IORef is a pointer to value that can be changed. IORef holds pointer to a value (boxed value) because most Haskell values are lazy.

Please look at unboxed vectors for another example: https://hackage.haskell.org/package/vector-0.12.1.2/docs/Dat...

You can create an unboxed mutable vector and read and update its elements. Vectors are stored as Structure Of Arrays (Vector (a,b) is transformed into (Vector a, Vector b)) and are very efficient in transformations.

Next to them you can find Storable vectors which allow you to store and update any values that have Storable class instance defined. They are for cases when you need Array Of Structures.

Continuing Vector example, ST monad allows you to create a computation that uses mutation internally and looks pure from outside - do new/read/write and then return freezed array. Apply ST monad runner and you get pure freezed array. IO monad allows you to pass that array between computations of different kind.