13 ms·
Gleam Is Pragmatic
- stonethrowaway 2y ago[flagged]
- flooow 2y agoWhat has C# got to do with... anything in this article?
- zelphirkalt 2y agoDoes the C# runtime or language or so have any of the abilities that a BeamVM language can make use of? Afaik Gleam can do the same things you can do with Erlang, easily making a cluster of machines and easily running code on any of the machines in the cluster, load balanced, pattern matching on binary ... Otherwise I don't understand what C# has to do with Gleam.
- neonsunset 2y agoI believe the comparison to C# is incorrect. Given what the article highlights, the direct competitor to Gleam is going to be F#. By virtue of using .NET, it offers an order of magnitude better performance, significantly cheaper per-process/task cost and equally capable overall concurrency primitives. A much bigger ecosystem with many polished libraries too.
- klibertp 2y ago> significantly cheaper per-process/task cost Can you elaborate? BEAM processes are very lightweight - what does CLR/.NET do to make them even cheaper?
- SirGiggles 2y agoI think a big difference is how C# uses stackless coroutines while anything on the BEAM uses stackful. How much cheaper is a good question however
- neonsunset 2y ago.NET's Task<T> + the state machine box for it emitted by Roslyn start at about 100B of heap-allocated memory, as of .NET 8/9. The state machines generated for those by F# don't seem to be far behind either (I tested this before replying, F#'s asynchronous computations aka async { } appear to be much less efficient however so the guidance is to avoid them in favor of task { } and .NET's regular tasks). Notably, BEAMs processes come with their own per-process GC each, which is going to add a lot of additional cost every time a new process is spawned. In a similar vein, Go's goroutines pre-allocate quite a bit of memory for their virtual stacks (60 KiB?). .NET's tasks, as sibling comment mentions, are stackless coroutines[0] so their memory usage is going to be much lower. They come with a different set of tradeoffs but overall their cost is going to be significantly cheaper because bytecode is JIT/AOT compiled to native, the GC has precise tracking of object liveness and because .NET does not perform BEAM-style preemptive userspace scheduling. Instead, .NET employs work-stealing threadpool with hill-climbing thread count scaling to achieve optimal throughput. This way, when the workers cannot advance all submitted work items in time, additional threads are injected, which are then preempted by kernel thread scheduler. This means that even if other workers are busy, the work items will not wait in the queues indefinitely. This is a pathological case and usually the thread count varies between 1-2x physical core count. This has a downside of achieving potentially worse scheduling fairness, and independent tasks allocating can and do affect each other w.r.t. GC pause impact. I believe this to be non-issue because this is more than compensated for by spending <10x CPU time vs BEAM on computing the same result, and significantly less memory (I don't have hard numbers but .NET is quite well behaved in terms of allocation traffic) too. At the end of the day, Task<T> is designed for much higher granularity of concurrency and parallelism so it would be quite unusable if it had greater cost. If you're curious, I made an un-scientific and likely incorrect but maybe interesting comparison some time ago (it's in Ukrainian but the table is readable enough I hope): https://gist.github.com/neon-sunset/8fcc31d6853ebcde3b45dc7a2af32127#tldr https://gist.github.com/neon-sunset/8fcc31d6853ebcde3b45dc7a... This calculates the CPU time and max MEM RSS usage required to spawn 1M tasks/coroutines/processes/futures that sleep for 5s and await their completion. [0]: This might stop being true in a pure sense of the word in .NET 10 because the task handling is going to be completely changed by replacing state machines generated by a language that targets .NET with specially annotated methods, for which the runtime itself is going to implicitly emit state machines instead, allowing to pay the cost only at "true" suspend points. Reference: https://github.com/dotnet/runtimelab/blob/feature/async2-experiment/docs/design/features/runtime-handled-tasks.md#runtime-handled-tasks-experiment https://github.com/dotnet/runtimelab/blob/feature/async2-exp...
- conradludgate 2y agoI'd rather use an external job scheduler and message queue, eg in kubernetes, rather than build it into the language runtime. With a message queue and horizontal pod autoscaling you can very quickly build an easy distributed workload with (at least to me) little effort in any language
- throwawaymaths 2y agoOr you could do with almost zero effort in a BEAM language. And sometimes setting up a kuberbetes cluster is not easy. Suppose you want to have one part that is hosted on a cloud service and another that is on-prem. It's certainly possible with kuberbetes, but you'll be fighting the kuberbetes paradigm. Plus you're checking in to this kuberbetes middleman for part A to understand the availability of part B, for example. Kuberbetes is highly devex optimized for stateless services that are kinda cattle. Not all use cases fit so neatly into that rubric.
- conradludgate 2y agoI would not say it's zero effort in BEAM. I wasn't able to figure out how to get it to work at all (I spent a weekend trying to build a distributed auth system, failed). But I would be able to spin up kubernetes, rabbitmq and a non BEAM language in not very long. > Suppose you want to have one part that is hosted on a cloud service and another that is on-prem. Is this hard with kubernetes? Seems pretty simple to add node taints and set up the appropriate VPN to me. BEAM is not magic. It will be doing a lot of the same stuff that any other scheduler or message queue will do, so I would personally rather choose the more composable solution rather than have to manage BEAM and be forced to use one family of languages only. I'm thinking if your company is already running kubernetes, adding BEAM on top is probably more effort than adding a message queue (given my limited understanding of BEAM)
- throwawaymaths 2y agoNever said it was zero effort. It's almost zero effort. But you have to have deep knowledge of BEAM primitives if you intend on doing something crazy (still almost zero effort). If you don't know what you're doing or don't have experience in the BEAM you will probably get it wrong. Sometimes libraries haven't gotten it right so composability might not be there and you might have to write a sidecar genserver and supervisor (~40 LOC) Good news is in elixir, though it is not easy to write (still low effort) most of it is pretty straightforward to read so a junior could probably successfully work through and understand a CR. The other very nice thing about doing it all in the BEAM is that you can make distribution and availability guarantees part of your testing suite very easy, because its even part of you localdev flow. That also means your devs will write that test, instead of of assuming they understand how that kuberbetes operator works (they don't).
- beanjuiceII 2y agotried gleam but the fact i have to manually serialize/deserialize things, pretty annoying, that doesn't seem very pragmatic
- steve_adams_86 2y agoIsn’t manual ser/de pretty common? I like it personally. Being explicit at program boundaries usually means far fewer bugs inside the program. In JS I can pile whatever JSON I want into an object, but eventually I need to throw Zod or something at it to tame the crazy. Maybe a generic “pile this data into this value and pretend it’s safe” tool might be nice for prototyping.
- beanjuiceII 2y agoi dont think manual ser/de is common at all, and languages like dart where it was used is a massive pain point for people so much that they are adding macros to the language and the first macro they add is for serialization. whats not explicit about saying hey i have a struct this is the data i expect, serialize/deseralize in this shape, validation is a another but separate concern. in javascript you are not doing anything manually so i'm not sure why thats an example?
- __MatrixMan__ 2y agoI'm a bit confused. How can you control how your data is serialized if not manually? Are there languages that use some kind of magically-figures-it-out layer that negotiates the appropriate serialization on the fly?
- googledocsftw 2y agoC# (or more precisely .NET libraries) does it using reflection. Attributes let you adjust the behaviour.
- neonsunset 2y ago
- steve_adams_86 2y agoWow, this is a great overview. I’ve been playing with Gleam a bit and this was really helpful. I’ll definitely refer to this later. I’d like to dig into the OTP library (I’m curious if anyone has worked with it much?) and create a state chart library with it, but I’m still firmly in the “I don’t totally get it” camp with a few parts of Gleam. I don’t deny that it’s pragmatic. Maybe it’s more so that I’m not up to speed on functional patterns in general. I was for years, but took a hiatus to write code for a game engine and supporting infrastructure. It was so Wild West, but I kind of liked it in the end. Lots of impure, imperative code, haha.
- okkdev 2y agoMost people use the OTP lib! There's this super useful intro repo: https://github.com/bcpeinhardt/learn_otp_with_gleam https://github.com/bcpeinhardt/learn_otp_with_gleam
- steve_adams_86 2y agoIncredible, thank you so much! This is exactly what I need.
- conradludgate 2y agoI've tried to get my head around functional programming and also OTP but I also just never got my head around it. Functional programming seems too limiting and OTP seems more complicated than I would have hoped for a supposedly distributed concurrency system. I'm sure it's just a skill issue on my part. Right now I'm way too rust-brained. I've heard lots of things about gleam being good for productivity but I don't feel unproductive writing web apps in Rust but I felt every unproductive trying to write a non-trivial web app in gleam
- giacomocava 2y agoYeah FP can for sure take some getting used to before it clicks! I think a great resource for that is Gleam's Exercism track (https://exercism.org/tracks/gleam https://exercism.org/tracks/gleam), not only will it teach you the language but by starting with small-ish exercises it can definitely help grokking FP concepts And if you feel like you're stuck and need help Gleam's Discord is a great place to ask questions :)
- atemerev 2y agoThe greatest power of BEAM-based languages is the fully preemptive actor model. Nobody else supports it. This is a superpower, the solution of most problems with concurrent programming. In Erland and Elixir, actors and actor-based concurrency hold the central place in the corresponding ecosystems, well supported by extensive documentation. In Gleam, actors and OTP are an afterthought. They are there somewhere, but underdocumented and abandoned.
- vereis 2y agoGleam runs on the BEAM
- atemerev 2y agoIt does. However, its actor implementation is not built upon Erlang/OTP, and currently is “experimental” and not even mentioned on the main site.
- lolinder 2y ago> its actor implementation is not built upon Erlang/OTP This seems to be the opposite of pragmatic. The most pragmatic approach to actors when you're building a BEAM language would be to write bindings for OTP and be done with it. This sounds kind of like building a JVM language with no intention of providing interop with the JVM ecosystem—yeah, the VM is good, but the ecosystem is what we're actually there for. If you're building a BEAM language, why would you attempt to reimplement OTP?
- arcanemachiner 2y agoI believe their implementation was written to support static typing (since Gleam is a statically-typed language).
- okkdev 2y agoBecause of type safety. The OTP lib is already great, but there are still some things missing, most requested being named processes. But there is work being done to figure out how to best make it work for gleam.
- behnamoh 2y agoIt's not pragmatic if you have to import these basic libs: ``` import gleam/dict.{type Dict} import gleam/int import gleam/io import gleam/result import gleam/string ```
- eterm 2y agoWhy not? What's wrong with a standard library the bits of which you want you choose to import?
- reikonomusha 2y agoIt's not that it's wrong—at least I don't think so. It's that it's an example of a choice that is not pragmatic. I suppose we should agree on what "pragmatic" even means, since it has become something of a cliché term in software engineering. To me, it roughly means "reflective of common and realistic use as opposed to possible or theoretical considerations". So is having to import basic functionality a pragmatic design? I would argue no. Having to import basic functionality for integers, strings, and IO is not pragmatic in the sense that most realistic programs will use these things. As such, the vast majority of ordinary programs are burdened by extra steps that don't appear to net some other benefit. Importing these very basic functionalities appeals to a more abstract or theoretical need for fine-grained control or minimalism. Maybe we don't want to use integers or strings in a certain code module. Maybe we want to compile Gleam to a microcontroller where the code needs to be spartan and freestanding. These aren't pragmatic concerns in the context of the types of problems Gleam is designed to address. To give a point of comparison, the Haskell prelude might be considered a pragmatic design choice, as can be seen from the article. It is a bundle of common or useful functionality that one expects to use in a majority of ordinary Haskell programs. One doesn't need to "import" the prelude; it's just there. I don't personally find Gleam's design choice a bad one, and while GP was a bit flippant, I do agree that it is not an example of a pragmatic design choice.
- widdershins 2y agoPragmatism is more than just giving people the quickest way to complete their task. There are other axes to consider, such as the simplicity of the compiler and the uniformity of the language experience. These contribute to the maintainability of the language itself and your own code also. When the rule is "if you need a module, you must import it", and that applies equally to standard library modules, hex packages or your own internal modules, there are fewer mental overheads. The procedure is always the same. Incidentally, this also means that the Gleam language server can automatically add or remove import statements, which it now does [0]. Personally, I also find it pleasing that I can look at the top of a file and say "oh, this module appears to be doing some stuff with floating point math and strings". It often gives me an overview of what the module might be doing before I begin reading the detail. [0] https://gleam.run/news/convenient-code-actions/#missing-import-suggestions https://gleam.run/news/convenient-code-actions/#missing-impo...
- rossng 2y agoThe `use` syntax is interesting - don't recall seeing anything similar before. But I'm struggling to understand how exactly it is executed and a glance at the Gleam docs didn't help. Is the `use` statement blocking (in which case it doesn't seem that useful)? Or does it return immediately and then await at the point of use of the value it binds?
- jyjasdfsssd 2y agoIt is syntax sugar for CPS [1]. [1]: https://en.wikipedia.org/wiki/Continuation-passing_style https://en.wikipedia.org/wiki/Continuation-passing_style EDIT: I believe prior art is Koka's with statement: https://koka-lang.github.io/koka/doc/book.html#sec-with https://koka-lang.github.io/koka/doc/book.html#sec-with
- rossng 2y agoHmm, it definitely looks more interesting in combination with effect handlers. Still not sure I find it super compelling in Gleam vs just not using callbacks.
- jitl 2y agoIt’s a generalization of async/await syntax in languages like JavaScript or Swift. I like that it provides a generalized syntax that could be used for coroutines, generators, or async/await without adding any of those specifically to the language syntactically. One level of callback nesting in a function is totally fine, two is a bit confusing, but if you have many async things going on do you really want 10, 15, 20 levels of nesting? What to do about loops? I certainly greatly prefer async programming with async/await languages that keep the appearance of linear function execution to stacking my callbacks and having a ton of nesting everywhere
- vips7L 2y agoSounds like the new “capabilities” stuff in Scala.
- 2y ago
- patte 2y agoThis is a very concise overview! I have made a small example chat app [1] to explore two interesting aspects of gleam: BEAM OTP and compilation to javascript (typescript actually). If anyone is interested... [1]: https://github.com/patte/gleam-playground https://github.com/patte/gleam-playground
- fire_lake 2y agoGleam looks nice but if an F# comparisons was added, I think that would come out ahead based on the authors priorities.
- cipehr 2y agoThe author links to a blog post talking about railway oriented programming in f#.. it might be fair to assume they are aware of f#
- munchler 2y agoAll the more reason to include it in the comparison.
- devmunchies 2y agoOne thing I dislike with erlang based languages (both gleam and elixir) is that they use “<>” for string concatenation. In F#, “<>” is the equivalent of “!=“. Postgres also uses <> for inequality so my queries and f# code have that consistency.
- sodapopcan 2y agoHa, ok so I gotta give one of these "that's a really strange thing to get hung up on" responses. Erlang and Elixir don't overload the `+` operator. In fact, they don't overload ANY operators. If you can forgive the syntactic choice of the operator itself (which I think it pretty fair considering Erlang predates Postgres by a decade and F# by two decades), this allows them to be dynamic while maintaining a pretty high level of runtime type safety. For example, one of the "subtle bugs" people refer to when criticizing dynamic languages (even strongly typed dynamic languages) is the following would work when both args are given strings or numbers: function add(a, b) { a + b } Erlang/Elixir eliminate this particular subtle bug (and it goes beyond strings and numbers) since: def add(a, b), do: a + b will only work on numbers and raise if given strings.
- mixmastamyk 2y ago
- jazzypants 2y ago> I won’t fall into the trap of trying to define Monads in this post. Instead, let’s talk about monadic-style APIs – that is, APIs that allow you to do a bunch of things one after another, with the ability to use the result of a previous computation in the next computation, and also allows some logic to happen between steps. Am I crazy, or did he just give a really good definition of monads in programming? I think that it benefits by not letting itself get bogged down in Category Theory nomenclature which doesn't actually matter when programming.
- bos 2y agoNo, this isn’t a good description of monads. It merely describes a case that shows up sometimes.
- jazzypants 2y agoDang, when I made this silly, little comment about FP, I didn't expect to get corrected by a legend in the field! Thanks for taking the time to respond.
- marcosdumay 2y agoHe described a problem people use monads to solve, not monads themselves. Haskell people do talk about monadic vs. applicative combinators that are different by whether you can use the results of a previous step on the next ones. But that doesn't have a direct relation with the actual definition of those. But yes, if you are teaching a programming language that uses monads to someone, you will probably want to explain the problem they solve, not the actual structures. As most things in math, the structures become obvious once you understand the problem.
- memco 2y agoYin the OOP world I’ve seen this pattern called chaining : usually either method or object chaining.
- pxc 2y agoIt's a style I really enjoy, and it's definitely not exclusive to one language or paradigm, exactly. I see it as more of less of a kind with pipelines in Unix shells, too. In Scala, a language with OOP heritage and support, plus lots of functional programming features, some of the most common methods you use in such chains are monads.
- skybrian 2y agoGleam's 1.0 release was in May and it's still adding major features. JavaScript support looks interesting. Browsing the package repo, I don't see how to tell which packages are supported on Erlang's VM, when compiling to JavaScript, or both. JavaScript-specific documentation seems pretty thin so far?
- oDot 2y agoYou're right about the lack of FFI-specific docs, but Gleam is such a simple language that it's very workable. I wrote Vleam[0], which allows writing Gleam inside Vue SFCs, and the experience was pretty good even without the docs. You do have to sometime read the source of other Gleam packages to understand how things work, but again -- Gleam is so simple it's not too bad of an experience. [0]: https://github.com/vleam/vleam https://github.com/vleam/vleam
- lpil 2y agoMost of the work for this has been done, the main missing piece is surfacing it in the UI, which someone will hopefully pick up soon.
- vivzkestrel 2y agonewbie here, how does gleam compare to golang, rust and python?
- widdershins 2y agoIt has some syntax similarities to Rust, but it has GC so there's no borrow checker (or any of the associated syntax). It is also fully immutable, unlike Rust. It leans heavily on sum types, just like Rust. Also expression-based syntax and some other things resemble Rust. However, it lacks Traits. Overall it looks Rust-ish but it's much simpler and has a functional focus. With Go it shares a lazer focus on simplicity and preemptive channel-based concurrency. But of course for all the above reasons listed above it looks very different from Go in most other ways. In many way its language choices are the opposite of Python (static types, immutability, massive concurrency is the norm).
- d--b 2y agoDoesn’t it compare mostly to F#, rather than Haskell or OCaml? The examples in the post really look like F# to me
- oldpersonintx 2y ago[dead]
- zombot 2y agoThe syntax doesn't look like it supports partial application? Big no-no. Also, no compilation to native code. Another big no-no.
- Hasnep 2y agoRoc is a similar functional language that doesn't automatically curry functions and doesn't have partial application, the Roc FAQ has a few reasons: https://www.roc-lang.org/faq.html#curried-functions https://www.roc-lang.org/faq.html#curried-functions
- klibertp 2y agoEDIT: quick note, this is a tangent; Gleam does support partial application with `_` and it works with pipelines as well. > This is not how it works in curried languages, however. In curried languages with a |> operator, the first expression still returns "Hello, World!" but the second one returns "World!Hello, " instead. This can be an unpleasant surprise for beginners, but even experienced users commonly find that this behavior is less useful than having both of these expressions evaluate to the same thing. The upside (on the curried side) is that you can define `|>` as a normal function (even without lazy semantics, as in OCaml.) How much of an "upside" this is will vary, but note that this generalizes to many other operators that can be added. The rest is a matter of API design, i.e., the order of arguments and the use of named arguments (and/or other syntax sugar.) For example, in the case of the post's example: "Hello, " |> Str.concat "World!" You can get the "beginner friendly" semantics with just a little change to the `Str.concat` (assuming named args support, using OCaml syntax): "Hello, " |> Str.concat ~rest:["World!"] In non-curried languages, this has to be a macro (or it needs to be built-in). If you already have macros in your language - that's good: you can easily make `|>` do things like `x |> func(arg1, _, arg2)` and more. However, if you make this a special case in the language, it will be hard to extend and impossible to generalize. So personally, I'd grade the options in order of power and convenience: - infix macros - infix functions in curried languages - special, hardcoded |> operator - beating your language black and blue until you get a work-alike (like with implicits in Scala) There's also a special category of languages where pipelines are primitives (shells, jq), but that is outside the scope of this comment, since they are more than syntactic sugar for function application :)
- amelius 2y ago> Running on the battle-tested Erlang virtual machine that powers planet-scale systems such as WhatsApp and Ericsson, Gleam is ready for workloads of any size. Does a Gleam programmer in practice need to deal with Erlang? Do Erlang error messages leak through?
- giacomocava 2y agoPure Gleam will get you really far without having to touch any Erlang, I've done Gleam for almost a year now and there were very little cases where I needed to write Erlang code myself, usually there's already a library that deals with it for most common needs :)
- amelius 2y agoCould you say something about the cases where you did need to write Erlang code? What kind of cases? Were you already proficient in Erlang and its ecosystem?
- giacomocava 2y ago> Could you say something about the cases where you did need to write Erlang code? Sure! For one of my most used packages (https://github.com/giacomocavalieri/birdie https://github.com/giacomocavalieri/birdie) I needed to get the terminal width to display a nice output, that has to be implemented using FFI based on the specific runtime (erlang or js) so I had to write it in Erlang, that was just a couple of lines of code. But now there's a Gleam package to do it, so if I were to rewrite it today I wouldn't even need to write Erlang for that and could just use that! > What kind of cases? Usually it is when you need some functionality that has to rely on specific things from the runtime (like IO operations, actors on the BEAM, async on the JS target, ...) and there's no package to do it already. Most of the common things (like file system operations and such) are already covered > Were you already proficient in Erlang and its ecosystem? Not at all :) I knew very little about Erlang (basically nothing behind the syntax), Gleam was my introduction to the BEAM ecosystem and it has worked out great so far! Hope this is helpful, happy to share my experience here
- Degorath 2y agoI understand why the `use` syntax is preferable for its generalizability to many different "callback style" things, but the whole construct of `use foo <- result.try(bar())` is so much worse than defining let* in ocaml and being able to write `let* foo = bar() in`...
- lpil 2y agoWhat would you say makes it much worse?
- ogogmad 2y agoIs there a way to implement matrix arithmetic with nice syntax (for instance, "A + B" to add two matrices A and B) in Gleam? The lack of ad-hoc polymorphism might paradoxically be a blessing.