rapind 16 hours ago

I just started a small project using gleam / lustre, and so far I’m loving it.

Worth trying if you’re on the fence, especially if you’re into static types, no nulls, functional, ML type languages. Plus beam of course.

  • sureglymop 9 hours ago

    Same and I like it too so far!

    Some thoughts so far: Installing gleam installed about 50 packages on my system (possibly all erlang/elixir related). But what if I just wanted to transpile to js? Perhaps this is a packaging issue on my system though.

    What I really wish is that Lua would be considered as another transpilation target. I think gleam could shine for DSLs in other programs but embedding Lua is 3x easier than embedding a js runtime imo.

    In general, I find the nicest thing so far the community. And I want to say that the quality of libraries and resources in the gleam community seems exceptionally high. It reminds me of the rust community insofar as that for most hard problems someone smarter than me has already worked on it and published a good solution (e.g. lustre, squirrel). Then I would say you can also expect a lot of creativity and experimentation, the things that may fly under the radar in larger language ecosystems stand out here due to the growing and welcoming community.

    • hayleighdotdev 7 hours ago

      > Perhaps this is a packaging issue on my system though.

      Gleam is only officially distributed via the releases on GitHub [1] so if you pick up Gleam from a package manager that's always maintained by someone else. I think most of those community distributions do include Erlang and bits which is probably what pulled in all those extra packages!

      [1]: https://github.com/gleam-lang/gleam/releases/tag/v1.13.0

  • truculent 7 hours ago

    I like all of the above but have no understanding of the BEAM or OTP. Can you recommend a good place to start learning about that?

  • akudha 12 hours ago

    For someone who hasn’t worked with either, is it better to learn gleam/lustre better or elixir/phoenix?

    • the-grump 11 hours ago

      My path is a little different. I have used Haskell, and I'm looking to get into OTP.

      My original plan was either Elixir or vanilla Erlang depending on which one suits my sensibilities better. Reading about Gleam recently has me super, super excited. That's definitely going to be my path now.

      I don't know if Gleam is the best entry into the world of rich types that you find in a language like Haskell--I'm yet to actually build something with it.

      What I can tell you is that Haskell is a complete joy to use and it honestly ruins most other programming for me. So as a direction, I cannot recommend it enough, and I'm hoping, for my sake and yours, that Gleam offers a similarly stimulating sandbox.

      Just a warning that it will take time to get used to "higher-kinded" types. It's an exercise in head scratching and frustration at first. The reward part arrives when you start thinking in types yourself and you know which ones to reach for and when you find the libraries you want by entering a signature on Hoogle.

      • raphinou 11 hours ago

        Interesting!

        I have a F# background, and thought to have read that some constructs I learned to appreciate are not available in Gleam (the one I can think of right now is currying, but I thought there were others).

        Also, Gleam otp didn't seem to be a priority.

        What's your experience regarding these 2 points?

        • innocentoldguy 10 hours ago

          The issue isn't that OTP isn't a priority for Gleam, but rather that it doesn't work with the static typing Gleam is implementing. This is why they've had to reimplement their own OTP functionality in gleam_otp. Even then, gleam_otp has some limitations, like being unable to support all of OTP's messages, named processes, etc. gleam_otp is also considered experimental at this point.

          • 59nadir 8 hours ago

            Having Erlang-style OTP support (for the most part) is very doable, I've written my own OTP layer instead of the pretty shoddy stuff Gleam ships with. It's not really that challenging of a problem and you can get stuff like typed processes (`Pid(message_type)`, i.e. we can only send `message_type` messages to this process), etc. out of it very easily.

            This idea that static typing is such a massive issue for OTP style servers and messaging is a very persistent myth, to be honest; I've created thin layers on top of OTP for both `purerl` (PureScript compiled to Erlang) and Gleam that end up with both type-safe interfaces (we can only send the right messages to the processes) and are type-safe internally (we can only write the process in a type-safe way based on its state and message types).

            • innocentoldguy 8 hours ago

              I wholeheartedly agree with you that gleam_otp is janky. Still, actor message passing is only part of the picture. Here are some issues that make static typing difficult in OTP:

              • OTP processes communicate via the actor model by sending messages of any type. Each actor is responsible for pattern-matching the incoming message and handling it (or not) based on its type. To implement static typing, you need to know at compile time what type of message an actor can receive, what type it will send back, and how to verify this at compile time.

              • OTP's GenServer behaviour uses callbacks that can return various types, depending on runtime conditions. Static typing would require that you predefine all return types for all callbacks, handle type-safe state management, and provide compile-time guarantees when handling these myriad types.

              • OTP supervisors manage child processes dynamically, which could be of any type. To implement static typing, you would need to know and define the types of all supervised processes, know how they are going to interact with each other, and implement type-safe restart strategies for each type.

              These and other design roadblocks may be why Gleam chose to implement primitives, like statically typed actors, instead of GenServer, GenStage, GenEvent, and other specialized OTP behaviours, full supervisor functionality, DynamicSupervisor, and OTP's Registry, Agent, Task, etc.

              OTP and BEAM are Erlang and Elixir's killer features, and have been battle-tested in some of the most demanding environments for decades. I can't see the logic in ditching them or cobbling together a lesser, unproven version of them to gain something as mundane as static typing.

              EDIT: I completely missed the word "actor" as the second word in my second sentence, so I added it.

              • 59nadir 5 hours ago

                I suppose I was unclear. It is OTP-style `gen_server` processes that I'm talking about.

                > OTP processes communicate via the actor model by sending messages of any type. Each actor is responsible for pattern-matching the incoming message and handling it (or not) based on its type. To implement static typing, you need to know at compile time what type of message an actor can receive, what type it will send back, and how to verify this at compile time.

                This is trivial, your `start` function can simply take a function that says which type of message you can receive. Better yet, you split it up in `handle_cast` (which has a well known set of valid return values, you type that as `incomingCastType -> gen_server.CastReturn`) and deal with the rest with interface functions just as you would in normal Erlang usage (i.e. `get_user_preferences(user_preference_process_pid) -> UserPreferences` at the top level of the server).

                Here is an example of a process I threw together having never used Gleam before. The underlying `gen_server` library is my own as well, as well as the FFI code (Erlang code) that backs it. My point with posting this is mostly that all of the parts of the server, i.e. what you define what you define a server, are type safe in the type of way that people claim is somehow hard:

                    import gleam/option
                    import otp/gen_server
                    import otp/types.{type Pid}
                    
                    pub type State {
                      State(count: Int, initial_count: Int)
                    }
                    
                    pub type Message {
                      Increment(Int)
                      Decrement(Int)
                      Reset
                    }
                    
                    pub fn start(initial_count: Int) -> Result(Pid(Message), Nil) {
                      let spec =
                        gen_server.GenServerStartSpec(
                          handle_cast:,
                          init:,
                          name: option.Some(gen_server.GlobalName("counter")),
                        )
                      gen_server.start(spec, initial_count)
                    }
                    
                    fn name() -> gen_server.ProcessReference(Message, String) {
                      gen_server.ByGlobal("counter")
                    }
                    
                    pub fn count() -> Result(Int, Nil) {
                      gen_server.call(name(), fn(state: State) -> gen_server.CallResult(State, Int) {
                        gen_server.CallOk(new_state: state, reply: state.count)
                      })
                    }
                    
                    pub fn increment(count: Int) -> Nil {
                      gen_server.cast(name(), Increment(count))
                    }
                    
                    pub fn decrement(count: Int) -> Nil {
                      gen_server.cast(name(), Decrement(count))
                    }
                    
                    pub fn reset() -> Nil {
                      gen_server.cast(name(), Reset)
                    }
                    
                    pub fn init(initial_count: Int) -> State {
                      State(count: initial_count, initial_count:)
                    }
                    
                    pub fn handle_cast(
                      message: Message,
                      state: State,
                    ) -> gen_server.CastResult(State) {
                      case message {
                        Increment(count) ->
                          gen_server.CastOk(State(..state, count: state.count + count))
                    
                        Decrement(count) ->
                          gen_server.CastOk(State(..state, count: state.count - count))
                    
                        Reset -> gen_server.CastOk(State(..state, count: state.initial_count))
                      }
                    }
                
                
                It's not nearly as big of an issue as people make it out to be; most of the expected behaviors are exactly that: `behaviour`s, and they're not nearly as dynamic as people make them seem. Gleam itself maps custom types very cleanly to tagged tuples (`ThingHere("hello")` maps to `{thing_here, <<"hello">>}`, and so on) so there is no real big issue with mapping a lot of the known and useful return types and so on.
                • pmontra 2 hours ago

                  I read the code but I'm not sure I understood all of it (I'm familiar with Elixir, not with Gleam).

                  For normal matters I do believe that your approach works but (start returns the pid of the server, right?) what is it going to happen if something, probably a module written in Elixir or Erlang that wants to prove a point, sends a message of an unsupported type to that pid? I don't think the compiler can prevent that. It's going to crash at runtime or have to handle the unmatched type and return a not implemented sort of error.

                  It's similar to static typing a JSON API, then receiving an odd message from the server or from the client, because the remote party cannot be controlled.

                  • 59nadir 2 hours ago

                    > [...] start returns the pid of the server, right?

                    Yes, `start` is the part you would stick in a supervision tree, essentially. We start the server so that it can be reached later with the interface functions.

                    > [...] probably a module written in Elixir or Erlang that wants to prove a point, sends a message of an unsupported type to that pid? I don't think the compiler can prevent that. It's going to crash at runtime or have to handle the unmatched type and return a not implemented sort of error.

                    Yes, this is already the default behavior of a `gen_server` and is fine, IMO. As a general guideline I would advise against trying to fix errors caused by type-unsafe languages; there is no productive (i.e. long-term fruitful) way to fix a fundamentally unsafe interface (Erlang/Elixir code), the best recourse you have is to write as much code you can in the safe one instead.

                    Erlang, in Gleam code, is essentially a layer where you put the code that does the fundamentals and then you use the foreign function interface (FFI) to tell Gleam that those functions can be called with so and so types, and it does the type checking. This means that once you travel into Erlang code all bets are off. It's really no different to saying that a certain C function can call assembly code.

                        pub type ProcessReference(message, term) {
                          ByPid(Pid(message))
                          ByGlobal(term)
                          ByLocal(Atom)
                        }
                        
                        @external(erlang, "otp_server", "call")
                        fn gen_server_call(
                          pid: ProcessReference(message, term),
                          call_func: CallFunc(state, reply),
                        ) -> Result(reply, Nil)
                    
                    And the corresponding Erlang code:

                        call({by_pid, Pid}, CallFunc) ->
                            {ok, gen_server:call(Pid, {call_by_func, CallFunc})};
                        call({by_global, Name}, CallFunc) ->
                            {ok, gen_server:call({global, Name}, {call_by_func, CallFunc})};
                        call({by_local, Name}, CallFunc) ->
                            {ok, gen_server:call(Name, {call_by_func, CallFunc})}.
    • MDTHLN 6 hours ago

      I love Gleam, but I would start with Elixir if you're interested in learning about how powerful the BEAM & OTP are.

      There's not much documentation/resources around OTP in Gleam. When I was playing around with it I often found myself referring to the Elixir docs and then 'translating' that knowledge to Gleam's OTP implementation.

      Gleam is still very new so this is totally understandable, and both are great languages so you'll likely have a lot of fun learning either of them.

      • 59nadir 5 hours ago

        Erlang is a much better language to learn if you're interested in learning about the BEAM and OTP, and the book "Programming Erlang"[0] is an excellent resource for learning it.

        0 - https://pragprog.com/titles/jaerlang2/programming-erlang-2nd...

        • _acco 4 hours ago

          I disagree. I started with Elixir and its OTP resources are really good. Books like Elixir in Action do a great job.

          I read Programming Erlang later, but it was just for fun, and I knew most things already at that point.

          • 59nadir 4 hours ago

            I've used Elixir since 2015 and in fact learned it first. I still think "Programming Erlang" is a much better book than any other for actually learning Erlang and BEAM/OTP principles. Erlang as a language is simpler, leaving more time and energy for learning the actual important bits about OTP.

    • Munksgaard 10 hours ago

      As someone who comes from Haskell/ML-like languages, I decided to opt for Elixir and Phoenix for my latest project, simply because of the maturity of the web framework and LiveView. If I weren't building a web app, I'd probably have gone with Gleam instead.

      Edit: I do miss static typing, but it's worth it to not have to reinvent the web framework wheels myself.

    • victorbjorklund an hour ago

      Gleam is cool but honestly for now the ecosystem is so much bigger in Elixir. And yes, you can use some libraries across and things like that, but then again you could also bring in the parts that you need from Gleam into Elixir and instead of vice versa. If you just want to learn a really cool language, I think Gleam is pretty cool. But if you want to learn a language that is more productive but still kind of cool, I would start really like here and then dip my toes into Gleam.

    • rapind 11 hours ago

      For me, gleam is a better fit for the reasons I mentioned, but elixir / phoenix is definitely more mature, so I guess it depends what you like and what you want out of it.

    • innocentoldguy 10 hours ago

      In my opinion, Elixir and Phoenix will give you a better experience with BEAM and OTP, excellent tooling, a more mature ecosystem, and one of the best web frameworks ever to exist. I think Gleam is cool, but I can't see trading these benefits in for static typing.

      To be fair, I can't think of anything I care less about than static typing, so please keep that in mind when entertaining my opinion.

      • raphinou 6 hours ago

        I also preferred dynamic typing, until my complex rails app grew to the point I didn't dare to do any refactoring. But I didn't switch opinion until I discovered ML type systems, which really allow for fearless refactoring. At occasion there's some battling to satisfy the typesystem, but even with that I'm more productive once the app grows in complexity.

        I thought I'd share my experience, not trying to convince anyone ; - )

giancarlostoro 3 hours ago

For all you wonderful people using Gleam, do you use Phoenix? Or any other web frameworks? I really would love to do Gleam with something like Phoenix, but I have not had enough time to check out all the options yet. I'm in the process of building a new project with Python, but if there's options with Gleam worth looking into I just might try it out for this new project.

hackingonempty 15 hours ago

PureScript is a mature functional programming language with an Erlang back end, if you want another statically typed alternative for BEAM. It is basically a dialect of Haskell with strict evaluation and row polymorphism.

  • lpil 7 hours ago

    The JS backend is mature, but the Erlang backend and accompanying ecosystem is very small compared to Gleam.

  • behnamoh 15 hours ago

    The website and gh repo say it compiles to JS. Where did you learn that it has an Erlang backend?

cedws 5 hours ago

I’m fascinated by the sound of Erlang/BEAM but I’ve never found the time to actually try it. How are people using it in production? Do you write all your service logic using it or delegate specific parts to it?

  • sethhochberg 2 hours ago

    I'm leading a team that is doing an incremental migration to Gleam by delegating specific parts - we're basically pushing the "functional core, imperative shell" pattern to its macro extreme and having Gleam pick up background jobs from our existing Ruby on Rails codebases where we have heavy calculation tasks.

    This approach is notably not making proper or really any use of OTP features, but it was an extremely easy way to adopt a safe, fast, functional language for number-crunching workflows while continuing to lean on Rails for everything else its great at: web interfaces, HTTP APIs, etc.

    Rails is basically the configuration tool for the various inputs of a job, and job is passed to Gleam via Redis as an atomic set of config inputs to be used when processing a dataset (usually big CSV files streamed from object storage). We use a very thin Elixir wrapper to do all network and file IO etc, Gleam modules are pure business logic called from Elixir.

    Some day soon, I'm going to try and write up a longer technical article about this approach... it comes up surprisingly often in HN conversations.

    • cedws 2 hours ago

      Did I understand correctly you’re using it for file processing? If so does it yield reliability benefits? We have an assortment of jobs written in Go that process files of various types (CSV, Parquet, TXT) in S3 too. The issue we have is that our Kubernetes jobs crash all the time when they encounter something unexpected. Obviously we should invest into making them more robust but what we really want is some way for the jobs to continue processing whatever they can instead of crashing and starting over.

      • sethhochberg an hour ago

        In our case, the files being processed are datasets that have already been normalized through another ETL tool. Since we're doing the preprocessing ourselves elsewhere, our Gleam parsers are set up to expect a pretty rigid set of inputs. We do all of the file IO / streaming in Elixir and pass the raw data into Gleam as Elixir maps: so Gleam just takes maps, parses them into types pretty rigidly, and our entire Gleam module ecosystem assumes "perfect enough" data.

        If we encounter row-level errors in a batch, we log those alongside the outputs. There's nothing particularly intrinsic about out usage of Gleam that prevents the workers from crashing during processing, its all about having error handling set up within the job itself to avoid killing the process or pod running it.

  • davydog187 5 hours ago

    We use Elixir at TV Labs to build our web services, a realtime matching engine, execute sandboxed Lua code, talk to microcontrollers over binary protocols, machine learning, and much more.

    It is an excellent general purpose language that succeeds in a lot of domains.

    Check out my conversation from Developer Voices for more info

    https://youtu.be/_MwXbHADT-A?si=2lRqjwAY9dsODyhW

  • josefrichter 3 hours ago

    Come and have a chat at elixirforum.com. Plenty of folks write serious stuff entirely/predominantly in Erlang/Elixir & BEAM and will be happy to answer your questions.

    • cedws 2 hours ago

      Thank you!

  • jnmandal 5 hours ago

    I've seen folks doing both. Certainly, once you understand the OTP well enough, it makes sense to build all your systems on it. I've been doing that for seven years now; the only real issue was trying to train juniors to be productive on those systems. It did take longer to get them going I found.

constructive 3 hours ago

gleam would be taken more seriously if they didnt plaster their strong political opinions on the project page..i do not understand why they need to talk about politics on a tech project page..this is not about agreeing or disagreeing with the viewpoints..just keep it out of your professional discourse if you want people to take you or your work seriously...otherwise it just deviates the discourse unnecessarily

  • timeon an hour ago

    > otherwise it just deviates the discourse unnecessarily

    You could have chosen not to deviate the discourse.

isodev 16 hours ago

Very cool! Looking forward to trying it out.

hackingonempty 16 hours ago

IMHO the actor model is great until you need to share something across processes, then you have a distributed computing problem inside your program.

For developing fault tolerant multicore programs I think I'm better off using a functional effects system with software transactional memory like Scala/ZIO than Gleam/OTP. I can still use the actor model where appropriate. Plus the JVM software ecosystem and runtime observability / battle-testedness is far better than BEAM.

  • troad 13 hours ago

    This is a wild take. It's one thing to criticise the BEAM for things it's bad at, it's another to criticise it for the thing it absolutely excels at.

    The BEAM is built around passing immutable messages between cheap green threads, watched over by resilient supervisor trees. This means no data races (immutability), no hung threads (supervisor trees), no boilerplate (all of this is built in). The performance is surprisingly good, since mandatory immutability permits reference-based optimisations (copying, passing, etc).

    The BEAM is, in fact, the perfect platform for the exact use case you describe - fault-tolerant, distributed/concurrent systems.

    • giancarlostoro 3 hours ago

      Not only does it do this all very safely, it uses the absolute most minimal of memory to achieve this.

      This video from 9 years ago... 1 million people in 1 chatroom, on a server with 40+ cores and 128GB of memory, one single physical server, it used up about 30 GB of memory. Show me a language that can do this efficiently. In under a second it went to a million different connections. The cores didn't spin up to grind hardcore, none of that. Just efficient lightweight processes running. This isn't even on "raw Erlang" its on Elixir -> BEAM which is impressive on its own in my eyes. I would love to see equivalent videos on similar hardware today in other languages. ;) Please by all mean, show me your JVM application that can handle this.

      I also love that he thinks all those languages that are a decade (and decades) younger than the BEAM which has been in production since 1992 are more production ready and tested. I don't think he realizes the origin story to Erlang whatsoever, it was 100% built to be used in production for a major telecoms provider.

      https://youtu.be/N4Duii6Yog0

  • t-writescode 9 hours ago

    I think it's also worth mentioning - that hasn't been mentioned yet, Erlang (which Gleam is built atop) is ... *the* language that facilitated the nine nines (99.9999999%) uptime; and one of the major components of its ability to do that came with the robust supervising and cross-machine infrastructures that inspired the actor systems later on.

    This is, quite literally, Erlang's job. It's one job. And it is proven *excellent* at it.

  • NL807 14 hours ago

    >IMHO the actor model is great until you need to share something across processes

    Incidentally that's what actors are designed for, passing data and being able to mutate their state without use of explicit synchronisation. You either copy or transfer ownership of data from one actor to the next via message passing. Actual sharing should be only done if the data in question is globally immutable.

  • victorbjorklund an hour ago

    Can you give an example where you MUST share mutable data that cannot be passed as immutable data structures? The only thing I can think about is hardcore number crunching and yes that should not be written directly in elixir or Erlang or something like that but you could always write NIFs for that.

  • isodev 15 hours ago

    In Elixir/Gleam/OTP.., the entire program is a collection of progresses which are isolated from each other. Even if you don’t implement the actor pattern, passing state between processes and coordinating is a solved problem. We have primitives like tasks, agents, GenServer, Supervisors etc.

    • hackingonempty 15 hours ago

      Whenever you message another process and need a reply there is a risk of deadlock. I didn't find any primtives in OTP for handling this, you have to structure your actor interaction to avoid it. You can't just have a little bit of shared memory.

      The actor model doesn't really offer any benefit over other models while bringing significant downsides. Plus, there are major downsides to using an unpopular platform like Erlang/BEAM.

      • ashton314 14 hours ago

        > Whenever you message another process and need a reply there is a risk of deadlock.

        There are edge cases, sure, but I have yet to encounter a deadlock after 7 years of professional work with Elixir.

        > I didn't find any primtives in OTP for handling this

        See `GenServer.call/2`. This covers 99% of call/return patters in distributed systems. I take it you haven’t written much (any?) Elixir because you would have found this function.

        > The actor model doesn't really offer any benefit over other models while bringing significant downsides.

        Actors are a way better abstraction for pretty much any application-level code I can think of. I say this having written Go, Rust, and Elixir. What downsides are you talking about specifically?

        > Plus, there are major downsides to using an unpopular platform like Erlang/BEAM.

        The BEAM is popular. At least 3 different serious languages use it. What down sites are you waving your hands up here?

        • troupo 7 hours ago

          >> Plus, there are major downsides to using an unpopular platform like Erlang/BEAM.

          > The BEAM is popular.

          At this point I'd say it's more popular than ZIO, an effects/io/concurrency library basically implementing its own language and threading model on top of Scala

      • g8oz 15 hours ago

        Unpopular as opposed to what, Scala/Zio?

      • isodev 15 hours ago

        Beam languages have a different concurrency model than what you’re used to in JVM world. I suggest that you try some of them in a real project (even without actors).

      • asa400 14 hours ago

        OTP typically handles this with timeouts and then restarts when timeouts occur. Not to say it can't happen, but there are strategies.

        • throwawaymaths 14 hours ago

          sometimes people think they know better and want to reinvent the wheel

      • throwawaymaths 14 hours ago

        Avoiding exactly that is why erlang gives you genserver.

      • signa11 15 hours ago

        ```

            Whenever you message another process and need a reply 
            there is a risk of deadlock.
        
        ```

        can you please explain how there is risk of deadlock here ? thanks !

        • jhgg 15 hours ago

          Risk of deadlock is real if you have processes calling each-other in a cyclic way. e.g. process A sends GenServer call to process B, that then sends a GenServer call to process A to in order to handle the original call. However, process A is busy waiting on B to reply to it's initial call.

          This is rarely a problem in practice however.

          • oxidant 14 hours ago

            receive takes a timeout. A would crash/hit the timeout and deal with the problem.

            • jhgg 12 hours ago

              Yes, agreed, hence rarely a problem in practice ;)

          • innocentoldguy 7 hours ago

            Wouldn't you just `cast` instead of `call` if you thought this was going to be an issue?

          • signa11 13 hours ago

            you are not blocked on response right ?

            • worthless-trash 10 hours ago

              You can cast or call ( non blocking, or blocking) you can do either.

              • signa11 8 hours ago

                how would you get a deadlock with non-blocking requests ?

      • toast0 11 hours ago

        > Whenever you message another process and need a reply there is a risk of deadlock. I didn't find any primtives in OTP for handling this, you have to structure your actor interaction to avoid it. You can't just have a little bit of shared memory.

        I see a lot of people that dislike Erlang that run into this or are afraid of running into it.

        Receive with timeout is certainly a primative to handle this, and there's things like process aliases so that late responses can be more easily ignored. But, there's nothing structural preventing it. It's just that when you do it, it hurts, so hopefully you stop doing it.

        But when you're in a situation whete you've setup circular messaging, it's very easy to identify. You see a process (A) that has is making no progress and/or has a large mailbox. You see the stack trace shows it is messaging process B, you inspect process B to see why it didn't respond and see it is messaging process A and there you go. Or you look at process A's mailbox and see that it has requests related to the request it's already trying to fill.

        > The actor model doesn't really offer any benefit over other models while bringing significant downsides.

        I find the actor model makes many types of request/response for persistent state to become very simple. A process owns the state, and application level read/write messages are sent to that process. The mailbox serializes access to provide an ordering and to make changes atomic. You certainly have choose your messages; if you have a counter and do a get and a set message, you will lose updates; you need to do increment/decrement/add kind of messages; same thing for larger/more complex datastructures. Linked datastructures are still tricky.

        It's also super nice for request/response connection handling. Just write straight line code that reads from the connection when it needs a request and writes to it when the response has been computed. When you need to be asynchronous, it's more complex, but not so terrible. The process becomes a loop waiting for a fully formed request from the client (which then gets sent off to a request processor) or a reply from a processor (which then gets sent to the client) ... Java's Loom green threads will likely socialize this further than Erlang has.

        This kind of feeds into your other question, but IMHO, Actors let you focus on a process owning state and the messages it receives and how they change the state. You don't need to model for the computer where those messages come from, that's emergent system behavior. BEAM doesn't need to know, and doesn't ask, in part because as a changable distributed system, there's not really a way to know. What sends what to who is essential complexity of the system, but the system doesn't need to know about it. If you had a full accounting of how messages are sent, then you potentially find message loops, but ... it's easier to just find and fix any loops at runtime. (hotloading code can make it very low cost to deploy changes, which makes it more reasonable to spend less effort finding issues before release)

        > Plus, there are major downsides to using an unpopular platform like Erlang/BEAM.

        Sure, you have to do more work yourself. On the otherhand, relying on the work of others isn't always less work than doing it yourself. There are at least a few Erlang consulting shops you can go to if you really need something and are unwilling or unable to do it. You can't find a lot of answers on stackexchange, but you also don't find a lot of wrong or outdated answers either.

        • worthless-trash 10 hours ago

          I have no idea how you'd solve this even if you were not using have the actor model, if you had functions that triggered a 'functional ring of functions', you'd have the same problem.

  • toast0 12 hours ago

    > IMHO the actor model is great until you need to share something across processes, then you have a distributed computing problem inside your program.

    I'm sorry, but your multicore computer (and most single core computers, too) are a distributed system, regardless of if you acknowledge it.

    Shared memory concurrency use implicit communicatiom between threads, but it's still communicating processes, you just don't have the same level of control and isolation. That said, of course there are situations where you can have better results with shared memory and there are situations where message passing is better. You can do shared memory in BEAM with ets, and if that doesn't meet your needs, with NIFs ... you likely lose out on some isolation, etc, and it won't always be a good fit.

    > Plus the JVM software ecosystem and runtime observability / battle-testedness is far better than BEAM.

    Maybe battle tested (although, I've certainly battle tested BEAM more than I've tested JVM; but I'll grant that many more people have done battle with the JVM)... but BEAM has quite extensive observability. You can see all sorts of stuff about every process, you can add tracing at run time. You can change the code at run time if you need to add more stuff; and runtime code changing is just in the normal setup, it's not exotic (although many people eschew it)

  • troupo 11 hours ago

    > I think I'm better off using a functional effects system with software transactional memory like Scala/ZIO than Gleam/OTP

    Translation: I think I'm better off using needlessly complex overengineered monstrosity that takes insane effort to do even the simplest of things, and that still gives me no actual guarantees, than a simple system that has been battle tested over decades of actual hardcore industrial use.

    • AlterEgo7 11 hours ago

      This sounds like a "I could not get my head around ZIO/Fuctional Effects ergo it's a needlessly complex overengineered monstrosity" argument.

      In fact, functional effects provide another elegant solution to problems requiring structured concurrency. You might like it, you might not. But there are many engineers in the Scala community who were bitten by the exact issues of actor deadlocking / difficulty of debugging compared to functional effects that refactored enterprise codebases from Akka (Scala actors) to functional effects.

      "A simple system that has been battle tested over decades of actual hardcore industrial use". The JVM certainly is not far from that.

      • troupo 11 hours ago

        > In fact, functional effects provide another elegant solution to problems requiring structured concurrency.

        There's nothing elegant about ZIO.

        > But there are many engineers in the Scala community who were bitten by the exact issues of actor deadlocking / difficulty of debugging compared to functional effects

        Because you can't truly retrofit a proper actor model onto a runtime that doesn't support it, and doesn't have proper tools for them.

        So they reach for third-party libs that try to implement half of Erlang, often poorly, and get understandably bitten by issues.

        > "A simple system that has been battle tested over decades of actual hardcore industrial use". The JVM certainly is not far from that.

        The JVM isn't built around built-in support for concurrency and distributed computing.

        Erlang's BEAM is. And its built-in primitives and standard library has been around for over three decades.