4 ms·
> In a strict language, a lazy thunk can be represented as a single mutable cell. In ML, thanks to type abstraction, the mutation is confined to the module that
by dbaupp 11y ago
> In a strict language, a lazy thunk can be represented as a single mutable cell. In ML, thanks to type abstraction, the mutation is confined to the module that implements laziness, and, in the rest of the program, there's no way to tell that anything impure is happening.
Unfortunately, the general story isn't quite so nice, due to thread-safety.
Immutable data structures are great for thread-safety, as there's no problems with data races or generally invalidating things, because there's no writes... but laziness introduces secret writes, among other problems. This means a type providing laziness either can't be used across multiple threads simultaneously, or is forced to have extra overhead (e.g. atomic operations, blocking & registering for being woken up when trying to force a thunk that another thread is already forcing), and users of a library definitely need to know if a module is using mutation in the former way, and may want to know about the latter.
Of course, if shared-memory concurrency/parallelism is forgone, this isn't a problem, but that omission has its own downsides. And... I would suspect the overhead of just implementing it in a thread-safe way can be considered negligible in many cases, especially if data is guaranteed to be pointer-sized (or less).
(Concurrency/parallelism are sometimes described as "abstraction breaking" for this sort of reason.)
- tome 11y agoI don’t understand what you’re saying. Haskell (specifically GHC) has perfectly thread-safe secretly mutable thunks. Are you suggesting there’s a problem with doing this in a strict language? An impure language? Or something else? I don’t see why you’d say there’s a problem with a secretly mutating thunk datatype.
- catnaroek 11y agoHe means that updates to those thunks have to be guaranteed atomic. This can be achieved in three ways: (0) disallowing threads running in parallel, (1) using atomic operations [only works on small enough pieces of data, such as pointers to boxed data], or (2) using explicit concurrency control [locks, whatever]. We know GHC doesn't do the first, and the third would also be overkill, so it probably does the second. This also explains why unboxed data in GHC is less lazy.
- catnaroek 11y agoYou have a point. We might actually end up with a multitude of lazy types (akin to `RefCell` vs. `Mutex` in Rust). In high-level languages like ML and Haskell, forcing all lazy data to be boxed, and atomically mutating pointers to boxed data, seems like a reasonable solution.