[HN Gopher] Pure destination-passing style in Linear Haskell ___________________________________________________________________ Pure destination-passing style in Linear Haskell Author : lelf Score : 51 points Date : 2020-11-12 22:03 UTC (1 days ago) (HTM) web link (www.tweag.io) (TXT) w3m dump (www.tweag.io) | whateveracct wrote: | Lots of low-level fun in Haskell's future :) | WJW wrote: | A really underappreciated part of Haskell is innovation in | things other than the type system. Linear types is one of them, | but STM and lightweight threading were in Haskell long before | they became popular in other languages. | kevincox wrote: | > C programmers don't appear to have a name for it. So it is | likely that you have never encountered it. | | The common name for this is "out parameters" | masklinn wrote: | Out parameter would generally be possibly uninitialised memory | in which the function will put the output data, rather than | allocated memory which the function will fill. | | Out parameter is literally just a workaround for the lack of | MRV, the callee still controls the allocation. | | For instance `pthread_create` uses an out parameter: | pthread_t threadId; int err = pthread_create(&threadId, | NULL, &threadFunc, NULL); | | the first parameter is uninitialised memory, it's only _written | to_ by the function. If C had tuples / MRV you'd say | (int err, pthread_t threadId) = pthread_create(NULL, | &threadFunc, NULL); | | instead. | | But might still use a "destination passing" function, because | the point of destination-passing is to make the caller control | the allocation, and allow amortisation. "Destination passing" | is pretty common in low-ish level Rust APIs for instance, while | "out" parameters is largely forbidden (you'd need a function | taking a reference to a MaybeUninit or such nonsense). | kevincox wrote: | I've commonly seen it used for both. Really anything that the | function will write to. | masklinn wrote: | > I've commonly seen it used for both | | That's a good reason to rename one of them because they're | completely different semantics and behaviour, and confusing | one for the other is a direct route to segfault city. | comex wrote: | From another perspective, they're both performing the | same operation on different types. With what you'd call | "destination passing", like char *buf = | malloc(128); snprintf(buf, 128, "hello"); | | the function is accepting a pointer to an uninitialized | array of char, and initializing the array of char. With | what you'd call an "out parameter", like | char *buf; asprintf(&buf, "hello"); | | the function is accepting a pointer to an uninitialized | _pointer_ to array of char, and initializing the pointer. | The caller still controls the allocation of storage for | the pointer itself. | nextos wrote: | A bit off-topic, but I see places like Tweag claim to use Haskell | in production for lots of things, including domains where I would | never consider this language a good fit like statistics & machine | learning. | | I'm very familiar with the Lisp family of languages (Scheme, | Common Lisp, Clojure, Dylan, Julia...) and with some from the ML | as well (SML and Scala). | | Is Haskell worth the effort for a small organization like a | startup or a research lab? If so, in what domains? If one wants | to have strict guarantees why not using Agda or Idris instead? | klodolph wrote: | My experience with Haskell is less "strict guarantees with | proofs" and more "changes to state are exposed in the type | system." I also think that Haskell has a much larger ecosystem | than Agda or Idris, so if you need libraries you can find them. | dllthomas wrote: | I may well be missing something, but IIUC the linearity in | Haskell is on the arrows - the function promises to use that | argument exactly once (if it terminates - and never more than | once). | | I think this means `splitDArray` is broken. It consumes the | linear argument, but then gives us two DArrays that we can forget | to fill (or, probably not as bad, fill multiple times). | | If that's the case, we probably want something more like | splitDArray :: Int -> DArray #-> (DArray #-> DArray #-> a) -> a | | Please someone correct me if I've missed something :) | danidiaz wrote: | The only way to get hold of a DArray is inside the callback of | the "alloc" function: alloc :: Int -> | (DArray a [?] ()) [?] Array a | | According to the linear Haskell paper: "f :: s [?] t guarantees | that if (f u) is consumed exactly once, then the argument u is | consumed exactly once. " | | Also: | | "To consume a pair exactly once, pattern-match on it, and | consume each component exactly once" | | So, if we call splitDArray inside that callback, then we are | obliged to consume each of the resulting (DArray a, DArray a) | exactly once to satisfy the constraint that the input is | consumed exactly once. | | It seems that linear Haskell will resort often to this | continuation-passing idiom. | ghayes wrote: | It's worth comparing this to linear types being a first-class | feature in Idris 2 [0]. It's a hugely exciting direction for FP | in general. | | [0] https://www.type-driven.org.uk/edwinb/linearity-and- | erasure-... ___________________________________________________________________ (page generated 2020-11-13 23:01 UTC)