Live data from Hacker News

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

cohost.org

111–120 of 188 posts

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

#111
post #105
post #98

Earlier quoted context omitted.

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…

> But the method itself has still got a much slower throughput than a tracing GC, when used in a similar manner

That is correct, but the issue is not with reference counting, but rather with having unnecessary extremely frequent RC/GC operations.

Once frequency is reduced to only necessary operations (which could be none at all for many programs), reference counting wins since its cost is proportional to the number of operations, while GC has fixed but large costs.

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

#112
post #99

Earlier quoted context omitted.

Sum types aren't a simulation of exceptions.

Can you elaborate?

Sure, exceptions are very different to Result-style error handling - even checked exceptions. Here are some differences:

* Errors must be explicitly listed as part of the function signature. Checked exceptions and the equivalent for exceptions but they are rarely used in practice. I think Android uses them, but they were so unpopular in C++ that they removed them from the language!

* The syntax to catch and handle errors is very different and more more verbose for exceptions. It can also make flow control a real pain in some languages where you can't declare a variable outside the try body (e.g. references in C++).

* Result errors need to be explicitly handled whereas exceptions are silently propagated by default.

Even though they're similar enough that you could translate one to the other in most cases, they're different enough that saying one is "an emulation" of the other is just stupid.

In my experience Result-based handling is far superior with two exceptions:

1. In functional code like map & filter where it can become quite awkward to explicitly deal with returning errors.

2. It's hard to get a stack trace from where the Err was created rather than from where it was unwrapped. Less of a problem with exceptions which record a stack trace from where they were thrown (in most languages anyway - C++ is an annoying exception).

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

#113
post #83

Earlier quoted context omitted.

Haskell almost seems like it was intentionally designed to perform poorly on real computers, primarily because of space leaks and secondarily because the non-strict evaluation gets compiled into a lot of function pointer jumps, which branch predictors hate. I think it's funny that they make you write linked list code as a metaphor for generators, but it seems like it should be the other way round. (Also, it has excep…

> (Also, it has exceptions which are a bad language feature, and typed throws which are a worse one.) Can you elaborate? You can't stop people simulating exceptions with sum types, and if you have exceptions, why wouldn't you want them to be typed?

By simulating exceptions, do you mean a `Result t e` type (which Haskell calls `Either l r`)? You can use these and the Functor/Applicative/Monad hierarchy to handle errors.

What is presumably talked about is that Haskell also has actual exceptions, generated by calling e.g. `error` or `undefined`.

The semantics of these are.. interesting, mostly thanks to lazy evaluation. For example, `fst (5, error "second") ` is safe to evaluate because the second half of the tuple is a thunk and does not get evaluated. Additionally, there is, to my knowledge, no way to handle exceptions in pure code, presumably due to the undefined evaluation order.

That said, I'm not sure what the alternative would be, because a function like !! (list indexing) can fail and dealing with its fallibility would be a big burden on the programmer.

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

#114
post #29

I recently ran into this issue when trying to memoize a simple numerical sequence in Hoon (yes, that Hoon. I know, I know...). Let's use the fibonacci sequence as an example. Let's write it the classic, elegant way: f(n) = f(n-1) + f(n-2). Gorgeous. It's the sum of the two previous. With the caveat that f(n=0|1) = n. In Python: # fib for basic b's def fib(n): ## Base case if n == 0 or n == 1: return n return fib(n-1)…

Hoon has magic memoization, though (~+)

    ++  fib
      |=  a=@
      ^-  @
      ~+
      ?:  (lte a 1)  a
      %+  add
        $(a (sub a 2))
      $(a (sub a 1))
Try e.g. (fib 100) (don't try it without the ~+)

The compiled code is itself memoizable at the VM execution level. This memo cache is transient within one system event (i.e., pressing enter after (fib 100) to get the result).

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

#115
post #33
post #27

I'm not convinced about the dismissal of option 2. I agree ST is clunky but not for the reasons given. It's clunky because it's impossible to mix with other effects. What if I want ST and exceptions, for example, and I want the presence of both to be tracked in the type signature? ST can't do that. But my effect system, Bluefin, can. In fact it can mix not only state references and exceptions, but arbitrary other eff…

Nice, first I'm hearing of bluefin - I'll be sure to check it out. As an aside, I watched an Alexis King stream (which I can't now find) in which she did a deep dive into effect systems and said something along the lines of: algebraic effect systems should not change their behaviour depending on nesting order e.g. Either > vs State >. Does bluefin have a particular philosophy about how to approach this?

Sorry, I don’t know why my post on bots was removed. I couldn’t reply to you. Oh well.

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

#116
post #99

Earlier quoted context omitted.

Can you elaborate?

Sure, exceptions are very different to Result-style error handling - even checked exceptions. Here are some differences: * Errors must be explicitly listed as part of the function signature. Checked exceptions and the equivalent for exceptions but they are rarely used in practice. I think Android uses them, but they were so unpopular in C++ that they removed them from the language! * The syntax to catch and handle er…

> saying one is "an emulation" of the other is just stupid.

I did say that indeed. Am I to conclude doing so was stupid? If so I would find that very rude.

(For what it's worth I was trying to understand what astrange meant by "it has exceptions which are a bad language feature, and typed throws which are a worse one" and offering that characterization as a way of trying to tease out exactly what he/she meant. Your response contains many interesting points and I would otherwise be interested in discussing with you further, but I'm not too inclined to now that you have suggested you might think I'm stupid.)

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

#117
post #83

Earlier quoted context omitted.

> (Also, it has exceptions which are a bad language feature, and typed throws which are a worse one.) Can you elaborate? You can't stop people simulating exceptions with sum types, and if you have exceptions, why wouldn't you want them to be typed?

By simulating exceptions, do you mean a `Result t e` type (which Haskell calls `Either l r`)? You can use these and the Functor/Applicative/Monad hierarchy to handle errors. What is presumably talked about is that Haskell also has actual exceptions, generated by calling e.g. `error` or `undefined`. The semantics of these are.. interesting, mostly thanks to lazy evaluation. For example, `fst (5, error "second") ` is s…

> By simulating exceptions, do you mean a `Result t e` type (which Haskell calls `Either l r`)?

Yes, exactly.

> The semantics of these are.. interesting, mostly thanks to lazy evaluation. For example, `fst (5, error "second") ` is safe to evaluate because the second half of the tuple is a thunk and does not get evaluated

Correct, and the semantics of loops is also.. interesting. For example `fst (5, last [1..])` is also safe to evaluate.

> What is presumably talked about is that Haskell also has actual exceptions, generated by calling e.g. `error` or `undefined`.

Well, I'm not sure, that's why I asked. I'm trying to understand what astrange meant by "it has exceptions which are a bad language feature, and typed throws which are a worse one". (Throwing exceptions from pure code should be left to such cases, that are impossible to recover from, in my opinion.)

> there is, to my knowledge, no way to handle exceptions in pure code, presumably due to the undefined evaluation order

Correct

> That said, I'm not sure what the alternative would be, because a function like !! (list indexing) can fail and dealing with its fallibility would be a big burden on the programmer.

Indeed. Even more so, what is one supposed to do when an invariant has been violated due to a programming error and there's no way to make progress? Haskell's exceptions are essential. It's even better when they're used in a well typed, well scoped manner, such as provided by my effect library Bluefin

https://hackage.haskell.org/package/bluefin-0.0.6.0/docs/Blu...

That's why I wanted to understand more about what astrange meant. It doesn't match my understanding!

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

#119
post #99

Earlier quoted context omitted.

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.

Sure, if your definition of exceptions includes "must include a stack trace", then sum types can't simulate exceptions. But by that definition Haskell hasn't had exceptions until the last year or two. You might agree with that (I don't) but I'm trying to understand astrange, who said "[Haskell] has exceptions which are a bad language feature, and typed throws which are a worse one". It seems doubtful that "having a stack trace" is part of what he/she considers bad about exceptions, so that aspect is probably not relevant to my line of questioning. What exactly is bad about exceptions? That's the point of me forking off this thread. So far no one has offered an answer.

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

#120
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…

You still need locking to make this work in a multithreaded environment, or at least atomics. And all malloc implementations used today are more complex than this, especially when allocating large objects, because you can't actually maintain a list for every possible size of allocation. That means they need to do extra work to handle fragmentation.

Plus, the free list has to occasionally be walked to actually free pages back to the OS. If you don't, then memory is never freed by free(), it is only marked for potential reuse.

There are several popular implementations of malloc (and their corresponding free), and they are all quite complex and have different tradeoffs. And, in fact, none of them is any more suitable for high performance or realtime code than a GC is. The golden rule for writing this type of code is to just never call malloc/free in critical sections.

And I will note that probably the most commonly used malloc, the one in GNU's glibc, actually uses Linux locks, not atomics. Which means that you are virtually guaranteed to deadlock if you try to use malloc() after fork() but before exec() in a multi-threaded process.

Post reply on HN