Live data from Hacker News

Functional languages should be so much better at mutation than they are

cohost.org

101–110 of 188 posts

Re: Functional languages should be so much better at mutation than they are

#101
post #95
post #38

Earlier quoted context omitted.

One thing that many people miss is that Haskell's monadic style is a direct consequence of lazy evaluation. It all started because they thought lazyness was nice, and wanted to make a language that brought that front and center. But then they found out that they had to come up with a new way to do side-effects, because traditional side-effects don't work when the order of evaluation is unpredictable.

> One thing that many people miss is that Haskell's monadic style is a direct consequence of lazy evaluation. It all started because they thought lazyness was nice, and wanted to make a language that brought that front and center. But then they found out that they had to come up with a new way to do side-effects, because traditional side-effects don't work when the order of evaluation is unpredictable. But this is no…

I wouldn't say that's an accurate description of how Haskell handles side effects. In fact I wouldn't say that Haskell has side effects at all.

Re: Functional languages should be so much better at mutation than they are

#102
post #12

I don’t know how Swift and Koka handle things, but I’ve written a lot of Tcl that uses the same CoW reference-counting trick. (Tcl is an under-appreciated FP language: everything is a string, and strings are immutable, so it has had efficient purely declarative data structures for decades). The downside in Tcl is that if you refactor some code suddenly you can add a new reference and drop into accidentally quadratic…

In Swift you occasionally have to introduce a temporary local variable to avoid accidentally quadratic behavior, but I've never seen it require anything complicated or hard to explain.

In Tcl there is an idiom to do things like

    some_func $value[set value “”]
where the [set value “”] bit reduces the refcount. There’s also a fairly widespread idiom of using the K combinator for this[1]:

    some_func [K $value [set value “”]]
It’s one of those things that has become second-nature to people in-the-know, but is a total headscratcher otherwise.

[1]: https://wiki.tcl-lang.org/page/K#c2a6014c2d129837889d8a8000d...

Re: Functional languages should be so much better at mutation than they are

#103

The article utterly falls apart in its first paragraph where it itself acknowledges that the whole ML family including Ocaml has perfect support for mutation, rightfully assume most Ocaml programmers would choose to not use it most of the time but then assume incorrectly that it’s because the language makes it somehow uneasy. It’s not. It’s just that mutation is very rarely optimal. Even the exemple given fails: > Fo…

> The article utterly falls apart in its first paragraph where it itself acknowledges that the whole ML family including Ocaml has perfect support for mutation

Was the article updated since you wrote this? I don’t see the text you’re referring to.

> Well, no, this is straight confusion between what’s expressed by the program and what’s compiled.

You’re getting at an important point here, but then you seem to fall into this same trap when you write:

> … the many fairly convoluted tricks haskellers pull to somehow reintroduce mutations

Monads started out as a way to represent the semantics of effects in a mathematical context, to support formal representations of the semantics of programming languages that were more tractable from the perspective of analysis and proofs. Even mainstream compilers ended up using related techniques, like static single assignment, for which an equivalence to continuation-passing style exists, and they did this for the same kinds of reasons: tractability of analysis and to support automated transformations.

The use of monads for writing ordinary code - as opposed to language semantics - in Haskell exploited these techniques, allowing effects to be expressed in a purely functional way. But at its root, this is a rigorous way of expressing scenarios that require effects, it’s not just some sort of “convoluted trick”. There are benefits to doing this that go beyond just a hack to implement effects in a pure language.

Which is why it’s unlikely that people who understand these issues will “stop using Haskell”, despite the learning curve barrier it seems to cause (arguably because people tend to learn to program in ad-hoc ways, which Dijkstra notoriously bemoaned.) But many of the most powerful languages have such a barrier, it just takes different forms depending on the nature of the language.

Re: Functional languages should be so much better at mutation than they are

#104
post #80
post #41

Earlier quoted context omitted.

> The point is that contrary to what the article states ML developers are not avoiding mutations because they are uneasy to use but because they trust their compiler when they know it will do good. Proof is that in other case they will use mutations when it makes sense to do so because the compiler does not do a good job. It will do a good job, yes. Will it do the best possible job compared to some other algorithm or…

Oops, this had a performance bug. Instead of: if d.length = Array.length d.values then begin d.values the array reallocation should actually be: if d.length = Array.length d.values then begin let new_array = Array.make (Array.length d.values * 2) x in Array.blit d.values 0 new_array 0 (Array.length d.values); d.values otherwise we allocate about a third more memory than needed. It's telling that even with this perfor…

But they are still very much in the same order of magnitude... pretty impressive that these solutions are all in the same ballpark, I would've expected much bigger differences.

Re: Functional languages should be so much better at mutation than they are

#105
post #98
post #89

Earlier quoted context omitted.

> Since it's not needed and it's massively worse than reference counting Lol, what? Maybe don’t go asserting stuff you clearly know little about. Reference counting is a fine tradeoff for manual memory-managed languages, but it is absolutely smoked out of the water by a tracing GC on most counts. It’s almost like JVM, V8, etc engineers know a thing about the topic and don’t have RC for a good reason. Tracing GC doesn…

That's because, unlike Rust, those languages with RC would have a lot of unnecessarily refcounted objects because they don't have value objects, do a whole lot of useless reference count updates because they don't have borrowing and always have to use atomics because they can't ensure that some objects are not shared between threads (and also would need a cycle collector in addition to the reference counting). If you…

So, RC is better than tracing GC, when it’s not used as memory management, and it is special cased everywhere.. got you!

Like, as I explicitly wrote, it is probably the correct choice for low-level languages close to the metal, that want easy compatibility with other languages through FFI. But the method itself has still got a much slower throughput than a tracing GC, when used in a similar manner. Anything else is a useless comparison, like is a bicycle better than a tank.

Re: Functional languages should be so much better at mutation than they are

#106
post #97
post #77

Earlier quoted context omitted.

People often forget that the runtime of free is not trivially calculatable! I've worked on tiny embedded systems (.net micro framework) where for a given usage pattern the GC was perfectly predictable, as it should be.

What do you mean? Assuming you use a set of slabs of fixed size objects and keep free objects in a linked list, both malloc and free are trivial O(1) operations. Destructors with cascading deletions can take time bounded only by memory allocation, but you can solve that for instance by destroying them on a separate thread, or having a linked list of objects to be destroyed and destroying a constant number/memory size…

But having such a naive malloc implementation would put it well below what a state-of-the-art tracing GC can do in allocation speed, and then we didn’t even get to fragmentation, elegantly solved by moving GCs. Of course, all these are tradeoffs, I’m just saying that it isn’t as easy.

Re: Functional languages should be so much better at mutation than they are

#107
post #40

The article utterly falls apart in its first paragraph where it itself acknowledges that the whole ML family including Ocaml has perfect support for mutation, rightfully assume most Ocaml programmers would choose to not use it most of the time but then assume incorrectly that it’s because the language makes it somehow uneasy. It’s not. It’s just that mutation is very rarely optimal. Even the exemple given fails: > Fo…

Can you provide evidence that code which is "as performant as using mutation" is generated? Mutation tends to be very hard to beat.

It's literally impossible on a CPU. Some people claim FP languages can make optimisations that are based on the fact that values are immutable and which other languages can't, and that's definitely true. But the problem is that those optimisations are almost never actually made by real programming languages, and when they are, they're still slower than a low level imperative language like C or Rust in very nearly every case.

Come'on FP hackers, prove me wrong!

Re: Functional languages should be so much better at mutation than they are

#108
post #104
post #80

Earlier quoted context omitted.

Oops, this had a performance bug. Instead of: if d.length = Array.length d.values then begin d.values the array reallocation should actually be: if d.length = Array.length d.values then begin let new_array = Array.make (Array.length d.values * 2) x in Array.blit d.values 0 new_array 0 (Array.length d.values); d.values otherwise we allocate about a third more memory than needed. It's telling that even with this perfor…

But they are still very much in the same order of magnitude... pretty impressive that these solutions are all in the same ballpark, I would've expected much bigger differences.

The dynamic array spends most of its time copying old elements as it grows exponentially. If you pre-size it to the right size, you eliminate this copying, and the difference becomes 5x. In practice you often have an idea of the size, at least as a rough estimation, so you would win by a larger margin. But that was not part of the spec.

Also, other ways of organizing dynamic not-quite-an-array-but-not-quite-a-linked-list data structures exist. It could be a dynamic array (or linked list) of dynamic arrays to eliminate repeated copying of the oldest elements.

Re: Functional languages should be so much better at mutation than they are

#109
A comment from the article page:

I blame Haskell and the sudden fixation on absolute purity that manifested just as the VC-startup set decided it was the next secret sauce, to the point of literally redefining what "functional programming" meant in the first place.

I think that fixation has forced a lot of focus on solving for "how do we make Haskell do real work" instead of "how do we make programming in general more predictable and functional", and so the latter fight got lost to "well we bolted lambdas into Java somehow, so it's functional now".

Bullseye.

Re: Functional languages should be so much better at mutation than they are

#110
post #99

Earlier quoted context omitted.

Sum types aren't a simulation of exceptions.

Can you elaborate?

They don't get stack traces, for one. (That's arguably the biggest problem with Rust: .unwrap() gives you a stack trace, but has problems; whereas ? erases your stack trace.)

In principle, static analysis could identify unhandled exceptions, then trace the exception, then make that information available to the top-level "Err returned from main" handler. In practice, that's never going to happen in Rust.

Post reply on HN