[HN Gopher] GHC 9.0, supporting linear types
       ___________________________________________________________________
        
       GHC 9.0, supporting linear types
        
       Author : cosmic_quanta
       Score  : 174 points
       Date   : 2020-12-30 15:58 UTC (7 hours ago)
        
 (HTM) web link (discourse.haskell.org)
 (TXT) w3m dump (discourse.haskell.org)
        
       | canjobear wrote:
       | I would love to read something about the connection between
       | linear types, linear logic, and linear algebra. This seems like a
       | deep and important connection, considering that modern ML is
       | looking more and more like applied linear algebra.
        
         | layoutIfNeeded wrote:
         | The category of finite dimensional vector spaces over finite
         | fields is a model of linear logic.
         | http://www.cs.ox.ac.uk/ralf.hinze/WG2.8/30/slides/steve.pdf
        
       | gautamcgoel wrote:
       | Can someone please explain what you can do with linear types? I
       | understand what they are (types which the compiler guarantees are
       | used exactly once), but I don't understand why they're important.
        
         | cultus wrote:
         | To add to other user's comments, linear types are useful for
         | reasoning exactly about (some) imperative constructs, which is
         | really, really hard to do with standard type systems. That's
         | why reliably guaranteeing e.g. why it's still difficult to
         | ensure safe and timely disposal of database connections in
         | Haskell (maybe not now) or Scala, despite their powerful type
         | systems.
         | 
         | There's many other such modal type systems/logics used for
         | other special purposes like temporal logic.
        
         | WJW wrote:
         | The way I usually think about it is that it's like the Rust
         | borrowing system but more powerful. If the compiler can be sure
         | a type will be used exactly once, you could for example do
         | automatic memory management by freeing the value directly after
         | it is used. You can also fix a range of multithreading bugs
         | with it, as other people have mentioned.
         | 
         | Finally, it allows you to express more invariants about your
         | data through the type system, hopefully reducing bug counts. As
         | an example, soneone could "implement" a sorting function as
         | `sort xs = []`. This has the correct type signature, but is not
         | a correct implementation of sorting. With linear types you
         | could express that the output has to access the values of the
         | input. (Of course what we'd actually want is to express other
         | invariants for sorting as well, but full dependent types are
         | still some ways off)
        
           | danidiaz wrote:
           | According to the proposal, affine types and linear types "are
           | not equi-expressive": https://github.com/ghc-proposals/ghc-
           | proposals/blob/master/p...
        
             | carterschonwald wrote:
             | Yup!
             | 
             | There's also quite a design space still to be explored for
             | these ideas. Even / especially for languages like Haskell.
        
         | marcosdumay wrote:
         | All the use cases I've seen are about optimization.
         | 
         | You can avoid a lot of copying and memory management if you
         | know the value will be used a single time, and then generate
         | another value, that will be used a single time again. It has a
         | very large potential impact, mostly on IO.
        
           | adamnemecek wrote:
           | It's not just optimization but also correctness. You really
           | don't want do a bit wise copy of say a file descriptor.
        
         | mhh__ wrote:
         | Haskell as a language is effectively an experiment to see how
         | much unsafe stuff can be done in a compartmentalized and "safe"
         | manner (see SPJ's "Haskell is useless"), so Linear Types are
         | another road to go down to achieve that goal.
        
         | tremon wrote:
         | The following blog post is referenced in the proposal:
         | https://www.tweag.io/blog/2017-08-03-linear-typestates/ . This
         | is maybe a different aspect than what others have been trying
         | to explain, but I found the socket-based example enlightening.
         | 
         |  _Type states_ allow the state of the socket (unbound, bound,
         | listening, closed) to be expressed as a part of the type (a
         | type annotation, if you will) so the compiler can verify that
         | you 're not reusing a closed socket, or try to send data over
         | an unbound socket.
         | 
         |  _Linear types_ then, allow to express in code how a type
         | (annotation) changes over time (e.g.,  "bind() takes an unbound
         | socket and returns it an a bound state") so the compiler can
         | statically check that it's in a valid state for every
         | operation.
        
         | dllthomas wrote:
         | The two most commonly referenced uses are safe mutation and
         | better resource management.
         | 
         | Safe mutation because when I have the only reference to a
         | thing, the rest of the system shouldn't care whether I change
         | it in place or make a new one.
         | 
         | Better resource management because you can be sure opened files
         | are closed exactly once and that nothing tries to use a file
         | after close, even where a bracket pattern doesn't fit (or fits
         | much more loosely).
         | 
         | In both cases, you're not doing anything you couldn't _do_
         | before, but it lets you do it safely. People speak of
         | performance because (especially in Haskell) mutating without
         | being confident nothing else can see is sufficiently full of
         | footguns that the community happily (... and rightly, IMO) pays
         | small performance penalties for security against them. This
         | lets us remove those performance penalties in more cases.
         | 
         | There's some room for the _compiler_ to notice that something
         | is only referenced in one place and safely convert some things
         | to mutation; again this doesn 't strictly need linear types,
         | but linear types may let us do that reasoning locally instead
         | of it requiring whole program analysis.
        
         | tsuraan wrote:
         | A specific safety article was written up by tweag.io, who wrote
         | a pretty slick set of java bindings. Managing resources between
         | two garbage collectors (ghc and jvm) is error-prone, so they
         | used linear types to help prevent a bunch of the things a
         | person can easily mess up. Their article is pretty good:
         | https://www.tweag.io/blog/2020-02-06-safe-inline-java/
        
       | whateveracct wrote:
       | A lot of good stuff in this release! Both LinearTypes and
       | QualifiedDo (which is useful for LinearTypes but also other uses)
       | 
       | The ghc-bignum change is kind of boring, but it does help a lot
       | with the long-standing problematic dependency on gmp.
       | 
       | Not to mention the stage being set for QuickLook impredicativity.
       | That's something that I've been wishing we've had for a long time
       | now.
       | 
       | Haskell remains at the bleeding edge of general-purpose
       | programming - more than ever with each release.
        
         | bjt wrote:
         | The GMP change is the most exciting one for me!
         | 
         | Years ago when I was really trying to get into Haskell, I
         | wanted its binaries to be as portable as those from Go. I was
         | so sad to see that this _one_ library made that impossible.
        
           | garmaine wrote:
           | What's the issue? GMP is pretty portable, no?
        
             | whateveracct wrote:
             | it's more about its license
        
             | endgame wrote:
             | GMP is LGPL, so if you distribute a binary to someone it
             | has to be possible for the recipient to re-link it with an
             | alternate libgmp (either via dynamic linking, or by
             | providing .o files and a script)
        
             | robocat wrote:
             | My understanding from a quick google:
             | 
             | The issue is that if you write a Haskell program, and you
             | want to deploy it as a single executable, then GMP is
             | included (statically linked), so the LGPL applies to the
             | executable.
             | 
             | The two obvious ways to meet the LGPL license restrictions
             | are:
             | 
             | * license your code in a way that is LGPL compatible (e.g.
             | open source your code using GPL)
             | 
             | * you could provide the app in a relinkable form (ie a big
             | .o file) as per section 6.a
             | 
             | Essentially there is no tidy way to distribute a closed
             | source Haskell executable without removing the GMP library,
             | which was the sole remaining LGPL dependency. It was not
             | trivial to remove the dependency because of the library's
             | quality (amongst other reasons).
             | 
             | (Edited for clarity.)
        
               | garmaine wrote:
               | I don't understand what this has to do with portability.
        
               | cercatrova wrote:
               | People don't want to share the freedoms they got with
               | their users. That's it. There's no reason commercial
               | software can't be under GPL style licenses.
        
               | whateveracct wrote:
               | If I'm releasing an indie video game in Haskell, I'm not
               | going to GPL it..
        
               | [deleted]
        
         | pwm wrote:
         | Just the other day i was doing something with LogicT where
         | QualifiedDo would have been super handy.
        
       | tluyben2 wrote:
       | That went pretty fast, on a ghc timescale. Well done! Promising
       | for the future.
        
         | whateveracct wrote:
         | ~3 years since the Linear Haskell paper all told
         | 
         | This included a pretty rigorous community review. The extension
         | did get some pushback & serious debate, and I honestly think
         | the entire process was handled wonderfully. It speaks well of
         | GHC's community culture (speaking as a sideline observer.)
        
       | chrisaycock wrote:
       | I first became fascinated by linear [1] and uniqueness [2] types
       | with Clean [3], a purely functional language that handles state
       | and IO without monads.
       | 
       | This Haskell extension uses linear types for resource safety and
       | scoped effects [4]. For example, linear types can prevent use-
       | after-free errors. Also, linear-typed arrays can perform in-place
       | updates if there are no external pointers to the data.
       | 
       | Similar to linear types is the _affine_ type, where a value is
       | used at-most once. Rust 's borrow checker is based on this
       | notion, so that values are "moved" rather than "copied".
       | 
       | [1] https://en.wikipedia.org/wiki/Uniqueness_type
       | 
       | [2] https://en.wikipedia.org/wiki/Substructural_type_system
       | 
       | [3] https://clean.cs.ru.nl/Clean
       | 
       | [4] https://arxiv.org/abs/1710.09756
        
         | dmix wrote:
         | Man, I wish I understood all of this stuff. I have a general
         | enough notion from digging into Haskell and reading into types
         | (including dependent types). I see the value of uniqueness just
         | from using Typescript and having dumb clashes of type variable
         | names, which may be unrelated.
         | 
         | But like a lot of Haskell much of this still goes over my head.
         | 
         | I'm so conflicted and my intuition is to leave this to the CS
         | university types and focus on IRL productivity in my own field.
         | But I want to invest more time into FP in the future.
         | 
         | I'm also technically a "frontend developer" professionally :p
        
           | yodsanklai wrote:
           | > But like a lot of Haskell much of this still goes over my
           | head.
           | 
           | It's not that hard, but it's easier to get one concept at a
           | time. I'm less familiar with Haskell, but languages like
           | Scheme/OCaml are routinely used to teach programming to
           | beginners.
           | 
           | Also there's no need to see a dichotomy between "IRL
           | productivity" and "CS/FP". Even if you don't use Haskell
           | directly, ideas from FP make their way to mainstream
           | languages, either as constructs, or libraries, or simply
           | programming style.
           | 
           | Overall, the more programming paradigms you know, the easier
           | it gets to learn new languages.
        
           | roflc0ptic wrote:
           | I have a degree in geography, and learned to do web
           | development largely on the job. It took me about 5 years
           | before I ventured into learning functional programming, and
           | in retrospect, I feel I could've been 5 years farther ahead
           | today. It makes you a better programmer, and while it might
           | only make you marginally better at the tasks you currently
           | do, it will open up a wide expanse of other possibilities you
           | didn't think yourself capable of.
        
             | whateveracct wrote:
             | > it will open up a wide expanse of other possibilities you
             | didn't think yourself capable of
             | 
             | Haskell has a tendency to make every problem seem exactly
             | the same :)
        
       | vsskanth wrote:
       | How can linear types be used for something like doing a bunch of
       | calculations on a DataFrame of unknown size ? ex: filter rows
       | based on certain conditions, compute some averages, and then plot
       | certain columns etc.
       | 
       | Its hard to find real world use examples and learn about linear
       | types when I search online. If anyone has links, that would help.
        
         | jose_zap wrote:
         | Some real world examples with links:
         | 
         | - A safe streaming library where effects are guaranteed to only
         | be performed once: https://www.tweag.io/blog/2018-06-21-linear-
         | streams/
         | 
         | - Efficient zero-copy network communication:
         | https://www.tweag.io/blog/2017-08-24-linear-types-packed-dat...
         | 
         | - Safe manual memory management and prompt resource release:
         | https://www.tweag.io/blog/2020-02-06-safe-inline-java/
         | 
         | - Explicit API design to guide developers on what function to
         | call next when implementing a protocol like TCP:
         | https://www.tweag.io/blog/2017-08-03-linear-typestates/
         | 
         | - Add correctness proofs to already existing algorithm
         | implementations like merge sort:
         | https://www.tweag.io/blog/2018-03-08-linear-sort/
        
           | vsskanth wrote:
           | Thank you!
        
         | whateveracct wrote:
         | They'd mostly be used for creating a pure API around any
         | mutability under the hood
         | 
         | But we already had ST for that, so the real advantage of LTs
         | over ST is you can type-safely "freeze" and "thaw" your mutable
         | structure without copying.
         | 
         | The mutable array example in the Linear Haskell paper is a
         | simple comparison to what you're asking for.
        
       | Kbsm wrote:
       | Learn more about the new linear types extension in GHC here:
       | https://tek.brick.do/64693fb8-39b4-40a5-8762-768009eeed91
        
       | juancampa wrote:
       | > Safe zero-copy data (de)serialization, a notoriously difficult
       | endeavour that is in fact so error prone without linear types
       | that most production systems today typically avoid it.
       | 
       | I wonder how this compares to Rust's borrowing approach which can
       | be used for the same goal. I don't know enough about Haskell to
       | understand Linear types. Perhaps someone more knowledgeable can
       | comment.
        
         | platz wrote:
         | GHC models linearity differently. Rust puts linearity on the
         | types, GHC puts linearity on the arrows.
         | https://i.imgur.com/s0Mxhcr_d.webp?maxwidth=640&shape=thumb&...
        
           | ric2b wrote:
           | What is "the arrow" in this context?
        
             | tremon wrote:
             | "the arrow" is an expression of a function operation, or
             | rather, how a function transforms its arguments. In
             | Haskell, the following signature:                 func :: A
             | -> B
             | 
             | declares a function _func_ that takes one argument of type
             | _A_ and returns a value of type _B_.
             | 
             | I'm guessing a bit, I feel this goes over my head, but
             | here's my layman's interpretation:
             | 
             | In Rust, in order to declare a linear type, you must do so
             | when initializing the variable:                 let x =
             | vec![1, 2, 3];
             | 
             | Now _x_ is declared as move-only, so that the vector cannot
             | be duplicated by assigning it to a second variable; after
             | assignment, accessing _x_ will result in a compiler error.
             | 
             | In Haskell, you declare a function                 func ::
             | A %1 -> B
             | 
             | to indicate that after this function call, accessing _A_
             | should result in a compiler error.
        
               | paavohtl wrote:
               | I don't find this answer satisfactory, because you also
               | need to specify in Rust whether or not a function moves
               | its argument(s).                 // This moves the
               | argument       fn func(a: A) -> B { .. }       func(x);
               | // x is not accessible after this line              //
               | This borrows the argument, and therefore won't move it
               | fn func(a: &A) -> B { .. }       func(&x); // x remains
               | accessible after this line
        
           | the_duke wrote:
           | Do you know the reasoning why?
           | 
           | Not tying the linearity to the type seems quite a bit more
           | confusing and error prone to me.
           | 
           | If a type can be used in different ways, I'd rather have two
           | different types.
        
             | platz wrote:
             | The core logic theory behind linearity on the arrows is
             | provably sound and more well understood theoretically, and
             | it fits better with Haskell's existing semantics.
             | 
             | The rust way is probably more practically useful from a
             | programmer's perspective, but it isn't as easily understood
             | to be sound from a type theory perspective.
        
               | the_duke wrote:
               | So to be clear:
               | 
               | Let's say a function returns a linear value. If I then
               | feed this value into a function that does not specify
               | linearity, all linearity is lost and the function can
               | treat it as a regular non-linear type?
               | 
               | If yes that would seem to require extreme discipline by
               | the programmer to remain useful.
        
               | mauricioc wrote:
               | There is no such thing as a "linear value" per se, since
               | linearity is on the arrow. But speaking of linear values
               | makes sense when there's an ambient monad, and all of the
               | restrictions you expect are enforced; see Section 2.7
               | (and the last paragraph of Section 6.1) of
               | https://arxiv.org/pdf/1710.09756.pdf for details.
        
               | whateveracct wrote:
               | No that's not the case. I am fairly sure that situation
               | you describe results in a type error. The extension does
               | in fact enforce linearity, and does not have such a
               | trivial & universal escape hatch afaiu.
               | 
               | Feel free to give GHC 9.0 a try with a minimal example of
               | your scenario!
        
               | ghostwriter wrote:
               | There is a recent article about these scenarios, you may
               | find it helpful -
               | https://tek.brick.do/64693fb8-39b4-40a5-8762-768009eeed91
        
         | Raphael_Amiard wrote:
         | Affine types, which Rust more or less implements with its
         | ownership system are very close to linear types, see
         | https://en.m.wikipedia.org/wiki/Substructural_type_system#Af...
        
         | sa1 wrote:
         | Rust has affine types (values can be used upto 1 time), which
         | are similar to linear types(values can be used exactly once).
         | Affine types are slightly more general, but offer less
         | guarantees about your program. To add back some guarantees,
         | Rust also has lifetime analysis.
         | 
         | So for most common use-cases, you will be able to use Rust and
         | Haskell for similar guarantees. But I'm also eager to see what
         | unique use-cases will be enabled by each of them.
        
       ___________________________________________________________________
       (page generated 2020-12-30 23:00 UTC)