Threads for lpil

    1. 4

      I’m not a big fan of the untypeable pipe in Elixir and Gleam. By passing the left side of the pipe as the first argument to the function call on the right, you’ve created an operator which has no meaningful type (that I’m aware of).

      By contrast, the & function in Haskell (which the OP conveniently ignores) is a regular function that has the type a -> (a -> b) -> b, and which allows you to rewrite the example from the article as

      [1,2,3] 
        & map (+1) 
        & filter (> 2) 
        & (!! 1)
      

      In other words, pipes can just as easily work with the left operand used as the last argument in the right function.

      The only justification I can think of (in Elixir, which I am more familiar with than Gleam) is that pipe-argument-last does not play well with optional and keyword arguments.

      1. 3

        you’ve created an operator which has no meaningful type (that I’m aware of).

        This is why it’s syntax rather than a function. Gleam’s core is very small, only things which cannot be implemented in userland get into core.

        1. 1

          Sure, but that’s only so because Gleam chose to make the pipe work on the first argument. If Gleam had chosen to work on the last argument, it didn’t have to be a part of the core, it could just be a regular (infix) function defined in userland.

          1. 2

            That’s unfortunately only true for languages like Haskell where functions are automatically curried.

            In uncurried languages like Elixir, Gleam, and Hack it would mean you can only use pipes with 1-arity functions, which would mean you have to wrap each function in a closure.

            xs
            |> fn(x) { list.map(to_string, x) }
            |> fn(x) { list.fold("", append, x) }
            
            1. 1

              Fair point, but that doesn’t justify why pipe puts the argument first instead of last.

              1. 1

                Once you don’t have autocurrying there is no difference between first and last, they have the exact same properties.

                In this case Gleam picked first as it is the convention in the BEAM ecosystem, and it mirrors OOP in Go, Python, Rust, and other popular languages.

      2. 2

        Neat! I didn’t know about the & operator in Haskell. I updated my sidenote with a link to it!

        Do you know of any libraries that use the & operator so I can see it in action?

        Gleam’s pipe operator will apply the function on the right if it doesn’t make sense to slot the argument on the left, see the note at the end of the section on function capturing in the language tour:

        The pipe operator will first check to see if the left hand value could be used as the first argument to the call, e.g. a |> b(1, 2) would become b(a, 1, 2).

        If not it falls back to calling the result of the right hand side as a function , e.g. b(1, 2)(a).

        1. 1

          Do you know of any libraries that use the & operator so I can see it in action?

          I don’t know about libraries, but it is used in Futhark. There’s also a functor version: <&>. The regular version is also very common in Elm and F# under the name |>. In all instances (actually, I’m not sure about F#, but at least in Elm and Haskell), it is a regular function, so users can define their own “overloaded” versions, like <&>, or even pass it to or return it from other functions.

          Gleam’s pipe operator will apply the function on the right if it doesn’t make sense to slot the argument on the left, see the note at the end of the section on function capturing in the language tour

          Interesting! One of these days I really have to dive into Gleam some more.

    2. 1

      More data to feed to Microsoft GitHub Copilot

      1. 25

        How was any of this not already public?

      2. 11

        Firefox is already mirrored on GitHub many times. Nothing has changed as far as copilot is concerned.

    3. 2

      I have to say that, to me, Scala was always miles ahead Erlang and Elixir. But I’d love to work on the BEAM because it seems like a way nicer ecosystem for distributed systems. Now gleam looks very nice and almost Scala actually. The only real thing I’m missing now is an effect-system. I really really hope it comes soon.

      https://gleam.run/frequently-asked-questions/#does-gleam-have-side-effects

      1. 4

        The core team have been quietly playing with effects systems but have not yet devised a design that is clearly the way to go.

        I’d love to hear what you look to gain from a feature like this and if you have any specific ideas and use cases 🙏

        1. 3

          It allows for easy control and readibility of anything concurrent that is not distributed. I.e. basic things like “run action A and retry it 3 times - but at the same time, run action B without retries. Then choose whichever succeeds first and cancel the other one”. Or more complicated ones that involved resources and streaming, all on the same machine.

          The distributed nature of the BEAM then comes on top, because I need multiple machines for e.g. load distribution, availability and so on. But within one machine, for me, an effect system has been the most productive way to describe how the logic should be build/composed and then executed. After having worked in both ways, I have a hard time imagining going back to program in a language where I need to run my actions “directly” instead of composing them.

          1. 2

            I.e. basic things like “run action A and retry it 3 times - but at the same time, run action B without retries. Then choose whichever succeeds first and cancel the other one”. […]

            […], for me, an effect system has been the most productive way to describe how the logic should be build/composed and then executed. After having worked in both ways, I have a hard time imagining going back to program in a language where I need to run my actions “directly” instead of composing them.

            For those of us who have not used an effect system but are attracted to the concept, do you have a link to where we could see such use of effects in practice? I for one don’t recall previously having heard of an effect system being used for retry control like this.

            1. 2

              If you have never worked with an effect system, then you should know that it’s a fancy term for a rather simple thing. Essentially, instead of running code that does things (like writing/reading from a DB, getting the current system time, making an http request) directly, you wrap it into a function (with no parameters) so that you can use it later. The whole point is then to create those functions and combine them with each other without executing them, essentially just creating “execution plans”.

              So your code starts to look like this

              let plan1 = ... some effect/action (a lazy function on steroids)
              let plan2 = ... some other effect/action
              let plan3 = ... yet another effect/action
              
              let plan4 = plan1.race(plan2)
              let plan5 = plan4.repeat(3times, exponential-backoff)
              
              let plan6 = plan5.timeout(max-waiting-time: 1min).ifFailedRecoverWith(plan3)
              

              If you just were to run this code, nothing would happen and your program would be finished immediately, because you only created functions without every running them. So with that style, at the very end of your program you do plan6.execute() and it will try to run plan1 and plan2 and uses whichever finishes first - or if they both fail, retry 3 times. But only until 1min is over, then cancelling the still running plans and falling back to plan3. The utility functions like “race”, “repeat”, “timeout” etc. are mostly builtin or you can extend the effect-class/effect-type with your own, which makes the code very concise and nice to read.

              After getting used to write my code like that, I find it extraordinarily hard to go back to the style I used before.

              As for your original question of examples of those systems/libraries, there are a couple. The one I currently use is ZIO which has builtin retries. In Scala there is another one called Cats-effect which has an additional module for retries. Kotlin also has an effect-system library with no builtin retries for the effects (to my knowledge). And typescript also has an effect-system library which comes with builtin retries.

              I’m sure there are some others out there, but those are the ones that I have personally tried out.

            2. 1

              One addition to my other post. Besides having builtin niceties like retries, races, concurrency control, streaming, error-handling and so on, one thing that has become crucial to me is the ease of extending my code-flow with analytics-utilities. For example, with Scala ZIO I can do this:

              let plan4 = plan1.track("plan1").race(plan2.track("plan2")).track("plan4")
              let plan5 = plan4.repeat(3times, exponential-backoff).track("plan5")
              

              What I also do (not shown in the example) is pass metadata such as parameters to the plans to the tracking. And when the program is run, I get very nice traces/spans from the excution, showing me exactly when code was started, finished, how long it took and what the parameters where - in exactly the granularity I want. Setting this up in the Java-world with magical reflection/byte-code-manipulation or annotations is such a PITA compared and always surprises me with edgecases that don’t work or, worse, show things in a wrong way because it is hard to correctly track nested actions and things like that.

          2. 1

            Sorry, could you be more precise when you say “it”? There’s quite a large number of features that get called “effects” so we have to be overly descriptive about what specific functionality we’re talking about.

            1. 1

              Right, so what I mean by effect is any piece of evaluatable code that will break referential transparency when executed directly.

              In other words, I mean just a fancy wrapping-function without parameters so that the code is not executed directly but only when the function is called. But instead of being just a function, it would be a special kind of function that allows for specific kind of composition and allows control of execution (e.g. concurrency) and everything that comes with it (when running concurrently on a different thread, that thread can blow up, it can timeout and so on). See also my other post.

              I’m not talking about a capability system or anything like that - which might also be nice to have, but I wouldn’t require that to be able to work productively in language.

              1. 1

                There’s lots of different designs and systems with those properties so it’s hard for me to understand what it is that you want. If you have some designs to propose we’d be very interested in them so please feel free to share if that time comes. Thank you

                1. 1

                  I have worked with a few effect systems and I found Zio to be the best one because of how pragmatic it is. If you check the website you will see that it doesn’t really focus on being an “effect system” but rather on what benefits it provides.

                  I’m happy to explain in more detail if the website/documentation doesn’t give you the idea what design I mean.

                  Even if gleam cannot provide all the features of zio, the most basic subset would already be incredibly beneficial.

                  1. 1

                    Ah! This seems more like a concurrency framework than what I’m used to people calling an effects system. It’s unlikely we’d see anything like this in Gleam as it typically makes use of the much more mature OTP actor framework.

                    1. 1

                      Aren’t those things fairly orthogonal though?

                      An effect-system is not a concurrency framework. It’s just that it’s very easy to build composable concurrency on top of it. But you can use effects totally without even offering the capability of concurrency. In fact, ZIO has an effect-type specifically for that.

                      I worked with Scala Akka, which is an actor framework similar to what you do with Erlang/Beam (though not as nice). And actors are super useful, but they come with their own set of problems. So when I used actors, my approach was to use effects within an actor and then actors for the whole system, e.g. to define robust borders with supervision, messagepasing (incl. everything that comes with it, such as dead letters). The latter is a good example of what you can’t just do with an effect-system. It doesn’t offer any mechanisms for load distribution, handling overloads and such.

                      But on the other hand, with actors alone it becomes much harder to cleanly orchestrate the actions that are run (including sending/receiving messages), especially as soon as state comes into play and your actor turns into a complicated state-machine (which sooner or later always happens). So to me, those two are orthogonal.

                      I’m not familiar with gleam OTP, but after having a quick look, it looks like there is something called “Task” - and that looks very much like an effect-type. ;-)

                      So you do already have it in there, it’s just that it’s more of an “accidential” effect-type. Similar to how someone who never heard / is not familiar with the concept of a “parser” would still write code that parses things. But knowing what a parser is, calling it like that and learning from existing parsers to provide standard utilities for composing/manipulating them is very beneficial. So maybe you can have a look at ZIO (or any other effect system for that matter) and then see how that might relate to the building blocks within gleam OTP?

                      EDIT: to frame it in yet another way, for me, an effect-system offers the basic building blocks and an actor-system is a much more highlevel and focussed framework for application development. And the actor-system might very well be built with the very same effect-system that is exposed to the developer.

                      1. 1

                        I’m sorry, I still don’t understand what design you’re advocating for. I had a look at the docs for the library you linked but it was all on the topic of concurrency.

                        I would be very interested in hearing what use cases and designs you have in mind, but I need you to be concrete about what you mean. You’re using highly overloaded language and as such I don’t actually know what design you are talking about when you give an overview of the benefits. There are many many designs that are named “effects systems”.

                        It would be useful if you could describe the design without using the term “effect” and other such vague language.

                        1. 1

                          I see that I’m failing to give a good explanation - sorry for that. As I already said, the precise definition is:

                          so what I mean by effect is any piece of evaluatable code that will break referential transparency when executed directly

                          And referential transparency is a fancy term, but in practice it pretty much means: “if you run the same piece of code (with the same arguments) twice, then if it always returns the same result, no matter of the state of the world, the piece of code is referential transparent, otherwise it is not”. (it also applies when nothing is return but when it still makes a difference when or how often it is called, e.g. writing to a db)

                          So when you run system.getTime() and the return-type is a date, then this function can not be referential transparent because it will return different results depending on the time when you call it.

                          So with an effect-system what you can do is wrap it into an effect. Essentially, it doesn’t return the time, it returns an effect (read: a function without parameters) that, when executed, returns the time.

                          It might sound totally stupid if you read it like that, but in practice it is very helpful. Even without concurrency. State-handling, errors, meta-functionality (such as tracing/logging) and even dependency-injection become much simpler when using this style consistently.

                          So in other words, when you read me writing “effect-type” then just read it as “a parameter-less function with utility-methods that can be executed and will return a result”.

                          1. 1

                            I understand what referential transparency is, and I understand those properties, I’m asking what design you had in mind that would have those properties and you think would be a good fit for Gleam.

                            There are a great many different designs that have all the properties you describe, and many of them quite varied in their implementation and what they are like to use.

                            1. 1

                              Oh, I see, so you was asking what “flavour” of effect-system I would prefer?

                              To be honest, at this point, I would really be happy with any kind. Because not having any is a blocker for me when thinking about using a language. But I can totally live with a “suboptimal” effect-system.

                              I think since Gleam strives to be a pragmatic language, I wouldn’t try to build the perfect effect-system that is extremely extensible and sound, but rather make it pragmatic. So probably rather like ZIO where a couple of different things are combined into a single type. So a combination of the Task we talked about, together with Gleam’s result type and some good support for interacting with message sending/receiving (since sending and receiving messages are effects too).

                              Optimally it would also be nice, if the effect-type supports dependency injection, as in “this effect requires a user-repository”, but that’s a nice to have.

                              1. 1

                                I wouldn’t recommend using the Task module for that, I think it’s for something else, but if you don’t have any specific requirements I think you could quite easily implement one in useland using thunks and use.

                                1. 1

                                  Yeah sure! I just wanted to say that the Task module already looks like something that I would expected from an effect-system. Often in those systems, “task” is the name for a certain flavour of an effect - for a reason.

                                  The thing is for effects - and therefore the effect-type used to model them: they are so universal, it would be bad to not have a common standard. it’s as if you would have implement lists all the time yourself. It’s definitely good if 1.) it is provided in a standard way and 2.) if the language pays a bit of attention to make it easy to use it in a smooth way.

                                  For instance, gleams “use”-syntax (and similar constructs in other languages) is really important to have nice syntax when working with effects.

                      2. 1

                        From your other comments, is an effect system similar to do notation in other languages? I believe Gleam’s use counts.

                        https://gleam.run/book/tour/use.html

                        1. 1

                          do-notation (or in Gleam “use” and in Scala “for”) ist just syntax that makes effect-composition or other kind of composition much smoother. So they are orthogonal but they complement each other really well.

                          If you look at the example in your link:

                          use <- logger.record_timing
                          use db <- database.connect
                          use f <- file.open("file.txt")
                          

                          Now, when those 3 lines of code are executed then the actions in there start to happen. The execution of this code might take e.g. 3 seconds (connecting to the db takes long).

                          With an effect system, the approach is a but different and would look like this

                          let executionPlan: Effect[FileHandler] = {
                            use <- logger.record_timing // record_timing method returns an effect-type in this example!
                            use db <- database.connect
                            use f <- file.open("file.txt")
                            usereturn f
                          }
                          

                          so you describe your actions and combine them (here using the “use”-notation which means sequential composition) and store the result in a varlable that, when run, will execute the actions and return the file handler (or whatever you chose to return). Running this code takes a millisecond or so.

                          So later you do

                          executionPlan.run()
                          

                          and this will then take 3 seconds because the actions are actually execute. You might do

                          executionPlan.retry(3times).run()
                          

                          instead. Now, if you are familiar with gleam, I would be curious how you would write the code if you want to achieve the same retry-functionality. I assume you would wrap the original code into a function and then do something with it? Essentially, by doing so you would be inventing an ad-hoc simple effect-type, create an instance of it (the function), do something with it (put retry-logic around it) and then execute it. And what I propose is to have a common-utility type for that kind of ad-hoc function instead.

                          I hope that makes it a bit clear.

    4. 9

      I was very fond of my X220 and would still use for it if it wasn’t stolen from me, but even at the time the display was disappointing and the trackpad was dreadful. It wouldn’t call it the best, certainly not now.

      1. 3

        you’re not supposed to use the trackpad, you can disable it in the bios :)

        1. 10

          “the feature is so bad that I recommend removing it” doesn’t scream “best laptop” to me.

          1. 2

            Most laptops don’t even let you disable it, and provide no alternative. Can’t see how that’s better.

            1. 3

              Most modern laptops have fully functional trackpads with gesture support. Apple’s even have proper haptics, so you can push the whole thing with a uniform click instead of the hinged designs most have. I used to be a TrackPoint diehard, but I don’t miss it after using a MacBook.

              1. 3

                You still have to take your hands off the keyboard to use it though, right?

                1. 2

                  While I certainly prefer the ThinkPad TrackPoint over traditional trackpads, I must admit, the MacBook’s trackpad is surprisingly usable with just my thumbs and without my fingers leaving the home row.

              2. 1

                I don’t miss it after using a MacBook.

                How odd. My home desktop is an iMac, but I have no other Mac laptops, because I hate modern Apple keyboards and pointing devices. Then, when I smashed my right arm to bits in April, $WORK lent me an M1 MacBook Air, becasue it can take dictation. I now have fairly extensive experience of using a modern Apple trackpad on current macOS.

                It has made me appreciate my Thinkpads running Linux much much more. The experience, for me, is horrible. I find it a crippled and deeply frustrating input device. It’s moderately good at things I don’t want to do, such as zooming, switching between full-screen apps, or switching virtual desktops, and it’s really bad at things I do all the time pretty much every day: middle-clicking, right-clicking, highlighting, dragging.

            2. 1

              There are countless laptops with a trackpoint and also a functioning trackpad.

              1. 2

                Yeah, and the best of them are ThinkPads.

          2. 2

            I think you are misreading the point here.

            Unlike most other laptops, the Thinkpad comes with a superior input device: the trackpoint. It requires less finger movement and it has 3 mouse buttons. That is why many people, including me, simply disable the trackpad.

            1. 2

              No, I understand that. What I mean is that the x220 has flaws that make it not a good contender for “best laptop”, even if you’re only interested in the trackpoint. A laptop that has a borderline unusable trackpad (the x220) is certainly inferior to one that uses that space to make the trackpoint experience better (the x200). A poor attempt at both is worse than excelling at one.

              1. 1

                I disagree, as you might expect.

                For one thing, you need to place in its context.

                In general, it’s a 2011 laptop. This means several things:

                1. Back then, trackpads were much smaller. It was normal for its time. For me, it wins by having physical buttons; I have also had an X240 at work, and used an X230, and the 220 beats both because it has physical buttons which are more use. It supports multitouch, and it works fine for two-fingered scrolling, which is about the only type of gesture I ever use.
                2. For its time, the screen was reasonable. It seems small and low-res now but it was acceptable for its time.
                3. Because it was 6 years old, my first one cost me a princely £150 in 2017. My second one cost me £250 in 2022, but it is the Core i7 model, it’s in mint condition, and it came with a docking station.
                4. But because it was a premium executive machine when new, they still work very well. My i7 machine has two SSDs in it (one SATA, one miniPCI) and 16GB of RAM, and the RAM cost about £20. I used my i5 one last year when I had to make a bus trip, and I wrote for the entire journey; its original 11 year old battery kept it working for a 3 and a half hour bus journey. And because it’s external I can trivially easily replace it, and even carry a spare.

                So its limitations are explicable due to its age, but its age also brings benefits.

                1. 2

                  The point of the article is that it’s the best laptop today and that its age doesn’t impact that.

                  At the time I was disappointed by both the trackpad and the display.

                  1. 1

                    OK, that’s fair.

        2. 3

          Quality multi touch trackpads and gestures are too good. I’ll never go back to a Lenovo laptop unless it has one.

          1. 1

            Since at least the x40 generation (Haswell) it’s all been decent Synaptics multi-touch trackpads. Nothing extraordinary, but nothing bothersome either, more than fine.

    5. 10

      the device is still better equipped to handle drops and mishandling compared to that of more fragile devices (such as the MacBook Air or Framework).

      In my experience this isn’t true (at least for the Framework), and the post doesn’t provide any proof for this claim.

      I’ve owned a ThinkPad X230, which is almost the same as the X220 apart from the keyboard and slightly newer CPU. I currently own a Framework 13. Although I didn’t own them both at the same time, and I also have no proof for the counter-claim, in my experience the Framework is no more fragile than the X230 and I feel equally or more confident treating the Framework as “a device you can snatch up off your desk, whip into your travel bag and be on your way.”

      (I remember the first week I had the X230 I cracked the plastic case because I was treating it at the same as the laptop it had replaced, a ThinkPad X61. The X61 really was a tank, there’s a lot to be said for metal outer cases…)

      The rugged design and bulkier weight help put my mind at ease - which is something I can’t say for newer laptop builds.

      Confidence and security are subjective feelings, so if owning a chunky ThinkPad makes someone feel this way then good for them. Not to mention I think it’s awesome to keep older devices out of e-waste. However, I don’t think there’s any objective evidence that all newer laptops are automatically fragile because they’re thin - that’s perception as well.

      1. 9

        I owned both a X220 and a X230 and I found the X230 to be much less durable than the X220, so the framework comparison might not quite stand up.

        1. 2

          Oh that is good to know, thanks. I’d assumed they were mostly the same construction.

      2. 3

        However, I don’t think there’s any objective evidence that all newer laptops are automatically fragile because they’re thin - that’s perception as well.

        It’s a reasonable null hypothesis that a thicker chassis absorbs more shock before electronic components start breaking or crunching against each other. Maintaining the same drop resistance would require the newer components to be more durable than the older ones, which is the opposite of what I’d expect since the electronics are smaller and more densely packed.

      3. 3

        How does the framework’s keyboard & trackpad measure up against the Thinkpad’s?

        1. 4

          It’s been years since my x220 died, but IMO the trackpad on the framework is leaps and bounds better than the trackpad on the x220. (Though, the one caviat is that the physical click on my framework’s trackpad died which is a shame since I much prefer to have physical feedback for clicking. I really ought to figure out how hard that would be to fix.)

          The x220’s keyboard is maybe slightly better, but I find just about any laptop keyboard to be “usable” and nothing more, so I’m probably not the right person to ask.

          1. 4

            x220 keyboard is peak laptop keyboard

            1. 1

              try x200 it’s better.

              1. 1

                I own both an x220 and an x201, full disclosure, I don’t find them better than my 2020 MBP, but not because of the keyboard….

                1. 1

                  I meant that the x200 keyboard is better than the x220 keyboard

        2. 3

          From my recollection: keyboard of the X230 about the same, trackpad of the Framework better (under Linux).

          The X230 switched to the “chiclet” keyboard so it’s considered less nice than the X220 one (people literally swap the keyboard over and mod the BIOS to accept it). I think they are both decent keyboards for modern laptops, reasonable key travel, and don’t have any of the nasty flex or flimsiness of consumer laptop keyboards. But not the absolute greatest, either.

          I remember the X230 trackpad being a total pain with spurious touches, palm detection, etc. None of that grief with the Framework, but that might also be seven-ish years of software development.

        3. 2

          I’ve tried both.

          The Framework’s input devices are, for me, very poor.

    6. 7

      Congratulations for the release !

      Wisp website, docs and examples looks really concise and are easy to follow for a non gleam dev: I will give it a try this week :)

      1. 4

        Thank you, very kind 💜

    7. 2

      Great to see a project written in Gleam getting attention!

    8. 4

      to_display has a typo:

      --- old
      +++ new
      @ -1,2 +1,2 @
      -pub fn to_display(self: Friend) -> String {
      +pub fn to_display(self: Friend) -> Display {
        Display(fn(options: DisplayOptions) { ... })
      }
      

      Really the only difference is that we manually convert from Friend to Display in Gleam, where Rust already knows how to use Friend as a Display.

      You can improve the representation of the “trait record” to get rid of this manual conversion. The result is known as the “dictionary passing” translation of type classes.

      Here’s how you you’d translate the Display example (I don’t know Gleam, I’m just making it up based on what you wrote in your post):

      // trait Display {
      //   fn format(self, DisplayOptions) -> String; 
      // }
      pub type Display<Self> {
        Display(format: fn(Self, DisplayOptions) -> String)
      }
      
      // impl Display for Friend {
      //   fn format(self: Friend, options: DisplayOptions -> String { ... }
      // }
      pub const display_friend : Display<Friend> =
        Display(fn(value: Friend, options: DisplayOptions) { /* use options and value.name */ });
      

      A function that takes a Display-able type gets this signature: fn my_function<A>(display_impl: Display<A>, value: A, ...) -> ....

      This also solves hwayne’s comment about using types at multiple traits. A function that takes a Display + Ord-able type gets this signature: fn my_function<A>(display_impl: Display<A>, ord_impl: Ord<A>, value: A, ...) -> .... You’ve gestured in a similar direction in this comment, and I think improving the representation of the “trait record” helps.

      1. 3

        This also solves hwayne’s comment about using types at multiple traits. A function that takes a Display + Ord-able type gets this signature: fn my_function(display_impl: Display, ord_impl: Ord, value: A, …) -> …. You’ve gestured in a similar direction in this comment, and I think improving the representation of the “trait record” helps.

        It seems like that has slightly different properties, because there’s no guarantee that disply_impl, ord_impl and value are all referencing the same value.

        1. 1

          there’s no guarantee that disply_impl, ord_impl and value are all referencing the same value.

          display_impl: Display<A> and ord_impl: Ord<A> don’t “reference” a value: they’re records of functions that operate on As.

          In fn my_function<A>(display_impl: Display<A>, ord_impl: Ord<A>, value: A, ...) -> ..., the body of the function would call display_impl.format(value) to format the value : A, and ord_impl.compare(value, ...) to compare the value : A with something else.

      2. 1

        Thanks for catching that incorrect type!

        Gleam is currently pretty limited in what is valid as a constant, so it wouldn’t quite work the way you describe, which is why I suggested passing conversion functions instead. Still interesting to think about tho! (and maybe Gleam will get more robust constants some day)

        1. 3

          Gleam supports both records and functions in constants!

          1. 1

            They don’t work with inlined functions like this (unless that got fixed). They have to be defined elsewhere and then referenced.

        2. 1

          Gleam is currently pretty limited in what is valid as a constant, so it wouldn’t quite work the way you describe

          That’s annoying. In the meantime you could change const display_friend : Display<Friend> to fn display_friend() : Display<Friend> for the same effect.

    9. 19

      While it’s true that “all you need is data and functions”, you could also say that “all you need is mov”, since “mov” is Turing complete. Type classes / traits, like many other PLT advancements, allow speaking more precisely and expressively about the desired semantics. Bounded quantification is strictly more expressive than universal quantification.

      1. 7

        Traits don’t actually add any expressive power to a language, they can at best make APIs more concise. Comparing not having traits to only using mov seems a bit much.

        1. 24

          they can at best make APIs more concise.

          That’s the definition of expressivity, no?

          I’m extra confused, because traits + constraints allow encoding logic that’s not at all possible otherwise at the type level (only permit a call if …), so how is that not expressive power?

          1. 6

            See Shutt’s notes on expressivity and abstractive power. I’ll quote the important bit:

            Language A can express language B if there is a simple way to rewrite B-programs as A-programs that preserves the overall pattern of semantics of programs — not only the semantic values of valid programs, but which programs are valid, i.e., halt and which are not valid/don’t halt. … That is, A can express B when there exists a decidable function φ mapping each B-program p to an A-program φ(p) such that φ(p) halts iff p halts. A can weakly express B when φ(p) halts if p halts (but not necessarily only if p halts).

            Usually, we can just replace a trait/typeclass dictionary with a struct or wrapper. In e.g. Haskell:

            f : Trait t => x -> y
            f : Trait t -> x -> y
            

            It’s clear that no expressive power is gained. It’s a way to delegate a bit of work to the compiler, to document abstractions, and to expose interfaces for implementation by other modules. See Oleg’s notes on dictionary-passing for a worked example which reduces Haskell typeclasses to OCaml structs.

            1. 6

              Shutt is talking about Felleisen’s paper “on the expressive power of programming languages”, and the key idea of that paper is more interesting than the part you quoted. That quote just describes any compiler, the Turing tar pit. Shutt’s next sentence is the interesting bit:

              How readily A can express B depends on how disruptively φ is allowed to rearrange the internal structure of p. Felleisen’s paper particularly focuses on the class of “macro”, a.k.a. “polynomial”, transformations φ, which correspond to Landin’s notion of syntactic sugar.

              Krishnamurthi did a nice presentation of it at “papers we love” https://youtu.be/43XaZEn2aLc

            2. 4

              Except the global coherence of type classes. Of course you can talk about incoherent instances and the like and claim that there’s no hard semantic difference, but you’d be ignoring the fact that writing incoherent instances is the exception and not the norm and if you write an API like this relying on instance coherence and if it misbehaves due to an incoherent instance, it’s the fault of the caller and not the implementer. So type classes make the semantics of coherence explicit, and the language makes sure that you have to fight it to get incoherent instances. Then you’re rewarded with the compiler doing the plumbing for you. So I don’t think you can ignore all this and pretend that they’re equivalent. And all of this not even mentioning the styles of code that would be too ugly to consider without the ergonomics of type classes, so even though you could express them, nobody would actually write code like this because even though the following seem like they’re equivalent:

              f : Trait t => x -> y
              f : Trait t -> x -> y
              

              The following certainly isn’t in practice:

              f : (A billion instances, some of them automatically constructed based on a chain of instances defined in terms of other instances) => x -> [y] -> SomeHKT z x (Maybe y)
              f : (A billion instances, some of them manually constructed based on a chain of instances defined in terms of other instances) -> x -> [y] -> SomeHKT z x (Maybe y)  
              
              1. 3

                I agree with you that they’re not equivalent. They happen to have the same expressive power: there aren’t algorithms which I can express with one but not the other. However, I’m only claiming that the rewrite goes in one direction; I’m not sure whether it’s possible to automatically turn structs into traits.

                I also agree with the rest of your point. When a language has traits or typeclasses, the compiler does a bit of extra work to resolve them.

          2. 4

            Traits are part of the type system, as are constraints. They are at the type level.

            1. 2

              And types are a part of a language.

          3. 3

            Sorry, to be clear I was using the computer science definition of expressive power, not the common use of the term expressive to mean “nice APIs”.

            Could you expand upon what would otherwise not be possible? Type classes de-sugar to additional function arguments, so anything you can express with them can be expressed without them.

            1. 3

              Yes, how can you express trait bounds that are statically checkable with regular functions?

              I guess you can technically express it with either a union type or if that’s not available, with multiple separately named functions (similar to ML modules).

              But even the article you linked distinguishes between theoretical and practical expressivity. I try to avoid the Turing tarpit, so I’m talking about practical expressivity, I.e. in this case expressing type bounds on a function without changing its name.

              1. 1

                Type class constraints are just syntactic sugar for an extra function argument per bound, so you can write it that way.

                I too am talking about practical expressivity. Type classes are practically very weak and can always be rewritten, they at most make APIs more concise. If we are to add such a large amount of complexity to a language I would like much more in exchange, and to not suffer a large compile time and/or runtime performance hit.

            2. 3

              Using the distinction between “theoretical expressivity” and “practical expressivity”, traits increase the theoretical expressivity of the type level language, and the practical expressivity of the value level language. Without them, there’s no way to ensure only a single implementation of a trait/vtable exists (or is used) for a given type.

              1. 2

                I was talking practically also- is that a useful property? Many type class/trait implementations don’t have that property (including the original paper), and in Haskell it’s common to introduce newtypes to work around that limitation.

        2. 12

          Traits don’t actually add any expressive power to a language, they can at best make APIs more concise.

          The same is true of functions. They don’t allow you to express anything that you can’t express with labels and goto. Like most language features, they are not there to make the language more expressive, they are there to bias the set of programs that can be concisely expressed. There is an infinite set of possible programs and any Turing-complete language can express them all. There is a finite set of correct, maintainable, and useful programs. The goal of a programming language is to provide an approximation of Huffman encoding over the set of all programs so that members of the set of desirable ones are shorter and easier to write than others.

          1. 3

            That’s true of the value language (that is, whether it is computable), but not of the type language (whether it can be statically verified or otherwise reasoned about). Functions do add to the expressive power of a language, type classes do not.

          2. 3

            Sure. The main difference is that functions are privileged primitive types in type theory, and so the native type theory of any programming language is going to have functions, regardless of the language under study.

            Like, take any fragment of code in any language, and use its native type theory to establish a precondition and postcondition. Then, we can convert that fragment into a function which maps from the precondition to the postcondition; even if the language doesn’t have syntactic functions, we can still apply the fragment in-place as if it were a function.

        3. 4

          They certainly add considerable expressivity to the type level language, and they also enforce consistency in the value level language. To emulate traits without traits would require explicit vtable (dictionary) passing. With vtable passing, there’s nothing preventing an intermediate function from swapping out trait implementations. Said another way, without traits, it is not possible to express that the vtable must be the same throughout the program.

          Traits also allow you to express blanket implementations, and ensures they are consistent and do not clash with other implementations.

          1. 1

            That’s a property of the Haskell implementation, but not of type classes generally. Many implementations (and the original paper) scope implementations so there can be multiple, and they can be swapped. Even in Haskell it’s common to work-around that limitation by introducing otherwise meaningless newtypes.

    10. 6

      I’m impressed with how consistent the releases are. How do you keep the release train flowing so well for a pre-1.0 language?

      1. 21

        Thanks to my sponsors I can just about afford to work on Gleam full time, and I’m very dedicated to the project. That’s about it really :)

        1. 1

          Always comes down to money doesn’t it!

          1. 2

            Unfortunately so! Hopefully we can grow the amount of sponsorship and make things easier in future.

    11. 3

      Great focus on ergonomics! I deployed Gleam to production for the first time last week. Thanks so much for all the hard work!

      1. 1

        Wow, that’s great! Congratulations. I’d love to hear more about how you’re using it if you’d be willing to share :)

    12. 5

      After reading the well written language tour, I have decided to give Gleam a try. I wanted to use it to build a single page application. Unfortunately, I was not able to figure out how to create and run a project with only a JavaScript runtime (is it possible?).

      I have searched GitHub issues and discussions. I have found template-gleam-javascript which is archived and understandably does not work. Unfortunately, using Discord requires an account, so I could not ask questions there.

      I think the language documentation is very good. I wish there was a document or an example application for starting with Gleam when you want to target a browser.

      1. 10

        Hello! Thanks for trying Gleam. Unfortunately the documentation is all very fresh at the moment, more in depth tutorials are to come later. That template is no longer needed as we now have JavaScript support in the build tool itself.

        To use the JavaScript target you can either add target = "javascript" to your gleam.toml or add --target javascript to gleam run, gleam test, etc.

        Here is an example SPA written in Gleam https://github.com/gleam-lang/developer-survey/tree/main/frontend

        Other places you can go to talk to the community is the GitHub discussions page or StackOverflow using the gleam tag.

        1. 1

          Do you know of other open-source projects we can study?

          A component I use a lot is the typeahead, like Twitter’s typeahead: async search, custom template for the results, etc. Would it be easy to hook into it or to replicate with Gleam? How would you approach that?

      2. 3

        The Gleam v0.16 release announcement contains a few example HTML+JS widgets written in Gleam and running in the page. Perhaps that code + examining the page source is enough to get started? You’re definitely right that it’s a gap in the docs.

        Most examples define 3 external fns that compile to JS’s document.querySelector, Reflect.get, and Reflect.set(on element, property, to value).

        1. 1

          That looks super neat. I’d love to ditch JS for a dynamic client application. Do we know of bigger code bases we could learn from?

          A UI component I use a lot is a typeahead, like twitter’s typeahead.js: async search, custom template for the results, etc. Would it be easy to hook into it or to replicate with Gleam?

    13. 1

      Hi Ipil,

      Is there a roadmap somewhere of what your are planning for Gleam and its ecosystem ?

      I read elsewhere that you are going to work full time on Gleam, so I’m curious :)

      1. 3

        Hello! Yes I’m now working full time on Gleam :)

        The GitHub issue tracker has some information but most the detail is in the discussion on Discord.

    14. 2

      Very confusing site. I spent 10 minutes clicking around, reading, and wondering what Gleam is.

      Apparently you have to click the logo to go to the main page…

      1. 2

        Sorry you found it confusing. Having the logo be a link to the homepage has been a web design standard for a long time so that was presumed sufficient. What could we do to make the navigation more intuitive for you?

        1. 2

          Thanks for your reply and no need to apologize.

          You’re right, lobste.rs has it as well so it might be just my old brain, but I would expect a ‘home’ section.

    15. 1

      I think the main problem is if you want to stop useing something earlier than the end of the scope. For example I want to use a database connection to read some data then release connection and process the data.

      I guess then you go back to explicit callbacks which isn’t a big problem.

      1. 2

        You can also introduce a new scope using { ... }, both would work.

        1. 1

          Which re-add an indentation level :/

          1. 1

            One level is OK, the intent is to avoid repeated intendation that grows across the page.

    16. 14

      Gleam is my favorite language that I haven’t actually tried yet (but I will as soon as I get home from my traveling).

      I read the language tour on my phone one evening, and the next evening I was reading and understanding source code. That’s how approachable the language is!

      use is interesting because it’s a fairly complex feature, but it opens up so many options (as shown in the link). At first I was skeptical, but I think I agree. It’s worth the added complexity.

      To give two examples of where Gleam has made (imo) interesting design decisions to keep the language simple:

      • There is no Option[T], only Result[T, E]. If you don’t want an error, just use Result[T, Nil]. Is it more typing? Sure, but now you only have one type instead of two.
      • A type (think struct) can have multiple constructors, each with their own fields. This means they effectively double as enums. Have a look at the docs, they explain it better than I can.

      Anyway, congrats Louis! Very inspiring work :-)

      1. 4

        A type (think struct) can have multiple constructors, each with their own fields. This means they effectively double as enums. Have a look at the docs, they explain it better than I can.

        This sounds like it’s taken straight from Haskell. (Not a criticism, just background.)

        1. 4

          One could do worse things then take inspiration from Haskell!

          I did not know this. :-)

        2. 4

          It’s not specifically inspired by Haskell, it was mostly inspired by Erlang. I didn’t realise that Haskell had the same feature but now I read the documentation it seems very similar. Cool stuff!

          1. 3

            I mean, Gleam does belong to the ML family of languages IMO, so we may as well say the feature is inspired by Standard ML! /s

            1. 1

              In what sense does it relate to ml?

              1. 7

                Some examples of “ML-like” features include…

                • Sum types as a primary data modeling tool, in some cases even displacing records for small-ish data structures

                • First-class functions and some use of recursion, often used to constrain and thereby elucidate control flow. This is sometimes in lieu of imperative-/procedural-style constructs such as…

                  • If/else statements (not if/else expressions)
                  • Switch/case statements (not if/else expressions)
                  • Goto, which has basically been a strawman against this style of programming for the past decade or so
                  • Defer, which though it allows code to be written out-of-order, still introduces a discrete procedural “step” to computation

                  In languages like Javascript and Gleam, this extends to the use of a syntactic construct you may know as a callback function.

                • Type inference (usually Damas–Hindley–Milner type inference)

                • Other idioms that, like the above, help to obviate and/or discourage the use of global state in implementations as they scale

                There are plenty of ML-like languages for different runtimes, including a few that are used for systems programming.† Languages often described as “ML-like” include…

                • Scala, ‘an ML for the JVM
                • F#, ‘an ML for the CLR
                • Elm, which also takes some inspiration from Haskell while not going quite as far with the generics, apparently for sake of error message readability
                • Facebook’s Reason, which is sometimes even called ReasonML for clarity
                • Rust, one of the only systems programming languages to have this distinction. Check for the features above if you don’t believe me!

                Haskell is explicitly inspired by ML, but is often considered its own category due to the radical departures it makes in the directions of a) purely functional programming and b) requiring (in most cases) the use of monads to represent effects in the type system.


                My educated guess: This is largely because the core syntax and feature-set is relatively well understood at this point. As such syntactic sugar is rarely necessary in order to express intent directly in efficient code. This is unlike in “dynamic” languages such as Python, Ruby, and Elixir, which tend to make liberal use of metaprogramming in order to make the syntax more directly express intent. This can often make it unclear what is actually happening to make a given piece of code run.

                1. 3

                  I find it interesting that nothing on that list is inherent to functional languages. All these sweet goodies might as well exist in an imperative language, but outside of Rust, they don’t.

                  I’m still sad Go completely missed the boat on that one.

                  1. 1

                    Yup. Maybe someday we’ll have an approach in between those of Go and Rust; that’s some of what I’m looking to Gleam for, even if it’s not primarily a systems programming language.† In the meantime, we have the sometimes-insightful, sometimes-regressive minimalism of Go; and the sometimes-insightful, sometimes-overwhelming maximalism of Rust.


                    † It is my broad understanding that the BEAM VM—on which I expect most* Gleam code will run—helps concurrent applications scale by denying granular control of threaded execution. This can be thought of as vaguely similar to the Go runtime’s decision to have a blessed implementation of cooperative multitasking, namely goroutines. In contrast, the Erlang ecosystem benefits from having a blessed provider and model (respectively) for all concurrent computation, thanks to the OTP supervision tree combined with the actor model of concurrent computation. It takes power away from the developer, but to the benefit of the system at large. Boy, is it ever exciting that we might actually have a statically-typed language built atop that excellent multitasking infrastructure!

                    1. 1

                      From my PoV Go and Rust are incomparable since Go is a GC’d language and Rust is a non-GC’d language. So on this basis Gleam, too, can never compete with Rust but is for sure a strong contender in the space Go plays in.

                      1. 1

                        If all that matters is the engine that runs your code, the sure. If a project is performance-sensitive, then its options for reasonable programming language are constrained anyway IMO. When I compare these languages, I have their developer experience in mind. At least, relative to how much performance they leave on the table.

                  2. 1

                    It honestly depends what one even means by “functional language” lots of ML and ML-like things exist which are not particularly functional including possibly: SML, OCaml, Swift, Scala, Kotlin, Haxe

                    1. 2

                      What’s not-functional about SML, OCaml, and Scala? Are you perhaps comparing them to the purely functional Haskell?

                      1. 1

                        What’s functional about them? Every language I listed is equally functional and not-functional depending how you use it. They’re all ML variants after all.

                2. 2

                  Facebook’s Reason, which is sometimes even called ReasonML for clarity

                  Credit where credit is due: Reason is a syntax extension for OCaml. It’s not a whole cloth creation by Facebook.

              2. 4

                I think it is an ML language in every way, although we moved away from the traditional ML syntax in the end.

    17. 6

      I really wish the only community options weren’t Discord and GitHub – not even an unofficial Matrix.org room or IRC channel mention (seems IRC used to be supported). I was looking into the language recently as something with types on BEAM, but I felt like there was no option to ask questions toward the community if I had them so I didn’t feel it was worthwhile to read any further.

      1. 12

        We did used to use IRC and we looked into Matrix but both lacked the anti-harassment and anti-abuse features that we need to run a community. Community is very important to Gleam so we need to ensure that we can deal with trolls and bigots should they arise.

        As a nice unexpected bonus the active community exploded in size once we switched from IRC, it seemed it was a barrier to entry for many people.

        1. 3

          Is there a downside for listing unofficial options for people to join at their own risk if they don’t feel comfortable giving their private data to Discord?

          1. 5

            It’s tricky. So far the experience with unofficial projects (not unofficial community forums, afaik there are none) is that people still come to the Gleam core team when they want help with them or have a problem to report. It ends up creating additional work for us and we have quite limited time due not having corporate backing.

            It would be good to list unofficial forums but we probably need to think carefully about the messaging and expectations around them.

            1. 2

              I can understand it from an administrative and marketing standpoint; it’s just a shame that we, society, can’t seem to have the option that protects privacy and also attracts the masses (who are not aware of the privacy threats, or see the convenience as a worthwhile tradeoff). Instead, someone such as myself feels put off and I can’t help be echo the schism it creates noted by the Drew Devault in Please don’t use Discord for FOSS projects . It’s the reason I suggest at a minimum listing unofficial bastions as a compromise.

              1. 1

                It’s just too much work I’m afraid. If we had more money we could, but right now I’m stretched thin.

        2. 1

          As someone who works on community and chat systems I would love to hear which anti-harassment and anti-abuse features are most important to a community like yours.

    18. 1

      How does the typing for messages work? Are the checks dynamic?

      1. 2

        Actors and messages are implemented in the gleam_erlang and gleam_otp libraries and they are statically checked.

    19. 10

      Gleam seems like a really cool language! I really like the design of Elixir, but I would love something statically typed. Something with first-class concurrency like Go, without the corner cases, and with functional programming creature comforts like ADTs to allow for composable error handling. The BEAM seems to me like the most mature platform for concurrent & distributed programs, and being ML-like gives you much of the rest “for free”. Perhaps I’m misunderstanding what Gleam is at present, but I think the language has a lot of promise.

      That being said, I’m not entirely sure what the roadmap is. I’ve looked through the website a couple times, and noticed the docs were somewhat incomplete and there weren’t yet a lot of facilities for creating & using generic types.

      Is assistance with evolving the language design welcome? Alternatively, is there a roadmap somewhere to guide a prospective contributor to high-priority issues or good first issues?

      1. 3

        Hello! Thanks for the support!

        Perhaps I’m misunderstanding what Gleam is at present, but I think the language has a lot of promise.

        Yes, I think you have an idea of the goals :)

        That being said, I’m not entirely sure what the roadmap is.

        The planned additions can be found in the issue tracker. We’re low on time so there’s not lots of detail in the tickets (most the discussion happens in Discord) but if anything catches your eye I’m happy to add extra info to specific tickets.

        Tickets that are good to start with have been labelled as such, and more will be added in future.

        Is assistance with evolving the language design welcome?

        Drop by the Discord and get involved! That said the language has matured somewhat and there’s not going to be any large changes in future.

        1. 3

          I’m “excited from afar” about Gleam, and it seems like maybe it’s mature enough for me to begin exploring. I haven’t used a BEAM language–besides networked services, what other use cases is Gleam a good fit for? I don’t think I’ve seen many CLI tools implemented in a BEAM language, for example. Even within the context of networked services, does just using Gleam pretty much get me all of the supervisor tree / reliability goodness that I hear about with Erlang, or do I need to opt into some framework? And if I use Gleam, will I effectively have to learn Erlang and/or Elixir? How much interop is there in the ecosystem, and how much is the user exposed to it?

          1. 5

            I haven’t used a BEAM language–besides networked services, what other use cases is Gleam a good fit for? I don’t think I’ve seen many CLI tools implemented in a BEAM language, for example.

            It’s a general purpose programming language with a virtual machine and a garbage collector, so all sorts really. I use it mostly for web services and little CLI tools that I use personally. I also have some frontend SPAs and widgets written in Gleam compiled to JavaScript.

            If I were to make CLI tools that I wanted to ship to users I would probably compile the Gleam to JavaScript and bundle it into one file. It’s a bit easier to distribute it like that then to ask people to install the Erlang virtual machine.

            Even within the context of networked services, does just using Gleam pretty much get me all of the supervisor tree / reliability goodness that I hear about with Erlang, or do I need to opt into some framework?

            The Erlang framework that offers these properties is called OTP and you can use it from Gleam like any other BEAM language. We’ve got our own version called gleam_otp which is statically typed.

            Most the time when writing web applications and such you can largely ignore it and the web server, database client, etc will all be written using the framework, giving you those properties.

            And if I use Gleam, will I effectively have to learn Erlang and/or Elixir? How much interop is there in the ecosystem, and how much is the user exposed to it?

            You don’t have to, but if you’re looking to use libraries we don’t have binding for it’ll help to learn some Erlang. Some Gleam users have never written any Erlang, others do a lot of it as they bring new APIs to Gleam. It largely depends on what you’re looking to do currently while the ecosystem is younger.

            1. 2

              This is great news. Really excited that it sounds like a lot of the networked services ecosystem leverages OTP already (rather than it being more niche). Personally, I’d prefer to have self-contained binaries for CLI tools, but that’s not much of a big deal–I can probably use Go or similar if that’s really important to me. I’ll play around with it soon. 👍

    20. 5

      Congrats on the new release lpil!