[HN Gopher] Gleam Is Pragmatic
___________________________________________________________________
Gleam Is Pragmatic
Author : crowdhailer
Score : 121 points
Date : 2024-10-06 18:15 UTC (4 hours ago)
(HTM) web link (blog.drewolson.org)
(TXT) w3m dump (blog.drewolson.org)
| stonethrowaway wrote:
| I don't think we've had anyone drop the hammer on the fact that
| C# is right around the corner (in part thanks to adopting a bit
| of this a bit of that from F#) with some of this outstanding
| periphery. Certainly I haven't come across an article that's bold
| enough to attempt it (bonus points for meme'ing it with "C# is
| all you need"). I'm assuming it's a mindshare thing and nerds
| would rather flex on each other by conjuring up unconventional
| languages. And perhaps I'm stating the obvious in this regard.
| flooow wrote:
| What has C# got to do with... anything in this article?
| zelphirkalt wrote:
| Does 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 wrote:
| I 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.
| conradludgate wrote:
| I'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 wrote:
| Or 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 wrote:
| I 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 wrote:
| It's not 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.
|
| Good news is in elixir, most of it is pretty
| straightforward to read so a junior could probably
| successfully work through and understand a CR.
| beanjuiceII wrote:
| tried gleam but the fact i have to manually serialize/deserialize
| things, pretty annoying, that doesn't seem very pragmatic
| steve_adams_86 wrote:
| Isn'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 wrote:
| i 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__ wrote:
| I'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?
| yawaramin wrote:
| Many languages have some kind of macro or codegen system
| that allows serializing or deserializing based on type
| definitions. Eg (pseudocode):
| @deriving(json) class Person: id: int
| name: str
|
| Would give you something like: def
| parse(s: str): Person: ... def print(p: Person):
| str: ...
| __MatrixMan__ wrote:
| I see, thanks. I thought maybe we were talking about the
| choice of json vs something else being automatic and
| chosen at runtime.
| googledocsftw wrote:
| C# (or more precisely .NET libraries) does it using
| reflection. Attributes let you adjust the behaviour.
| neonsunset wrote:
| Or with build-time source generation (because this
| specific pattern of reflection is AOT-unfriendly). It's
| not as convenient if you are using default serializer
| options, but if you don't - it ties together
| JsonTypeInfo<T> and JsonSerializerOptions, so it ends up
| being a slightly terser way to write it. I do prefer the
| Rust-style serde annotations however.
| record User(string Name, DateOnly DoB);
| [JsonSerializable(typeof(User))] partial class
| SerializerContext: JsonSerializerContext; ...
| var user = new User("John", new(1984, 1, 1)); var
| response = await http.PostAsJsonAsync( url, user,
| SerializerContext.Default.User);
| steve_adams_86 wrote:
| Sorry I wasn't clear; I meant to use JavaScript as an
| example where it isn't manual.
|
| Despite it being easy to use, I find I inevitably wind up
| requiring a lot of ceremony and effort to ensure it's safe.
| I'm not a huge fan of automatic serialization in that it
| appears to work fine when sometimes it shouldn't/won't. I
| agree that it's a lot of effort though. I guess the
| question is if you want the effort up front or later on. I
| prefer up front, I guess.
| lawn wrote:
| This is the biggest reason I cooled a bit on Gleam and whenever
| I want to do some backend stuff I'd much rather use Rust (using
| serde to convert to structs) or Elixir (put it in dynamic
| maps).
|
| I wish Gleam would implement some kind of macro system, making
| a serde-like package possible.
| __jonas wrote:
| I agree that the stdlib decoder functions aren't the most
| ergonomic, but I think people are aware it's a pain point and
| there is development in that are, these two packages for
| example:
|
| https://hexdocs.pm/decode
|
| https://hexdocs.pm/toy/
| steve_adams_86 wrote:
| Wow, 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 wrote:
| Most people use the OTP lib! There's this super useful intro
| repo: https://github.com/bcpeinhardt/learn_otp_with_gleam
| steve_adams_86 wrote:
| Incredible, thank you so much! This is exactly what I need.
| conradludgate wrote:
| I'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
| atemerev wrote:
| The 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 wrote:
| Gleam runs on the BEAM
| atemerev wrote:
| It does. However, its actor implementation is not built upon
| Erlang/OTP, and currently is "experimental" and not even
| mentioned on the main site.
| lolinder wrote:
| > 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 wrote:
| I believe their implementation was written to support
| static typing (since Gleam is a statically-typed
| language).
| okkdev wrote:
| Because 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.
| lolinder wrote:
| The question of type safety has come up so often here
| that I guess it's worth replying:
|
| That's exactly what I mean by this not seeming pragmatic.
| Pragmatic would be making do with partial type safety in
| order to be fully compatible with OTP. That's the much-
| maligned TypeScript approach, and it worked for
| TypeScript because it was pragmatic.
|
| Now, maybe Gleam feels the need to take this approach
| because Elixir is already planning on filling the
| pragmatic gradually-typed BEAM language niche. That's
| fine if so!
| H12 wrote:
| IIRC the re-implementation was necessary for type-safety.
| pmontra wrote:
| I agree with the part about reusing OTP but some of the
| server syntax of Erlang and Elixir is not good IMHO. I
| never liked using those handle_* functions. Give them
| proper names and you cover nearly all the normal usage,
| which is mutating the internal state of a process (an
| object in other families of languages.) That would be the
| pragmatic choice, to lure Java, C++ programmers.
| throwawaymaths wrote:
| Elixir gives you Agent, which is what you want, but for
| reasons, Agent is a bad choice.
|
| What you're not seeing with the handle_* functions is all
| the extra stuff in there that deals with, for example,
| "what if the thing you want to access is unavailable?".
| That's not really something that for example go is able
| to handle so easily.
| steve_adams_86 wrote:
| This is exactly what I want from Gleam. It does seem to be
| under documented and abandoned. Is there any understanding of
| why? Like you say, this seems like a super power. I see so much
| potential. A language that's ergonomic, pragmatic as the author
| says, great performance, low-ish barrier to entry, etc. It
| seems like it could be an awesome tool for building highly
| reliable software that's not so difficult to maintain.
| cassepipe wrote:
| It is a very young language that may explain the why
| jatins wrote:
| this is Gleam OTP package https://github.com/gleam-lang/otp
|
| I agree it's underdocumented but doesn't seem abandoned (has
| commits in last week)
| dullcrisp wrote:
| I understand things best by comparing across different
| languages so don't take this the wrong way but I wonder if you
| can help me understand: If say I start a goroutine in Go and
| give it a channel to use as a mailbox, concurrency in Go is
| cooperative but it'll automatically use OS threads and yield
| whenever it reads from the channel. Does Erlang/OTP do
| something different? If so what does it do and what are the
| advantages? Or is it more that the library and ecosystem are
| built around this model?
| throwawaymaths wrote:
| I believe go yields after every function exit. Erlang does
| the same, but there are no loops (you _must_ use tailcall) so
| you can 't lock up the CPU with a while(true).
| samatman wrote:
| That used to be true, but no longer, goroutines are truly
| preemptive, in 10ms time slices.
| Jtsummers wrote:
| Erlang gives a reductions budget to processes. After a
| certain number of reductions, or if a process hits a yield
| point (like waiting to receive a message), the process will
| yield allowing another process to run.
|
| Go uses preemption now (since 1.14), but it didn't always.
| It used to be that you could use a busy loop and that
| goroutine would never yield. Yield points include things
| like function entries, syscalls, and a few other points.
| behnamoh wrote:
| It'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 wrote:
| Why not?
|
| What's wrong with a standard library the bits of which you want
| you choose to import?
| reikonomusha wrote:
| It'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 cliche 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.
| orthoxerox wrote:
| I can understand having to import the "dirty" parts of the
| stdlib, like I/O, or the "heavy" parts, like Unicode or
| timezones. But why force someone to import every single type?
| Most functional languages have a prelude that covers the
| types every non-trivial program uses: booleans, numbers,
| strings, collections.
| Jtsummers wrote:
| > But why force someone to import every single type?
|
| That's not importing the types, it's importing a suite of
| functions related to the types.
|
| https://hexdocs.pm/gleam_stdlib/gleam/int.html - gleam/int
| for example. The int type is already in the language and
| usable, this import brings in some specific functions that
| are related to operations on int.
| rossng wrote:
| The `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 wrote:
| It is syntax sugar for CPS [1].
|
| [1]: 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
| rossng wrote:
| Hmm, 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 wrote:
| It'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 wrote:
| Sounds like the new "capabilities" stuff in Scala.
| yawaramin wrote:
| You can do something similar in OCaml (as an operator defined
| at the library level, not a specialized new syntax):
| https://github.com/yawaramin/letops/blob/6954adb65f115659740...
| taberiand wrote:
| The equivalent in F# is let! (F# computation expressions are
| quite powerful); in rust the ? operator. Other languages have
| similar features.
|
| It's syntactic sugar, but the readability is worth it
| eddd-ddde wrote:
| I think it's similar to koka's 'with'.
|
| https://koka-lang.github.io/koka/doc/book.html#sec-with
| patte wrote:
| This 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
| fire_lake wrote:
| Gleam looks nice but if an F# comparisons was added, I think that
| would come out ahead based on the authors priorities.
| cipehr wrote:
| The author links to a blog post talking about railway oriented
| programming in f#.. it might be fair to assume they are aware
| of f#
| devmunchies wrote:
| One 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.
| jazzypants wrote:
| > 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.
___________________________________________________________________
(page generated 2024-10-06 23:00 UTC)