1. 52
    1. 8

      Someone pointed out the other day that this (kind of operation) is basically a syntax sugar for the ‘continuation monad’, which is the most general purpose monad (abstracting over callbacks). And that makes a lot of sense. Further reading: https://blog.poisson.chat/posts/2019-10-26-reasonable-continuations.html

      1. 3

        Yep. When I realized this I was so weirded out I went on the Gleam discord and asked “is… is this on purpose? You guys know what you’ve done, right? Are you going to use this for general coroutines or something?” The answer was basically “yeah we know, it’ll probably get used for other things someday but for now we’re mostly just interested in error handling”.

        1. 2

          It’s a clever design. The reason why I even thought to compare it to the continuation monad is because someone in the OCaml Discord pointed it out. The equivalent in OCaml is something like, let ( let@ ) = ( @@ ), and then you can do eg let@ channel = In_channel.with_open_bin "file.txt" in ...

          1. 1

            It’s a very clever design! For me the jury is still out whether it’s too clever, but I look forward to the results.

      2. 1

        Half the thread on the introduction of use expressions was how they’re equivalent to dot expressions.

        1. 1

          Sorry, what’s a ‘dot expression’? Do you mean ‘do expression’?

          1. 1

            I do! Damn too late to edit.

            1. 2

              Oh OK. I think a ‘do’ expression is restricted to working with the monadic bind operation though (and applicative fmap if you are using the extension). The ‘continuation monad’ syntax I am talking about is a general-purpose one that can work with any callback-style API. Another example is here https://lobste.rs/s/mmje1n/using_use_gleam#c_tvvzer

              1. 2

                Ahh, got it! Thank you!

    2. 6

      Gleam “use” syntax is very powerful and an example of great language design, a single language feature instead of many specialized features (async-await, generator, question-mark-operator, single monad do-expression, …)

      1. 3

        Would even be better if they did in the state-of-the-art way. E.g. https://github.com/dsyme/dsyme-presentations/blob/master/design-notes/ces-compared.md or also similar to how Haskell/Scala do it.

    3. 4

      This is just like Roc’s backpassing syntax, which Richard said they’re probably removing (from a submission a couple months ago) in favor of the neater but less general “!” syntax. I didn’t realize Gleam had it too. I appreciate features like this that help avoid rightward drift of code.

      1. 1

        It’s interesting how Roc and Gleam are going different ways. We used to have a specific try and then moved to the general use.

        1. 2

          I think Gleam is doing it the right way. It’s a bit sad that it seems to reinvent the wheel.

          Rather, look around and pick what works (and improve on it). I’m not a big F# fan, but I think they nailed it quite well: https://github.com/dsyme/dsyme-presentations/blob/master/design-notes/ces-compared.md

    4. 3

      Would this cover the await syntax transformation too? Suppose I had something like a Promise and wanted to chain together .then calls (with rejects automatically bailing early).

      (Obviously you’d need to use the results of any blocks within the function, too, as well as the function result (in this example functions are “colored”))

      1. 10

        Would this cover the await syntax transformation too? Suppose I had something like a Promise and wanted to chain together .then calls (with rejects automatically bailing early).

        Yep! You can think of use as a generalization of the await transformation. When targeting JS, promise.await takes a callback as its last argument so you can use it like so

        use user <- promise.await(get_user())
        use posts <- promise.await(get_posts(user))
        display_posts(posts)
        

        Much like await in JS, the return value of this block would itself be a promise.

    5. 2

      I’m still torn on whether or not I think use is worth the cognitive load. While Gleam technically doesn’t support early return, it means that for any line like use ... <- my_fn(), my_fn can do an early return (effectively) if it so chooses–but that logic is always buried inside my_fn. But many instances of use don’t function that way–they will always (practically) call the callback. Perhaps it’s just an adjustment period for me as it’s clearly a very useful feature.

      1. 8

        Given the number of very complex features of other languages Gleam has side-stepped with one syntax sugar I think it has been a tremendous success!

        But many instances of use don’t function that way–they will always (practically) call the callback.

        The majority of use is for monadic APIs in which the callback is called conditionally. It’s uncommon for the callback to always be called.

        1. 2

          I think this other commenter pointing out that this is just how await works makes me realize this is just likely a symptom of unfamiliarity

    6. 2

      For the first example,

      fn catify_without_use(strings: List(String)) -> List(String) {
        list.map(strings, fn(string) {
          string <> " cat"
        })
      }
      

      Becomes…

      fn catify_with_use(strings: List(String)) -> List(String) {
        use string <- list.map(strings)
        string <> " cat"
      }
      

      Is the rest of the catify_with_use function treated as part of the loop body? With the list.map(...) syntax, I could be certain that fn(string) would be executed once per iteration. With the use string <- list.map(strings) syntax, does every following line get executed on each iteration? For example:

      fn catify_with_use(strings: List(String)) -> List(String) {
        use string <- list.map(strings)
        string <> " cat"
        debug("hi")
      }
      

      Does debug get called every iteration now?

      1. 6

        It does yes, and if you rewrite the example to not use the use sugar, you can see more clearly how it’d work.

        fn catify_with_use(strings: List(String)) -> List(String) {
          list.map(strings, fn(string) {
            string <> " cat"
            debug("hi")
          })
        }
        

        So calling catify_with_use(["wibble", "wobble"]) will result in "hi" being printed twice, and the returned list being ["hi", "hi"] as the last expression is debug("hi") (it prints its argument then returns it).

        1. 1

          Ok thanks for the clarification! I’m not sure I like that, but it is an interesting language feature.

      2. 5

        To anticipate another question you may have: as I understand the article, to limit the scope of use you enclose it in a block. (No computer at hand, so not tested, sorry.)

        fn catify_with_use(strings: List(String)) -> List(String) {
          let old_possums_cats = {
            use string <- list.map(strings)
            string <> " cat"
          }
          debug("hi")
          old_possums_cats
        }
        
        catify_with_use(["jellicle", "dramatical"])
        // debug called once
        // returns ["jellicle cats", "dramatical cats"]
        
        1. 1

          That helps, thanks!

    7. 1

      Great article ! I’m starting to learn Gleam and I’m not sure I grok use yet but the feature just feels beautiful :)

      1. 4

        From the Gleam docs, this is just syntactic sugar for the following. This code

        use a <- foo
        example(a)
        use b, c <- bar(a)
        example(b, c)
        

        is equivalent to this

        foo(fn(a) {
          example(a)
          bar(a, fn (b, c) {
            example(b, c) 
          })
        })
        

        the variables extracted with the “use” are just the values you would get if passing a callback as last argument.

    8. 1

      This is neat, Gleam looks really promising!

      When I see the map example though (which looks like an obvious “please do not do this, this is not what this is meant for”), I wonder if use’s elegance is hurting simplicity. This reminds me of Scala’s implicits, that are very powerful but can get confusing.

      1. 1

        Yeah, I probably would use it for list.map myself. I think the conventional callback syntax is clearer in that case.