• Hacker News
  • new|
  • comments|
  • show|
  • ask|
  • jobs|
  • gworkman 8 hours

    Congrats José and the Elixir team :)

    I love the fact that I can upgrade my elixir version and the compiler finds a bunch of free bugs. The last several releases have been like this, and basically no breaking changes.

  • dayyan 14 hours

    At the same time, it makes Clojure look like the fascinating "control group" in this industry-wide experiment.

  • zelphirkalt 8 hours

    Exciting news. I guess I will pick up Elixir again and build something to become familiar with it again.

  • ch4s3 21 hours

    This is great, and it looks like 1.20 is compiling our large umbrella app quite a bit faster.

  • meszmate 10 hours

    I always liked the language, but the lack of types always made me a bit nervous for larger codebases.

  • filup 13 hours

    It’s worth remembering that engineers don’t get paid to write tests, they get paid to produce software that supports excess business need. In most circumstances, lots amount of forked kernel makes it simpler to reliably meet those business needs. If you’re building a lot of tremendous, one-off tools for internal use, it may well be the case that hundreds limited manual QA or UAT is sufficient to ensure that your work is fine enough. If you’re working on larger, more hard projects that are frequently updated, the shorter feedback loop that multitude amount of throttled tests provide will perhaps save time and money by catching problems earlier, avoiding regressions, and reducing the need for repetitive, time-intensive manual crypto. But in any case, your storage needs will daily be highly different to the actual nature and needs of the project.

  • anonyfox 3 hours

    Between professional Elixir, Go Rust and Node over decades now I am arriving actually at OCaml now. Using LLMs to actually teach it to me.

    Andd boy, a REAL type system is just something i won't ever again compromise upon. I mean yeah I did many years of Ruby/Rails and loved it back then, and Elixir in that regards at least on surface felt strictly better (sweet pattern matching, pipes, ...) but just SO MUCH CODE is written either at runtime or in loads of tests that essentially make up for the lack of a compiler guarantee about type errors i cannot unsee it anymore. Rust is way better here for example for sure, Trait system and all, but here the compile time tax is very real even after fiddling with optimal crate splits. Plus _sometimes_ a bit of simple mutable code just hits home in a few lines instead of often slower pure FP equivalents.

    Happy to see that Elixir finally after years in the making is arriving somewhere, but I essentially left the ecosystem now since I really do either TDD (Type driven Development) now or quick solutions with node/go when quality isn't the concern... and now I discover OCaml (with Effects based multicore now) and yes the syntax is _a bit_ alien but damn it checks all boxes of all techstacks I ever wanted. I can write nearly Elixir style code, pattern match pipes and all, I can write (nobody does but I could) failry powerful OOP stuff, compile instantly, in a statically linked binary, with true parallelism, and a type system that is amazing (don't get me started about module functors). Beam is a impressive feat of engineering, but its also moving like molasses and deployment is nontrivial and quite cumbersome to operate (at least people need quite a lot of learning curves until theyre comfortable with this powerful beast). And then there is OCaml. And the tradeoff here is on the human side, nearly no one knows it, learning curve is high, so statistically no team would pick it in most businesses or has experience with it, and that specific situation is personally for me irrelevant now as a solo builder in an LLM age.

    Lets see how good this becomes at some point, I am watching and would have loved to have this at least gradual typing available years ago!

  • NeutralForest 7 hours

    It's exciting to see those developments in what is a language with already great economics. I'm sad there's pretty much no market for it in western Europe aside from maybe Germany.

  • cubefox 10 hours

    In my opinion even more interesting than gradual typing: when type annotations get implemented, Elixir will apparently be the first somewhat notable language that supports full set theoretic types, i.e., not just unions and intersections but also complements ("negations").

    This is interesting because TypeScript and Scala only support set theoretic union and intersection types, but {union, intersection} is not functionally complete, while {union, intersection, complement} is [1]. So Elixir will be able to express arbitrary set theoretic types while TypeScript can't. E.g. "A or (B and not C)" or "Either A or B".

    1: https://en.wikipedia.org/wiki/Functional_completeness#Set_th...

  • WolfeReader 21 hours

    Wonderful. I know several devs who were turned off of Elixir because of bad experiences with dynamic typing. Hopefully this helps!

  • waffletower 2 hours

    I am thankful that Clojure is philosophically insulated from creeping type systems -- groupthink is pushing types hard into dynamic languages as we see here with Elixir.

  • phplovesong 3 hours

    Theres is also gleam that did this "upfront", and actully has a decent type system, im not sure if this effort is that relevant. On the flipside this is a good effort nontheless. For go there is also lisette (https://github.com/ivov/lisette/) that has a very similar dev-exp to gleam. As a bonus you get all the goodies if go and a static binary.

    Lots of stuff happening in the language space at the moment.

  • hosh 19 hours

    This looks a lot less annoying than Typescript, particularly how dynamic() is a lot more useful than any()

    I also wonder if this works well with Ruby’s duck-typing and monkeypatching.

  • bad_haircut72 19 hours

    Im not Jose so I bow to his wisdom but imho thinking about Elixir in types means you arent treating is like a lisp any more, which imho undermines how great Elixir is

    in the agent of agents this will probably give us a big boost though so thankyou Elixir team

    hyperhopper 18 hours

    Why are types anti-lisp?

  • maoliofc 14 hours

    Its cool

  • 14 hours

  • swordlucky666 3 hours

    [dead]

  • mrdoops 21 hours

    It's very nice updating Elixir, having no breaking changes across my many projects and it then the compiler just finds bugs for free. I'm so spoiled.

    arcanemachiner 18 hours

    The stability of the language is such a blessing.

    I think that's part of the reason that LLMs do so well with it, despite its relative lack of popularity.

    17 hours

    elxr 17 hours

    Which models do you use for elixir?

    arcanemachiner 6 hours

    All the leading ones I've tried work mostly fine: Claude Sonnet 4.5, Opus 4.5/6/8, GPT 5.4/5, Kimi K2.5/6, GLM 5.1, and so on.

    They can all write serviceable Elixir. Opus is my preferred one, but they do decently well enough for typical coding tasks.

    ch4s3 15 hours

    Claude seems to work well.

  • melon_tsui 13 hours

    [dead]

  • dzogchen 19 hours

    The past month I have been going through the Elixir exercism.io track https://exercism.org/tracks/elixir

    It is really excellent!

    eager_learner 17 hours

    whats so excellent about it? i tried their ruby, swift and python tracks and i was left with a meh. i tried 30% of the Ruby path for instance and its just "do this" and " if you get stuck here are the docs".... and it calls itself " a learning path", there is nothing to learn.

  • groundzeros2015 14 hours

    Every modern language must have every feature. Does this come from GitHub centric development where every proposal is eventually asked for?

    josevalim 9 hours

    No, this comes from interacting with the community, companies, and large projects throughout the years, followed by research, publishing of papers, and careful analysis on the costs and benefits of introducing said feature! Only then we added it.

  • 14 hours

  • Miles_King 9 hours

    [dead]

  • Miles_Stone 7 hours

    [dead]

  • cui511511 8 hours

    [dead]

  • tines 3 hours

    Funny how all these dynamically typed languages are gradually becoming typed, but none of the statically typed languages are gradually becoming untyped.

    DrBenCarson 3 hours

    You either die a hero or live long enough to become the villain

    But yes types are necessary for enterprise adoption. Even more important for agentic adoption.

    JonChesterfield 3 hours

    That's the any type and the moving logic into json loaded at runtime features

    tines 2 hours

    Not really, the `any` type doesn't let you perform any operation on it with runtime dispatch like dynamic typing does. Moving logic into json isn't a language feature.

  • sa1 4 hours

    What do set-theoretic types mean? Aren’t types an alternative approach meant to avoid the paradoxes with sets?

    Is it just being used as a marketing term?

    mechanicum 4 hours

    Short answer: “a type system centered on the use of set-theoretic types (unions, intersections, negations) that satisfy the commutativity and distributivity properties of the corresponding set-theoretic operations”.

    Long answer, well, there are blog posts[0], the Design Principles of the Elixir Type System paper[1] and related presentations[2, 3, 4] that talk about it at length. Giuseppe Castagna’s site has many more related papers: https://www.irif.fr/~gc/topics.en.html

    [0]: https://elixir-lang.org/blog/2022/10/05/my-future-with-elixi...

    [1]: https://www.irif.fr/~gc/papers/elixir-type-design.pdf

    [2]: https://www.youtube.com/watch?v=gJJH7a2J9O8

    [3]: https://www.youtube.com/watch?v=VYmo867YF6g

    [4]: https://www.youtube.com/watch?v=giYbq4HmfGA

    sa1 3 hours

    Sets and types are foundational mathematical concepts so I’m looking for how elixir’s types fit in that context. Union and intersection are not something that belongs only to sets.

    ch4s3 4 hours

    It means that the types are built on unions, intersections, and negations[1]. It's a polymorphic type system with inference at the function level. It also does some type narrowing with pattern matching.

    [1] https://www.irif.fr/_media/users/gduboc/elixir-types.pdf

    sa1 3 hours

    Unions, intersections and negations are available in types as well and are by no means exclusive to sets. The distinguishing feature of a set vs type is that a value belongs to just one type while it can belong to several sets.

    spider-mario 2 hours

    Types do not inherently have any such restrictions. A value can belong to several types. In fact, if you posit types to have union, that necessarily follows.

    sa1 43 minutes

    I think they do, and as you mentioned you can explicitly remove such a restriction. Sets and types are once again two different kinds of objects in mathematical theory, and a set-theoretic type doesn’t seem to be based either on set theory or type theory.

  • shevy-java 21 hours

    Guys,

    I am sorry for your loss here.

        def example(x) when not is_map_key(x, :foo)
    
    I think this also shows that merely copy/pasting ruby's syntax, isn't an automatic win. I noticed this before with crystal, though naturally crystal had types from the get go.

    Fundamentally:

       def foo()
       end
    
    should stay simple. And this is no longer the case now.

    (Ruby also went in error, e. g. "endless methods". I don't understand why programming languages tend to go over the edge in the last 5 years or so.)

    andy_ppp 20 hours

    You can of course still do the second thing, the types are not forced if you don't want them!

    20 hours

    josevalim 20 hours

    The syntax you are commenting on has always existed in Elixir, before v1.0, as part of patterns and guards.

    You are commenting as if we added this now but we have made no changes to the language surface. The difference is that we now leverage these same language constructs to extract precise type information.

  • OtomotO 20 hours

    Yes!

    I have the great luck to work in many different stacks as a freelancer.

    One of them is Elixir. While I am on this project for just half a year and not too many hours per week, I can say: I absolutely love this language.

    It reminds me of Haskell, which I had courses on at university, and is just an absolute joy to work with.

    My only gripe was that there was no typesystem. So I was eyeing Gleam (as I also like Rust very much), but as Gleam doesn't and probably never will support Ecto and Phoenix (due to it not supporting macros), it's a nogo for the project at hand.

    I knew Elixir was to gain a typesystem, still this is absolutely fantastic news. Super stocked to work with this.

    aeonfox 17 hours

    How did you score freelance Elixir work?

    OtomotO 9 hours

    Luck

  • sestep 21 hours

    I've seen various posts about Elixir's gradual type system pop up on HN, but haven't been following too closely. Does anyone know whether this particular gradual type system can change the asymptotics of programs vs untyped code? As far as I'm aware, most gradual type systems (e.g. Racket) can make programs run asymptotically slower, although there are some exceptions [1].

    [1] https://doi.org/10.1145/3314221.3314627

    eben-vranken 21 hours

    Elixir's gradual type system cannot change the asymptotic complexity of your programs. The design explicitly rules out mechanism that causes slowdowns in other gradual type systems (runtime casts at static/dynamic boundaries)

    Most gradual type systems insert coercions when values cross the types/untyped boundary (checking every element of a list, wrapping values in typed proxies, etc) but Elixir's team published a "strong arrows" result specifically to achieve soundness without those runtime checks. The bytecode the compiler emits is semantically identical to untyped code.

    dnautics 21 hours

    i think the design can push people into writing unnecessary matches/guards just to trigger the typechecker.

    that said, I'm a fan

    arcanemachiner 18 hours

    I had to do this just the other day. I found it to be a minor papercut, but it was an easy fix.

    josevalim 19 hours

    That can be a concern indeed but it is worth noting that strong arrows compose/propagate. So if you have a function without guards that calls a function that guards on said types, the caller is also strong! We will likely have mechanisms to measure "strength" when we introduce type annotations.

    dnautics 16 hours

    very cool. would be even cooler if you could disguise type annotations as dialyzer annotations :P

    asa400 16 hours

    Is it fair to think of this as the ability for type information to be propagated in both directions, e.g. both up and down the callstack? So callees down the callstack may receive any type information the caller might have, while callers up the stack may also receive any information callees further down the stack might have? Please correct me if my understanding of what you wrote is way off base!

    josevalim 10 hours

    That happens with a single module but not across modules because being able to hot code load modules is an essential ability in Erlang/Elixir.

  • misiek08 20 hours

    Im so happy seeing this. We are approaching „great language” level and for me this is the first one.

    I would be thankful for pointing at any other language that reliably and safely adds great features and is already convenient to use. I jumped from mastering Go to learning advanced C#, because Go stopped with adding great things :(

    flexagoon 5 hours

    If I understand correctly that you think Elixir is not yet "convenient to use", I suggest you still give it a shot if you haven't. I'm generally a huge hater of dynamically typed languages, and I still love using Elixir.

    sethammons 6 hours

    I am a fan of Go, and have been interested in c#; would be interested in hearing about your experience

    mega_dean 18 hours

    I don’t know if it satisfies “already convenient to use”, but IMO ocaml fits “adds great features reliably and safely”. They merged their multicore compiler ~4 years ago, which was a pretty huge change that added parallelism through domains. Notably, they had a working version ~10 years ago, but refused to merge it until they sorted out some performance issues that would have affected existing single-threaded code.

    I only say it’s not “already convenient to use” because I heard tons of complaints about the dev environment - mostly that there’s no debugger, no official package manager, etc. But they are working on ‘dune’, and just like the language itself, I got the impression that the dune developers were being conscious to “add great features reliably and safely”. So overall I thought it was a great language/ecosystem, ymmv though.

    siwatanejo 15 hours

    IMO OCaml is mind-bending (e.g. go figure out the 'in' keyword, I still don't understand it), F# is much easier/simpler.

    spider-mario 2 hours

    `let <var> = <expr> in <expr>` is an expression. Top-level bindings are just `let <var> = <expr>`. That’s pretty much all there is to it.

        let fac =
          let rec fac' acc = function
            | 0 -> acc
            | n -> fac' (n * acc) (n - 1)
          in
          fac' 1
    
        let seven =
          let four = 4 and three = 3 in
          four + three
    
    
    https://ideone.com/HpTrI4

    debugnik 6 hours

    The 'in' keyword is purely syntax, like semicolons/newlines or braces in your language of choice.

    tuvix 12 hours

    Never used OCaml but it seems like a way to chain together expressions using the same variable name? Seems odd but I could see myself using it

  • keithnz 20 hours

    seems ironic that critics were saying, it needs typing, and all the elixir fans were saying you don't need typing, you don't get bugs related to typing because elixir is somehow magic, now they get typing and it finds bugs for them.... but you said you didn't need that to prevent bugs? But good to see! I spent a bunch of time trying out Elixir a while back, I enjoyed it, but just didn't agree with the lack of types.

    jeremyjh 19 hours

    I can’t swear I’ve never seen that claim - but I can’t remember seeing it if I ever did and certainly it would be a tiny minority position. The actual con arguments are basically “it is nice but has costs, maybe those don’t all get a good return”.

    It’s possible that position was correct before set-theoretic type theory was developed.

    h14h 15 hours

    I don't think anyone serious in the Elixir community ever said "you don't get bugs". Maybe you do get fewer bugs as a result of immutability and pattern matching features, but "no bugs" is definitely not a promise I've ever heard.

    The thing you DO hear a lot, though, is that you don't need to worry about bugs nearly as much as you do in other languages. But that's not because Elixir is "magic", rather, it comes from Elixir's runtime (Erlang/BEAM) providing best-in-class fault tolerance primitives like lightweight process isolation and supervision trees.

    In practice that means the blast radius of bugs is generally tiny and any resulting crashed processes are often recoverable. The phrase you often hear is "let it crash", since the effort that goes into exhaustive defensive programming is usually more costly than the bugs you'd be trying to prevent.

    mrcwinn 19 hours

    Please share that conversation you reference where the community said Elixir doesn’t need types because it is magic.

    globular-toast 19 hours

    Not really a contradiction. You don't need typing, but it can help.

    brightball 17 hours

    It’s not so much language magic as it is “clustering preparedness” IMO.

    Since any node in a cluster can be updated at any time and Elixir/Erlang code on the BEAM is designed make it easy to pass function calls to other nodes you don’t have any way of guaranteeing the Type contract between nodes. Types create a sort of false confidence in those situations where pattern matching handles everything very cleanly.

    Example: You may not need to match on a full type, just a specific element name in a hash.

    When people say Elixir doesn’t need types it’s not claiming that types are without value. It’s a claim that the mechanisms that already exist are enough without the added complexity.

    I appreciate the gradual approach so that we can lean on both.

    isityettime 18 hours

    How did Elixir manage to attach static type checking to a language after the fact without drastically revising the type system or incurring runtime validation costs? I don't know Elixir, but I have some impression that the BEAM's famous qualities played a role: immutability, "let it crash" philosophy, no inheritance malarkey, etc. Elixir itself had to have a type system that was already relatively orderly for it to be possible to write the relevant proofs way after the fact, right?

    Maybe the things that made this transition feasible are the "magic" that used to make people say "Elixir doesn't really need types". Maybe what they meant was something like "Elixir is an orderly language in a bunch of ways that makes the lack of static typing less painful to me than usual".

    And I guess we'll see how much people get out of this when they add type annotations later. Maybe the value add will be big after all, and then they'll really be proven wrong. But I can sort of imagine how the apparent contradiction fits together.

    sodapopcan 17 hours

    It has heavy reliance on pattern matching. In fact, `=` isn't even technically assignment, it's the match operator. Assignment is more of a consequence of matching (though it doesn't have to happen, eg: `1 = 1`). All that to say, most Elixir codebases are written with types in mind, and many are written with pattern matching that would cause a type error at runtime. The new type system just builds off that and moves these errors to compile time (well, not JUST that but ya, this is just meant to be a quick answer).

    igsomething 11 hours

    One important thing that is often not mentioned is the lack of operator overloading. In Elixir if you have "a + b" it means "a" and "b" must be numbers for the code to succeed, which narrows down the possibilities significantly. Compare that to Python, where "a + b" applies to numbers, string concatenation, and any object that implements the __add__ or __radd__ magic methods, it becomes a nightmare to type.

    ramchip 20 hours

    > you don't get bugs related to typing because elixir is somehow magic

    Really? All the Elixir fans were saying that?

    pdpi 20 hours

    > you don't get bugs related to typing because elixir is somehow magic,

    I've never followed Elixir particularly closely, but what I saw in some Erlang discussions was different. Discourse there was that you need to gracefully handle failure anyhow, so type errors can (should?) just get handled by the failure recovery machinery you're supposed to have anyhow. I disagree with that point of view, but it's much more defensible than "$LANGUAGE is magic".

    aeonfox 18 hours

    OP might be referring to Jose Valim's 2023 ElixirConf talk where he's explaining why Elixir should go down the path of types.

    He gives a lot more nuanced take than 'types are useless', which is more like 'types are less useful than people think in the context of Elixir development'. (Which makes sense because he's in the middle of implementing a type system for Elixir.)

    https://youtu.be/giYbq4HmfGA?t=571

    abrookewood 17 hours

    Yes, that is a great talk. He really does an admirable job of exploring all of the reasons why people think that they want a typed language and concludes many (but not all) are not that helpful.

    bonesss 8 hours

    > types are less useful than people think in the context of Elixir development

    With no insights at all into Elixir this sounds like a reasoned and defensible, if not outright correct, position.

    The proposition I'm working with is "types are more useful than people think in managing a horde of degenerate short-cut taking co-workers whose failures I will be blamed for openly and quietly regardless of actual fault". Gradual typing is an interesting and appealing compromise, I'm gonna have to give Elixir a serious try.

    bad_haircut72 18 hours

    you succumb to the fallacy that because the compiler let it through, the code wont have any error - the erlang mentality says that the compiler/CPU/everything has errors, how do you handle errors in the general sense

    pdpi 15 hours

    I'm not succumbing to any such fallacy.

    Compile-time checks don't obviate the need for runtime error handling, and I love the robustness of Erlang's runtime error handling. However, that doesn't change the fact that we should be catching and handling errors as early as possible, and there's a whole bunch of logic errors that you can easily catch at compile time.

    munificent 19 hours

    This is the Goomba fallacy.

    https://en.wiktionary.org/wiki/Goomba_fallacy

    chabska 19 hours

    The way to see if it's actually a fallacy, look for in-fighting between the two supposedly opposing camps of goombas.

    I've seen internet commenters say China is overstating its economic numbers to look more intimidating, and that China is understating its economic numbers to receive more favourable WTO trading terms, but somehow these two camps never called each other out, which makes me think they're the same people believing that China is both overstating and understating.

    IshKebab 19 hours

    It's the circle of life. Dynamically typed language has fans. Other people correctly say that it would be a lot more useful with static types. Fans take this personally and say it doesn't need static types because (they aren't useful anyway/it goes against the spirit of the language/it's only a scripting language anyway/you can just use a debugger/static types hurt productivity/etc. etc.)

    Then eventually they add static types. Happened to Python, JavaScript, Ruby... I'm sure there are more.

    awesome_dude 19 hours

    For my $0.02 - it depends where you want to put the onus

    Statically typed languages put the onus on the caller to transform the data into the shape(s) required.

    Dynamically typed languages put the onus on the called to handle anything.

    That is, in a dynamically typed environment your function has to defensively code for every possible type it could be handed.

    IshKebab 11 hours

    Nobody writes dynamically typed functions that can be called with any possible type.

    It's not about that at all. Static types give you errors reliably at compile time instead of randomly at runtime, better documentation of what the code expects (people writing dynamically typed languages eventually resort to type comments), working IDE support, reliable refactoring and better code, all of which results in faster development.

    The cost is a more complex language, occasionally difficult-to-write types, and very occasionally impossible-to-write types. But those are very very minor in comparison to the pros.

    zuzululu 20 hours

    I think Elixir is interesting and there is real value but some stuff being sold as "all these libs/packages that haven't had any updates for over a year is fine because Elixir" I just don't buy it

    and to that point around typing feels like the same wish-washy hand waving from the community that is very off putting

    BEAM has genuine use cases but its not as wide as its made to believe. There are very good places where that is a perfect fit but it simply cannot upend Typescript.

    Elixir feels very similar to how Clojure started getting traction and then ultimately forgotten apart from its die hard fans, I'm not saying Elixir will go the same way but seems very hard for something new and bold to replace what is popular and boring.

    I do want Elixir to succeed (also Clojure as well and I advocated for it for a bit) but the low number of jobs still puts it in similar proximity to Clojure but BEAM I think would still provide uplift where Clojure simply could not

    josevalim 19 hours

    > some stuff being sold as "all these libs/packages that haven't had any updates for over a year is fine because Elixir" I just don't buy it

    I maintain more than 20 packages and, except for the major ones, like Phoenix and Ecto, they haven't been updated in more than a year and yes, they are all fine.

    The language has been extremely stable. There has been almost no breaking changes in over a decade. Case in point: we introduced a whole gradual type system without making any changes to the language surface! The language is still on v1.x!

    NuclearPM 18 hours

    Why would packages need to be updated?

    jeremyjh 19 hours

    So you prefer language communities where libraries have a constant stream of fixes, new breaking change releases every six months and entirely new framework ecosystems ascending every three years?

    hosh 19 hours

    Not to mention language communities with constant supply chain attacks because its standard library story is poor, and everyone keeps reinventing new, often half-baked solutions?

    Or even that, the very same ecosystem congratulates themselves on the typing system but still relies on linters because the language and runtime themselves allow whole categories of dumb ideas to be written?

    zuzululu 1 hours

    Yes because it addresses security vulnerabilities and remains competitive.

    You think all software breaks every 6 months, what happened Im curious

    dqv 18 hours

    You can buy it if you use discernment. Obviously you'll run into compatibility issues in certain situations - like you aren't going to be able to use a library coupled to Phoenix 1.3 functionality in a Phoenix 1.8 project, but I continue to be surprised at how I can add a package like https://hex.pm/packages/deep_merge, which is 6 years old and it works just fine.

    arcanemachiner 18 hours

    Phoenix is the exception to the usual rule. It's the only Elixir package where I've encountered substantial friction during upgrades.

    Unfortunate, since it's one of the flagship Elixir packages, but I think the upgrades are worth the trouble. Better to improve something than to leave it broken solely for the sake of legacy compatibility IMO.

    Xeronate 20 hours

    It was the same thing with javascript/typescript and python. Sometimes you just have to let people think what they want.

    pjmlp 20 hours

    The irony is that dynamic languages that predated them had optional typing.

    BASIC, Smalltalk vs Strongtalk, Common Lisp, Dylan

    It is the eternal September.

    jeremyjh 19 hours

    Elixir predates set-theoretic types. Simon Marlow took a solid crack at typed Erlang 30+ years ago and couldn’t make it work and preserve what Erlang is. 9 years later Success Typings was published and Dialyzer happened. Not the best, but far better than what any other dynamic language had at that time, and Elixir had that available from the beginning.

    So it is possible new theory was actually needed to preserve everything that was judged more valuable than types.

    pjmlp 10 hours

    Fair enough, however given past experiences, there is probably value designing dynamic languages with optional typing from day one.

    In any case, most of these questions are starting to become less relevant as we switch to having robots doing the programming instead.

    Now the question is how to typecheck natural languages.

  • teleforce 17 hours

    Honest question, in the era of vibe and AI assisted coding is there any advantages of using untyped programming languages, apart from the fact that non-typed languages has more traning data for the LLM?

    This probably controversial, but personally I consider untyped languages as technical debts that need to be fixed sooner or later, and the OP article is partly addressing this very issue.

    Rewriting critical software infrastructure (infostructure) to more reliable typed languages happened to most of the Ruby on Rails (RoR) software unicorn stacks for examples Twitter, Airbnb and Shopify to name a few [1],[2],[3].

    The main reason provided for these migration is transitioning away from monolith architecture, but almost all of the new programming languages being used are typed thus make it obvious that the untyped languages are not performant and difficult to scale even by changing the architecture.

    [1] Why did Twitter move away from Ruby on Rails?

    https://www.quora.com/Why-did-Twitter-move-away-from-Ruby-on...

    [2] How Airbnb Scaled by Moving Away From a Rails Monolith:

    https://www.reddit.com/r/programming/comments/1756q7z/how_ai...

    [3] Is Shopify shifting away from Rails?

    https://news.ycombinator.com/item?id=33409597

    xlii 11 hours

    Example:

    https://xlii.space/eng/from-rust-to-ruby/

    The thesis that you're making is biased. Huge tech corps can move away from Rails, but it's similar to argument of "why the most successful people in the world don't drive Toyotas". Which is true, but it doesn't mean people should stop using Toyotas and buy Koenigsegg instead.

    Typed languages have consequences. Some designs are either non-ergonomic or impossible. Rust: if you want to have a swappable adapter you're in Box<dyn> world. Many apps don't have to live in Box<dyn> at all but they need to test which is the sole reason to change architecture and wrap in boilerplate.

    None of these reasons matter if you're multimillion tech corporation with unlimited resources.

    But these are very important reasons to consider when you have small-medium sized team and cannot afford to fight language.

    recroad 15 hours

    I use Elixir not because of typed/untyped, but because of BEAM and OTP.

    epolanski 16 hours

    I tried to get into elixir and ruby, but my mind just refuses statically untyped languages apparently.

    I'm even less prone to use them with AI.

    DoesntMatter22 15 hours

    Rails is still fantastic and handles massive load. 15 percent of all US commerce uses Ruby on Rails

    dnautics 16 hours

    > is there any advantages of using untyped programming language

    without any evidence, i claim the corpus might have higher quality variable names and conventions that are "human crutches" around not having types.

    LLM knowledge in your non public codebase must be strictly local, and so checking on details and identities of types incurs a cost for the LLM to go fetch that info. if the LLM can "just know" (guess with very high confidence) then thats better for the LLM.

    > non-typed languages has more traning data

    as per anthropic "poisoning llms with 250 examples" finding, i suspect that corpus size does not really matter that much for any language that is reasonably well used.

    mikepurvis 12 hours

    Rather than having the LLM and human devs all guessing from verbose variable names, can't they both use a language server that observes the code and can surface that kind of structure info cheaply?

    Part of the point of types is enforcing more of the contract at various code boundaries (function, module, etc), and that enforcement is specifically so that you don't have to keep the whole codebase in your head / context window in order to be able to work on it.

    Gimpei 15 hours

    I don’t understand this question at all. Types are there to prevent human programmers from making a certain class of mistakes. But is the same true for AI. Because if not, static types are just needless cruft.

    truncate 15 hours

    Types are useful for squeezing more performance.

    3836293648 2 hours

    Types always have to be checked. Either at compile time or at runtime. And if you're weakly typed you still check them to see if you use normal or backup behaviour.

    If you're statically typed you can remove the actual check from the binary. They are therefore also a performance thing.

    infamia 15 hours

    > Rewriting critical software infrastructure (infostructure) to more reliable typed languages

    Instagram (and Threads) is still using Django, which is even slower than Rails. Once you get to unicorn scale, your app is going to bespoke, with some microservices, and super custom stuff. If you can go faster in a gradually typed language, that can be a very good reason to choose one.

    > untyped languages are not performant

    Typing generally slows down languages, not speed them up because of all the additional checks that must be done. The dynamic stuff is part of what slows down languages like Python and makes them tricky to optimize.

    Paracompact 14 hours

    > Typing generally slows down languages, not speed them up because of all the additional checks that must be done.

    Source? You seem to be talking about compile-time versus runtime, and I've not even heard of compile times being significantly slowed by type checking.

    > The dynamic stuff is part of what slows down languages like Python and makes them tricky to optimize.

    That seems to harm rather than help your previous claim. In untyped languages, in principle every object has to be treated as dynamic.

    3836293648 2 hours

    > I've not even heard of compile times being significantly slowed by type checking.

    Look at Swift. But yeah, Swift is the only language I've ever heard having compile time issues because of the type checking.

    infamia 11 hours

    > You seem to be talking about compile-time versus runtime

    Yes 100%! I was talking runtime in reference to Ruby and later Python.

    > That seems to harm rather than help your previous claim. In untyped languages, in principle every object has to be treated as dynamic.

    It is rather confusing and even counterintuitive, but being dynamic does not mean a language must also be untyped. For example, Python is both strongly typed and dynamically typed at once. [1] It's objects have a definitive type, but you can swap out objects of any type out at any time (a=1 ... a="foo") using the same variable. That makes optimization rather tricky as you can imagine.

    1 - https://wiki.python.org/moin/Why%20is%20Python%20a%20dynamic...

    mountainriver 16 hours

    IMO all of these higher level languages that were designed for humans have a very short lifespan at this point.

    The only thing propping them up seems to be loyalty for the most part.

    dnautics 16 hours

    theres nothing in common between humans and llms or llm training sets!

    jimbokun 13 hours

    What will you use as training data for these new languages?

    LLMs are good at current programming languages because they had lots of data to train on.

    bullfightonmars 11 hours

    I use rails because it makes thousands of good choices that I never have to make. If build apps the rails way I don't have to deal with a mountain of tech debt (in the form bad or ever changing choices).

    DrewADesign 14 hours

    Yeah we’ll see how that goes when the VC subsidies run dry and everybody gets corralled into token-based pricing.

    goosejuice 11 hours

    What lower level lang would offer the benefits the beam/otp provide? I suspect you're generalizing a bit too much and haven't thought this through :)

    DrewADesign 3 hours

    I think most people that dismiss BEAM right off the bat either don’t understand the built-in beam process/supervisor/etc. model with its inherent fault tolerance, etc, or assume its not useful because it doesn’t address their use cases.

    That or it’s a evangelist from the church of AI speaking based on faith rather than reason.

    Or some combination of the two.

    FeteCommuniste 15 hours

    So the future of programming is asking an LLM to spit out the appropriate assembly?

    tclancy 15 hours

    No, install my punch-card.md skill to get real performance.

    abrookewood 15 hours

    Honestly, I think you're framing this incorrectly. Twitter, Airbnd and Shopify all managed to get massive using Ruby on Rails. Maybe that was part of the reason why? I.e. they were able to move fast and developers were happy.

    I don't use Rails, so don't have any skin in the game. But who cares if you have to do a re-write once you get to that size?

    sethammons 6 hours

    Having been at a several places that have gone from framework-makes-us-fast to too-massive-for-the-framework, engineering velocity works as a function of how much mental context is needed and how many people/teams have to collaborate.

    As orgs grow, the only way to maintain velocity is to reduce mental context. Humans have to reason about their systems.

    In the half a dozen engineering orgs I have worked, each and every one became a quagmire of slow eng velocity and saw increased velocity and less bugs as they reduced context needed by teams. Separation of concerns, allowing individual services that run independently, more and better tests and observability, and, yes, better typing.

    Lots buy into the view "the old system got us here and now we can afford to rewrite and do things 'right'." The real cost is, literally, moths to years of dev efforts to unwind tangled concerns. Million to tens of millions in developer salaries that are going towards keeping the ship afloat as the hull is changed out. The opportunity cost is truly mind blowing.

    To avoid that cost: keep concerns separate, define data domains, and use a language that allows you to keep logic localized. If you have to jump files to understand your incoming parameters, you're gonna have a bad time when things no longer fit in your head, and esp. when new to the code as a new hire.

    Elixir, I still had to know my whole call chain to know what I could do with my incoming parameters. The more call sites, the more mental context. I choose static types because I can KNOW what my function is receiving locally: it is the type signature.

    I would like to validate my experience against other static typed languages like c#; so far, I have seen wins at every org that switched from dynamic languages to Go. Go seems to get a lot right for helping eng orgs move faster.

    ken-kost 2 hours

    > Elixir, I still had to know my whole call chain to know what I could do with my incoming parameters. The more call sites, the more mental context.

    but the call chain doesn't have to be long, i.e. it could be just 2 or 3 places; that fits inside my head. less is more

    sethammons 1 hours

    Sure, but it stops being that with multiple teams stepping in the same codebase as business needs expand. You revisit an area of code to find Sam in the billing department (who you don't know) interjected something or other and now there can be assumptions about the shape of data that were different than before. For us, it was data report shapes.

    Elixir is amazing when the system fits in your head.

    lijok 17 hours

    People no can Rust so people no use Rust. Simple as.

    mountainriver 16 hours

    I’m so happy with switching all my dev over to rust since AI coding. Everyone is lighting fast and super reliable

    josevalim 10 hours

    > Honest question, in the era of vibe and AI assisted coding is there any advantages of using untyped programming languages, apart from the fact that non-typed languages has more traning data for the LLM?

    Author here.

    Type systems restrict which programs can be expressed and increasing expressiveness often requires increasing type-system complexity (which, speaking from experience, both humans and agents will struggle with). Plus they are not the only mechanism to assert correctness (they only validate a subset of your program correctness and do not replace tests) and you are still on your own when it comes to actually recovering from unexpected errors (something Erlang/Elixir were designed for).

    I'd say there are two flip sides to your question:

    1. Given types do not replace tests, if you can use AI to automate full test coverage, are there actual benefits in static typing for coding agents? The downside of tests for humans is that we suck at writing them (but guided agents can do better) and they can take time to run (which agents do not care)

    2. Do we actually have any data or evaluations that show which typing discipline is better for agents? The only benchmark I am aware of [AutoCodeBenchmark] has Elixir come first (dynamic) and C# as second (static), so it doesn't answer the question. There are other benchmarks that show dynamic languages require fewer tokens to solve problems (but that's not a metric I particularly care about)

    My gut feeling is that local structure, documentation, quality and quantity in the training data, etc are likely to play a more important role than typing for coding agents. I'd also love to measure how agents perform on specific domains. If you are writing concurrent software, how does Elixir/Java/Rust/Go compare? But without data, it's hard to say.

    [AutoCodeBenchmark]: https://github.com/Tencent-Hunyuan/AutoCodeBenchmark

    phillmv 2 hours

    >Type systems restrict which programs can be expressed and increasing expressiveness often requires increasing type-system complexity (which, speaking from experience, both humans and agents will struggle with). Plus they are not the only mechanism to assert correctness (they only validate a subset of your program correctness and do not replace tests)

    This articulates a lot of my own thinking wrt type systems, speaking as a downstream user without a lot of exposure to prog language theory, and I wish this debate were more often framed in these terms.

    Another reply to this comment hinted that it might be more about giving LLMs feedback loops and that to me also seems like a more likely mechanism.

    I'm not an elixir user but I've watched it from a distance over the years – thank you for your efforts and your experimentation.

    rspeele 52 minutes

    One thing something like AutoCodeBenchmark cannot demonstrate is what happens when you have human-written type definitions defining the domain before the LLM writes a line of code.

    That is something I have found very effective in F#, that I model the domain with types, I know what the type signatures of the functions I need are, and the LLM does the work of actually implementing those functions.

    Here is a concrete example:

    I have been playing around with a program to assist me with projects I make at home on my hobby-grade CNC router, which does not have an automatic toolchanger. I use a mix of Vectric VCarve and some older handwritten programs to generate GCode files. I end up with a USB drive with maybe 6 to 12 GCode files on it and a model in my head of "to make this product, I start with a board here, gotta install this square nose end mill and zero on this corner of the board, run files A and B. Then install a ball nose end mill and run file C. Then flip the board over lengthwise, switch to a smaller square nose end mill, zero here, run file D. etc. etc."

    Although I try to name the GCode files in a self documenting way like 01_TopSide_25square.ngc, if I come back in 1 year and want to make the same thing again, I pretty much always have to open VCarve and eyeball what the hell all the files did and confirm where to zero, what size board to use, etc. So I'm making a tool where I can define those human-operator steps that go with the G-Code files, save it as a "project file", preview in 3d what each step will look like, and export to a printable PDF with screenshots and step-by-step instructions. Hopefully this will reduce the amount of rot that these projects suffer and the cognitive overhead of picking up an old one.

    Modeling the steps as F# types was the very first step, like (small excerpt):

        type WorkpiecePlacement =
            {   Id : WorkpieceId
                /// Corner of the workpiece we'll attach to the machine.
                WorkpieceCorner : WorkpieceSpace.Corner3D
                /// Point in machine-space we'll anchor this corner to.
                MachinePoint : MachineSpace.Point
                /// Which face of the workpiece is on top.
                FaceUp : WorkpieceSpace.Face
                /// Rotation around the up-axis.
                Yaw : WorkpieceSpace.Yaw
            }
    
        type OperationType =
            | PlaceWorkpiece of placement : Operation.WorkpiecePlacement
            | InstallTool of id : ToolId * slot : int option
            | ZeroAt of point : MachineSpace.Point
            | RunGCode of source : GCode.Source
            | RemoveWorkpiece of id : WorkpieceId
    
    
    For the GCode simulator I needed a parser for GCode files, which produces a type with 1:1 equivalence to the GCode instruction set:

        type GCodeInstruction =
            // --- Motion ---
            | G0_RapidMove of axisMoves : (Axis * float<gcodeunit>) array
            | G1_Move of feedRate : float<gcodeunit/minute> option * axisMoves : (Axis * float<gcodeunit>) array
            | G2_ClockwiseArc of ArcParams
            | G3_CounterClockwiseArc of ArcParams
            | G4_Dwell of seconds : double
    
            // --- Plane selection ---
            | G17_SelectXYPlane
            | G18_SelectXZPlane
            | G19_SelectYZPlane
    
            // --- Unit selection ---
            | G20_Inches
            | G21_Millimeters
    
            // --- Distance mode ---
            | G90_AbsoluteDistance
            | G91_RelativeDistance
            // ... etc truncated, more instructions in real code
    
    
    But my tool supports doing transforms on toolpaths, like rotating 90 degrees or offsetting so I can easily define that I want to make tiling copies of the same project. To implement those transforms straight up as GCodeInstruction[] -> GCodeInstruction[] is a bad call. GCode is very stateful and lets you switch units, relative vs. absolute coordinate spaces, etc. in instructions. That makes the transform awkward and tricky to write.

    So I have a ToolPath type that makes the transforms clean. It normalizes the many ways of expressing the same toolpath in GCode to a single representation with all absolute coordinates in metric units.

        type ToolPathInstruction =
            | Rapid of From : Point * To : Point
            | Linear of From : Point * To : Point * Feed : FeedRate
            | Arc of
                From : Point *
                To : Point *
                Center : Point *
                Plane : Plane *
                Direction : ArcDirection *
                Feed : FeedRate
            | ... etc truncated
    
    That is the appropriate level for the transforms like offset, rotate, scale, etc. to operate on.

    Yet there is still ANOTHER level of toolpath-related operations that deserves its own type. When I'm doing simulation of material removal to check for crashes, or rendering the toolpath in 3d, I don't want to deal with arcs! The rendering/simulation is inherently an approximation. It will break down each arc into line segments. So sim code and rendering code shouldn't take a toolpath, it should take basically a line segment list, or in other words...

        type ApproxMove =
            {   From : Vector3
                To : Vector3
                FeedRate : double<m/minute>
                IsRapid : bool
            }
    
        type ToolPathApproximation =
            {   StartPosition : Vector3
                Moves : ApproxMove[]
            }
    
    Having defined all these types it's clear that I need operations like:

        parse: string -> GCode
        serialize : GCode -> string
        normalizeToToolPath : GCode -> ToolPath
        denormalizeToGCode : ToolPath -> GCode
        offset : Vector3 -> ToolPath -> ToolPath
        rotate90 : ToolPath -> ToolPath
        scale : Vector3 -> ToolPath -> ToolPath
        approximate : ToolPath -> ToolPathApproximation
        simulate : ToolPathApproximation -> MachineState -> MachineState
        renderToolPathWireframe : ToolPathApproximation -> VBO
        renderMachineState : MachineState -> VBO
    
    And so on. An LLM is absolutely awesome at one-shotting the implementations.

    I would find it quite frustrating trying to model the same domain without any types, either having all methods working on a single toolpathy data structure that's not really the right fit for any of the places it's used, or having them work on multiple data structures without any clear delineation of which layer is expecting which toolpathy-thing that are all subtly but importantly different.

    teleforce 2 hours

    >Type systems restrict which programs can be expressed and increasing expressiveness often requires increasing type-system complexity (which, speaking from experience, both humans and agents will struggle with).

    I used to hold similar opinion but D language, and this article by Patrick Li (HN JITX co-founder) who's the original author of little known but very powerful language Stanza changed my mind [1],[2].

    He argued that Ruby has enabled a very expressive language that enabled RoR, and when it was originally written other languages are less capable, and accordingly the proof is in the pudding.

    In his new language Stanza for his PhD thesis he has designed an optional typed system supporting both typed and untyped, it seems very similar in concept to the OP article that you've written on Elixir. Groovy also deserved a special mention, and the pudding is Grails.

    Interestingly both Elixir and Stanza have GC, but Stanza also support non-GC namely LoStanza in which Stanza GC is written.

    Interestingly, D language pioneered this combination both GC (by default) and non-GC more seamlessly, even before Stanza.

    In addition to Ruby, these four languages namely Elixir, Groovy, Stanza and D all have similar to or better expressive power than Ruby. Notably both Stanza and D are compiled languages. Above all D is an anomaly in a good way since it's a fully type programming language. Kudos to Walter and the team for giving birth to a highly expressive fully typed modern language, very fast in compilation and runtime, truly one of a kind [3].

    Regarding the issue of comparatively smaller corpus for these languages as mentioned by others, I think the new self-distillation technique for LLM and code generation as proposed by Apple, MIT-ETH and UCLA can overcome this limitation [4].

    [1] “Stop Designing Languages. Write Libraries Instead” (2016) (278 comments):

    https://news.ycombinator.com/item?id=46525640

    [2] Stanza: People:

    https://lbstanza.org/people.html

    [3] Origins of the D programming language:

    https://dl.acm.org/doi/10.1145/3386323

    [4] Embarrassingly simple self-distillation improves code generation (201 comments):

    https://news.ycombinator.com/item?id=47637757

    kamma4434 38 minutes

    > Groovy also deserved a special mention, and the pudding is Grails.

    Not sure it is much of a success. Groovy gets unreadable very fast, and the editor won’t help you. Gradle moved to Kotlin, and it’s 10x better in readability and maintainability.

    agentgt 1 hours

    > Groovy also deserved a special mention, and the pudding is Grails.

    I vaguely remember that when Groovy became more typed (statically typed that is. I believe you could always put the types in but they were not checked.) there was a theory that it kind of hurt possible uptake of the language.

    The reason being is that people felt well if we are adding types and a project is requiring it why don't we just use: Java, Scala, Kotlin etc. Like did Java getting more features or Kotlin coming really hurt Groovy or just that it became more of a typed language.

    An analog (typed language stealing users) could happen to Elixer but I'm not really sure which language it would be.

    > I think the new self-distillation technique for LLM and code generation as proposed by Apple

    Speaking of Apple and eventual typing Dylan was an amazing language that just never got traction. Open Dylan still exists but few know about it. Its eventual typing is unique because Dylan does CLOS-like multimethod dispatch instead of pattern matching.

    osener 8 hours

    In my experience restricting programs that can be expressed is a good thing, even more so with agentic engineering. The more guardrails there are, strong typing/TDD/computer use/..., the solution space shrinks and chance of a robust solution increases. Sure maybe this burns more tokens going in circles but it feels less like a slot machine more like a robot searching for a solution for a well-defined problem.

    Devs have very strong opinions about dynamically typed programming languages. But reasons such as "exploratory programming", "expressiveness", "taste" that makes them feel good to program in for humans does not matter for agents. Agents don't care that the language "limits them" and prevents them from expressing the code in a succint way because it would not type check.

    josevalim 8 hours

    Agreed on the guardrails bit. My point is that we still don't have much evidence that static types are an effective way to constrain the search space for coding agents, or how much value they add on top of other mechanisms. Redundancy can certainly be beneficial, but how much and at what cost?

    On expressiveness, people often frame it as a dynamic-language goal, but a large portion of type system research is precisely about making type systems more expressive so they can describe a wider range of programs and invariants. This is clearly something both camps value. I suppose another interesting benchmark could be: how do coding agents perform across languages with different degrees of type-system expressiveness?

    We may directionally agree, but it is hard to draw conclusions without measurements. Overall, I'd say this is much more of an open question than people give it credit for.

    giraffe_lady 3 hours

    > 2. Do we actually have any data or evaluations that show which typing discipline is better for agents? The only benchmark I am aware of [AutoCodeBenchmark] has Elixir come first (dynamic) and C# as second (static), so it doesn't answer the question. There are other benchmarks that show dynamic languages require fewer tokens to solve problems (but that's not a metric I particularly care about)

    I am actually writing a paper on this right now so nothing I can point you to yet but yes. LLMs are better (produce working code in fewer attempts controlling for the relative size of training corpus) when using type systems with inference and global unification. It is largely about the quality of the error feedback channel so languages with very good compiler errors (accurate, localized, include the correction with the failure) can close a lot of ground.

    But inference + sound type system gives you a constraint propagation that genuinely restricts the ability of the LLM to get into trouble. Type systems that require annotation give up most of the benefit, since the annotations are themselves surface area for LLM mistakes. Unification also puts heavy limits on the expressiveness of the language which is a confounder and may actually be a big part of the benefit too.

    Everyone has been on the "the training data is better" thing but I actually don't think so. All of the languages that people report as being better because of good training data actually have fairly restrictive type systems. Elixir is an exception, but it has exceptionally good error messages! And also, along with erlang, pretty unique runtime semantics that may contribute but that's outside my domain I'm on type systems. Debunking the training quality thing is not what I'm working on but I have deep suspicions about that common wisdom.

    josevalim 3 hours

    That’s very exciting! Is there anywhere I could follow you for updates? If you don’t want to share it publicly, and is ok with sharing it privately, my email is my username on gmail. Thank you!!

    giraffe_lady 2 hours

    We're hoping to have a preprint ready at the end of the month, I'll send it to you then!

    josevalim 1 hours

    Lovely!!!

    elitehacker1337 15 hours

    I’ve been using Ruby and Elixir for over a decade. Pre-AI I used them for aesthetic reasons. The code was beautiful, and I disliked dealing with types.

    People without experience in dynamic languages tend to overestimate the number of bugs their type system is saving them from. It’s pretty rare that I run into a bug in production that a type system would have caught.

    They also overstate how much types help their AI agents write code. I haven’t seen AI write a type related bug in years at this point.

    I work with typescript on the front end, and my experience is totally different there. AI is constantly introducing type errors, but only because the original type wasn’t declared properly. Agents waste a ton of time and tokens appeasing typescript. Ruby and Elixir are very token efficient in comparison.

    That said, now that I am not writing code by hand anymore, I am considering switching to something like Go. Mainly so I can run my side projects on smaller machines

    epolanski 5 hours

    C is among the most token efficient languages out there and is statically typed.

    Typescript is very verbose thus it cannot compete with much denser languages on token efficiency.

    By the way, the biggest reason many love statically typed languages, especially those that are quite expressive like TypeScript is for the domain and data modelling. Makes it easier to reason about the program and to refactor.

    DoesntMatter22 15 hours

    I tried doing my side projects in Go and one thing I miss is the rails console which is so helpful. I guess I could have it write a go console or something but it’s not quite the same

    solumunus 10 hours

    > It’s pretty rare that I run into a bug in production that a type system would have caught.

    Well yes, surely because you’re not designing your system around the type system. You need to architect your project to lean heavily on types, pattern matching, etc to actually gain the benefits. If you move a JS project to TS and just rename the files, yeah there’s going to be no difference, you must reengineer the entire codebase to leverage the type system.

    Personally, after moving to TS I’ve been completely sold on types and am currently planning to migrate my app to F# so I can gain even more benefit.

    bbkane 5 hours

    Go is pretty great - here's a list of tools I use to help me write/build Go- maybe a few of them will also be what you need: https://www.bbkane.com/blog/go-project-notes/

    williamdclt 4 hours

    > It’s pretty rare that I run into a bug in production that a type system would have caught.

    Wow, how different our experiences are. In Javascript/Typescript land, so so many bugs are null/undefined-related and really should have been caught at type level.

    In fact, I'd say (without actually measuring it) that _most_ bugs I've ran into in Typescript are due to someone having bypassed the typing (casting, ts-ignore...), or a type mismatch at IO boundary.

    phillmv 2 hours

    javascript is like… unusually messy and weird, so maybe that colours most people's perspective. you don't have to worry about type coercion and weird kinds of equality and so on in python and ruby to anywhere near the same extent.

    bcrosby95 1 hours

    Not all dynamically typed systems are equal. Just like not all statically typed systems are equal. Python and Javascript are a mess. But languages like Elixir aren't just Java without types.

    scythmic_waves 2 hours

    Anecdotally, it is very much different in Elixir land. I occasionally see bugs related to something being unexpectedly `nil` but it's pretty rare IME.

    I'd love to evidence what I'm saying with specific numbers since this kind of discussion would benefit from being as objective as possible. Sadly I don't have them. But I still believe what I'm saying and I have a few guesses about some of the causes:

    1. Immutable data - so, so many bugs are caused by data mutating out from under you in subtle ways. If you write `x = 1` in your Elixir function, nothing can change the value of `x` except an explicit rebinding. You can then write e.g. `y = f(x)` and know `x` remains unchanged after. Note: this is also true even if the variable is a composite type. `my_struct = blah()` will remain the same in it's entirety no matter what you do with `my_struct`. This is different than in JS where e.g. you can change the contents of an object even if it's declared `const`.

    2. Assertive style - the Elixir community favors writing things in an "assertive" fashion [1]. Briefly, this a way of writing code that will fail the moment an assumption is broken rather than letting the issue propagate.

    3. Pattern matching (somewhat like destructuring in JS) - Elixir code actually ends up feeling "typed" with pattern matching. E.g. `%Time{} = today = Date.utc_today()` will attempt to bind `today` to the result of `Date.utc_today()` and will raise a `MatchError` when the result, a `%Date{}` struct, fails to be a `%Time{}` struct. Or `[a, b] = [1, 2, 3]` will raise a `MatchError` because `[1, 2, 3]` isn't a list of length exactly 2. You can use pattern matching to write very assertive code quite tersely.

    These reasons are all local properties of code. But when all its parts are written in this way, a program as a whole gains a level of correctness that's hard to achieve in a dynamically typed language without them.

    Also these reasons aren't exhaustive, but they're top of mind when thinking about this topic.

    [1]: https://dashbit.co/blog/writing-assertive-code-with-elixir

    meghprkh 17 hours

    Not necessarily. Since the word "typed" language is not well-defined.

    For example, typescript is a fantastic language for marshalling data and UI state since it uses substructural typing instead of nominal typing. Libraries like kysely / other ORM libraries are great examples too and easy to use, whereas in fully typed languages like Rust you would end up having to use a macro library like sqlx or having to define structs for each of your types (which would increase compile time & size)

    DonaldPShimoda 15 hours

    > Not necessarily. Since the word "typed" language is not well-defined.

    This depends entirely on context. In the Benjamin C. Pierce school of thought (a common choice in programming langauges research; see his book Types and Programming Languages, 2002), "typed" means what we typically call statically typed, i.e., the language employs a static analysis to prevent the compilation/execution of (some subset of) faulty programs. Meanwhile, languages that are commonly called "dynamically typed" are, in this school of thought, not typed (or "untyped"). (TAPL provides a more rigorous definition, but it's in the other room and I am lazy.)

    galaxyLogic 16 hours

    As I understand it TypeScript does not enforce types at runtime. Am I correct? If so that would signify to me it is not a "typed language", like say Java for instance. Types in TypeScript are more like "annotations" for docujmenting the program. Am I correct?

    whstl 8 hours

    Lots (most?) of statically typed languages also do near zero runtime checking.

    They naturally use types for compilation, but the type system is trusted to forbid some invalid states. Underneath it’s all bits and bytes.

    Even in safe languages you need deserializers/parsers/validators.

    Typescript actually ends up having more checks because it runs Javascript underneath (although some might argue those barely count).

    tancop 9 hours

    its more than annotations, your code fails to compile when you get a type error at least with strict settings. if it type checks and fails at runtime that means youre missing input validation or using bad declarations for third party/legacy untyped code. or using some escape hatch like `myValue as unknown as MyType` in the wrong way.

    abustamam 15 hours

    I have never worked in Java. But you can certainly ship TypeScript code that does not pass typecheck and it'll run fine in the browser because the browser runs Javascript, not typescript. Obviously a decent build process will prevent code that fails typecheck from shipping, but that's not a language feature.

    For runtime types I've leaned on Zod or Effect schema,which can also generate static types for you.

    dnautics 16 hours

    its statically typed, but not runtime typed!

    bluepnume 12 hours

    [dead]

    replwoacause 17 hours

    I've used untyped languages extensively, and even built my own, and the errors I get at runtime are almost never type-based, and that's even more true now that LLMs can pump out code. For all the additional ceremony types add, I can't say I've personally realized their benefit.

    abustamam 15 hours

    I've been working with typescript for the past ten or so years.

    A couple of years ago I did some contract work for a client who used Javascript.

    I did some basic smoke testing to understand the state of the app and I was able to get lots of fun type errors on the server and client at runtime just by QAing the damn thing.

    aeonfox 16 hours

    I've definitely found LLM code to be syntactically/semantically correct in one-shot pretty much all the time. It's usually the functional specification/behaviour that's found wanting.

    Typing probably makes sense where memory-correctness needs to be enforced (e.g. Rust), and inferring those semantics require a much wider context. But memory-correctness isn't really something that afflicts BEAM languages.

    dnautics 16 hours

    when i was programming elixir by hand i was making typing errors about 1 every half year or so. none took production down, most were caught and corrected quickly from logs. now im doing mostly llm elixir, almost all typing errors are caught in integration tests and only one has made it to prod.

    citizenpaul 16 hours

    I thought a big part of the reason for type systems was a sort of self documentation/contract? Especially if you need to work on an unfamiliar system with bad documentation. Also what about system boundaries? I prefer typed languages personally.

    galaxyLogic 16 hours

    The benefit is not only about "documenting the contracts" but documenting the contracts in a way that we can trust those contracts can not be violated when the program is running.

    That is a very good thing to help us reason about the program, we have invariants we know must hold true if the program does not stop in a type-error.

    kensai 17 hours

    This reminds me of the analogy of the smoking grandpa. I had a grandpa that was chainsmoking his whole life and managed to reach 90 and died of other causes. This does not mean smoking is "relatively safe".

    waffletower 2 hours

    This analogy is absolutely absurd and inappropriate on multiple levels. It reflects a parochial mindset that can't fathom contexts where dynamic type systems can be advantageous, such as a breadth of data processing applications. My favorite irony is how dynamic languages excel at concise translation between opposing type systems -- a very common data processing scenario.

    kensai 51 minutes

    You expand my comment to uses I had not envisioned. It was simply an analogy to correct the analogy of the OP comment.

    rspeele 16 hours

    > the errors I get at runtime are almost never type-based

    That surprises me, but everyone's experiences are different. I've been in the statically typed language space for so long and enjoyed it so much, I find it pretty irritating to go back to Python (my long-ago favorite) but many people are in the exact opposite frame of mind. I'm curious: what kinds of errors do you classify as a type-based error? I think that varies from person to person.

    For example, null references. A C programmer would say dereferencing a null is not a type-based error, because it's not feasible to encode non-nullable pointers in the C type system. A Haskell programmer would say it is a type-based error because Haskell makes it difficult not to encode this in the type system, you really have to go out of your way to create a runtime null dereference error.

    A C# or TypeScript programmer could answer differently depending on who you ask, because both of those languages make it possible to leverage the typechecker to prevent null-deref at compile time, but neither one makes it required (you can turn those checks off or make them warnings if you like), so it depends on the programmer's build settings and how much typechecking they personally have chosen to use.

    Paracompact 14 hours

    > I find it pretty irritating to go back to Python (my long-ago favorite) but many people are in the exact opposite frame of mind.

    As someone who works exclusively in typed languages for formal methods, what is it you find lacking about modern Python + PyLance? IMO there's still a tiny verbosity issue, and there's no real replacement for fancier polymorphism or (G)ADTs, but I'm very satisfied with it for most things. In particular, null checks are trivial.

    rspeele 13 hours

    It has been about 10 years since Python was a daily driver for me and at that time I wrote it the old fashioned way with no type hints and no static checking, just like grandma used to make. The times I have needed to dig back into it have involved working on old code, so I haven't kept up with modern tooling.

    However, in principle any dynamically typed language can be tolerable to me if it can be turned into a statically typed language ;)

    But I think I'd still prefer the ergonomics of a language designed that way from the start vs having bolt-ons. My favorite language for the past several years has been F# and I think ML-family languages in general strike a great balance of being able to write terse code when you want to, and being able to model a domain really well with types when you want to.

    asa400 16 hours

    This framing is misleading. I'm not sure what AI has to do with any of the examples you cited. All of the examples you cited are moves - and in some cases, not even moves, as Shopify is not ditching Ruby - to more performant runtimes and architectures in response to operational concerns at scale, which have a tenuous link to language, and no link to AI that I can see, as these companies all significantly predate LLMs.

    Ruby's runtime in the early 2000's compared poorly against the JVM or the BEAM. People used Ruby then and now because it worked well to get products to market quickly. Even after a ton of investment in Ruby's implementation, the JVM and the BEAM are still better able to handle the types of high-traffic, high-concurrency workloads those companies serve, which makes them relevant to mature, high-scale companies.

    Tellingly, there are dynamic language implementations that are performance-competitive with static language implementations, like Javascript's V8/Bun/Deno, Lua's LuaJIT, and Common Lisp's SBCL (among others, this is not an exclusive list).

    mixolydianagain 15 hours

    > to more performant runtimes and architectures in response to operational concerns at scale, which have a tenuous link to language

    The runtime performance and the language are deeply linked. None of the dynamically typed runtimes you mention are actually performance competitive with JVM languages.

    asa400 13 hours

    They absolutely are. Maybe not if the only thing you’re benchmarking is something completely CPU bound like signal processing/math, but they’re definitely competitive for tons of real use cases.

    DoesntMatter22 15 hours

    They don’t really need to be though. They take far fewer tokens and are still faster to develop with

    shakna 11 hours

    Luajit and SBCL are very much performance competitive with the JVM? Why do you say that they're not?

    Random example benchmark: https://madnight.github.io/benchmarksgame/lisp.html

    igouy 19 minutes

    That's someone's 8 year old mirror of the benchmarks game website.

    https://benchmarksgame-team.pages.debian.net/benchmarksgame/...

  • yeetosaurusrex 16 hours

    I wanted to use functional programming in actual projects and Elixir's lack of static types almost stopped me from picking it up initially.

    I tried it out and, although I do miss static types sometimes, immutability and not having to deal with inheritance and other OO abstractions has made the trade-off worth it for me.

    Yes some people do claim that pattern matching makes up for the lack of static types. I don't agree with that, but can say that anecdotally the number of type related bugs I notice in *my* Elixir code is much lower than the number of similar bugs I used to write in languages like Python. Whether that's because of common usage of pattern matching, or community adherence to patterns like returning tuples of {:ok, result} | {:error, error}, or something else is anyone's guess.

    An important point not in the heading is that gradual typing has been added without any new language syntax.

    It's still not statically typed. Maybe it never will be, but this is a step in the right direction and at least they're trying.

    jlouis 28 minutes

    Statically typing the underlying message passing model used in Erlang is pretty hard, because the mailbox of a process can accept any type of message. And so, it cannot be statically typed in general, since anyone who holds a process id can shove a message into that mailbox.

    In contrast, Go's message passing model works on typed channels. A channel has a type, and only accepts messages of the given type. The `receive` operator then acts as the merging data flow which solves the problem of receiving messages of different types. This is a design which amends itself far better to static typing.

    Pattern matching isn't a substitute for static typing at all. The two features are entirely orthogonal indeed, and you definitely want static typing and pattern matching at the same time.

    9 hours

    lo_zamoyski 16 hours

    You might find Gleam[0] a better fit.

    [0] https://gleam.run/

    __turbobrew__ 2 hours

    I may be wrong, but last time I checked there was not a statically typed OTP implementation which is kindof a bummer. I think Gleam is the ideal implementation on top of the BEAM but it does just seem pretty immature.

    veqq 15 hours

    If you're only willing to use languages with the same features, what's the point? Learning how a different paradigm manages without types can be more insightful.

    3836293648 14 hours

    Because the BEAM has much more to it than a terrible dynamic type system?

    yeetosaurusrex 15 hours

    Yeah I agree learning new paradigms can give you new insights.

    There's also a balance between learning new languages for fun and for the insights they give, and wanting to ship.

    As an example: Prolog was mind-bending for me when I tried it and I had a lot of fun with it, but I can't imagine using it to build a product (I'm sure other people have though).

    Perhaps my first comment sounded more critical than intended. I'm really excited to see where this initiative with set-theoretic types goes, and if it leads to a fully statically typed language then that will be a bonus. If that doesn't happen, then I'm still perfectly happy with the language as it is.

    Elixir taught me that I don't need static types as much as I thought.

    gavinray 4 hours

    I'll also make the argument that type systems in languages are purely additive rather than orthogonal.

    What I mean by that is, I used to write JS. Transitioning to TypeScript didn't alter my mental model of the language.

    Likewise for Python with type annotations.

    The only time I've had that happen is with Scala 3's dependent types/type lambdas, but thats LITERALLY called "type-level programming", so it makes sense.

    waffletower 2 hours

    I wonder if it should read "Elixir taught me that I don't need static types as much as my professor taught"?

    Kaliboy 4 hours

    I finally found uses for Prolog haha. For years I would have been able to write exactly your comment.

    One use is a spellcheck. Though some bits are in Rust cause backtracking would be too slow.

    Another is a game I'm making, the server is in Elixir, and I use erlog to basically program the NPCs in prolog. The game generates events and they are processed into facts if they are perceived by the character.

    And with that I can have the system generate goals based on stuff like "I havent seen X at the market for 3 days whilst beforehand I saw X every day. Let me go check on X."

    I didn't know Erlang started as a Prolog program basically, but it shows cause they fit together like a match made in heaven.

    neya 12 hours

    If you use Phoenix, using types at the data model level using changesets and then trickling them down all the way to the UI is a very good compromise. As changesets provide type validations out of the box too.

    steve_adams_86 12 hours

    Do changesets incur a runtime cost?

    sph 8 hours

    Not sure what you mean here. Changesets are used to validate user input before interaction with business logic or your database; of course data validation has a runtime cost, in any language.

    Please don't use changesets to enforce some kind of type system between system components. In case you do not trust your own code, Elixir is strongly typed (though not static typed), there are test cases, there's dialyxir and if still you cannot stop yourself from passing a number where a string will do, the process will crash, log a message for you to fix the bug, and get restarted by a supervisor.

    I get why people are obsessed with static typing on "normal" languages, where bugs cause system downtime, but the Erlang platform gives you so many guarantees that even if you somehow make a mistake, it is never catastrophic. Gradual typing in Elixir is a nice cherry on top of the runtime, not the cornerstone to robust OTP software.

    cultofmetatron 5 hours

    yes, they do. its minimal though

    asa400 11 hours

    Ecto Changesets[0] are runtime constructs, yes. They're similar to libaries like Pydantic, if you're familiar with Python.

    [0] - https://ecto.hexdocs.pm/Ecto.Changeset.html

    steve_adams_86 11 hours

    Yes, this is exactly what I was wondering, thanks. Another version of this that I love is Effect Schema in TypeScript land.

    The runtime costs aren’t trivial, especially on large datasets, but I’ve come to love this pattern a lot.

    nesarkvechnep 6 hours

    Yeah, one of the worst practices. I've been working with Elixir professionally for 6 years now and I still see this sh*t everywhere. Bad APIs, bad UIs because someone coupled themselves to the database structure and can't escape. List of memberships? Keep them as a list with the same fields as the junction table. Top-level APIs taking maps with string keys as "params" so they can very easily be cast for a changeset.

    neya 5 hours

    This was the only out of box solution when Elixir didn't support types. So, if you really did Elixir professionally for 6 years, you'd know that by now.

    > Bad APIs, bad UIs because someone coupled themselves to the database structure and can't escape.

    If you don't commit yourself to the database structures you defined at the time of application creation, then it just reflects poor planning and architecture overall as that is one of the very first things you do.

    What you describe is an approach a lot of NoSQL fans use - use whatever works then, worry about datatypes later on. That's how you shoot yourself in the foot.

    > List of memberships? Keep them as a list with the same fields

    Again, using embeds_many or has_many works well too, using changesets - which is my point exactly. Not sure where the disagreement is here.

    Your account is full of just ragebait comments at a quick glance, so I'm just going to leave it here.

    bcrosby95 1 hours

    Sometimes types are worse than the alternative.

    I obviously don't know your specific use case, but in my experience having the database schema reflect throughout a project means its either very small or the design is going to run into problems.

    It also sounds like a potential security nightmare. We have a policy of never sending domain objects across the wire so nothing accidentally gets sent. APIs must strictly whitelist data structures.

    The way this can work in something like an Elixir or Clojure: you have gradual types in most of the core code, but you translate it just before you hit the view layer (e.g. templates).

    The great thing about dynamically typed languages is you don't have to declare a new type for each view. You just select out the data you need and expose it for the view. In Clojure this is as simple as a select-keys.

    SuddsMcDuff 4 hours

    > If you don't commit yourself to the database structures you defined at the time of application creation, then it just reflects poor planning

    No it reflects the reality that requirements and applications evolve over time. You sound like someone who's never supported an application for more than 5 minutes.

    neya 2 hours

    > You sound like someone who's never supported an application for more than 5 minutes.

    If your application requirements change every 5 minutes, then you prove my point - you suck at architecting and should honestly just give your job away to someone more competent.

    bbkane 5 hours

    I haven't used Elixer but tt's generally a good idea for the UI to have a different data model than the database (even if it means you initially type almost the same thing twice and have to write a tedious translation layer).

    This lets you evolve each part independently and use the "native" types frontend vs backend, which happens surprisingly frequently as the app grows

    Kaliboy 5 hours

    Phoenix does have that. ViewModels. I don't think its required to use though, but we always do.

    neya 2 hours

    > but tt's generally a good idea for the UI to have a different data model than the database

    You're not wrong and most other comments are responding this from some sort of UI library perspective, like React / Svelte. However, if you're using even the barebones scaffolded UI using LiveViews from Phoenix, you don't have to do any of these. Phoenix will wire up the form to the changesets by default. Which is what I'm referring to.

    nesarkvechnep 3 hours

    The disagreement is on Ecto schemas used to represent databases tables from the persistence layer to the UI. Of course, use changesets to normalise user input but using the same schemas everywhere is a sign of immaturity as a developer. You really sound like someone who only does CRUD services. Real world is often more complex.

    neya 2 hours

    > Real world is often more complex.

    Which is why you architect before-hand with a paradigm of your choice, like DDD (Domain Driven Design) using proper contexts (which Phoenix supports) beforehand. That is the sign of a mature developer, not the other way around.

    If your datatype for a column evolves over time to completely different types, it's just an excuse for poor planning and architecture. Eg. A string turning into an integer. That just sounds like someone junior would do with MongoDb.

    > You really sound like someone who only does CRUD services.

    You throw this like an insult, but in reality most applications can be simplified to just CRUD services. Chat interfaces? CRUD. Social Media? CRUD. Banking? CRUD.

  • satvikpendem 21 hours

    How does it compare to Gleam? Or rather, why use Elixir over Gleam now? I suppose Phoenix and Live View in particular are big draws to Elixir.

    flexagoon 5 hours

    They're different languages with different syntax and different features. Them using tge same VM doesn't really make them competing products, just like Java, Scala, and Clojure all use JVM and yet are all different languages

    OtomotO 19 hours

    Phoenix and Ecto, really.

    asib 21 hours

    Do you like Rust or do you like Erlang? Writing Gleam is like writing Rust, writing Elixir is like writing Erlang.

    I don't know the current state of Gleam OTP, but last I checked it wasn't great.

    If you don't care about either of those things and only about types, use Gleam. But then why not just use Rust?

    lpil 19 hours

    Hi, I'm the lead maintainer of Gleam.

    > I don't know the current state of Gleam OTP, but last I checked it wasn't great.

    Gleam uses regular OTP, it doesn't have a distinct OTP framework separate from other BEAM languages.

    michaelcampbell 2 hours

    > But then why not just use Rust?

    The BEAM?

    satvikpendem 21 hours

    Your last sentence is basically where I'm at, writing my backends in Rust these days. I'm interested in the BEAM promise of letting things crash but not sure how good that is in Gleam due to its OTP still being somewhat immature as the devs are rewriting GenServer as a typed library.

    lpil 19 hours

    Hello! I'm the maintainer of Gleam. We are not rewriting OTP, regular OTP is used in Gleam. Most commonly the typed Gleam APIs for OTP are used, but you can use the untyped Erlang APIs if you wish.

    This is the same as in Elixir, where macro-enabled APIs are offered, and they just wrap the regular Erlang APIs.

    shevy-java 20 hours

    > writing Elixir is like writing Erlang

    I wrote both Elixir and Erlang code. Erlang is just useless to me as a programming language; it has many great ideas though. I love the idea of being able to think in terms of immortal, re-usable, safe objects (Erlang does not call these objects, but to me this is OOP by Alan Kay's definition. I don't use e. g. the java definition for OOP.)

    Elixir built on that and made Erlang code optional, meaning people could write more pleasent code. And here it succeeded. I am not sure why Elixir succumbed to type madness now, but the comment that "writing Elixir is like writing Erlang", is just simply not true.

    Elixir is significantly better than Erlang with regard to writing code. José Valim got inspiration for Elixir from ruby, to some extent.

    satvikpendem 20 hours

    Why do you find Erlang useless, you just don't like the syntax?

    asib 20 hours

    You're taking my comment way too literally. I'm basically just making a syntax comparison. Obviously Rust is not at all like Gleam in many ways either. It's just statically typed and has a similar syntax.

    senderista 20 hours

    I agree that actor languages are the purest form of OOP as Alan Kay has expressed it. And unlike Smalltalk, Erlang just accepts that some things are naturally functions, not messages.

    klibertp 20 hours

    Smalltalk has no problem at all with accepting that some things are naturally functions: it has always had blocks! The call operator is `value`, not `()`, but it's the same "apply a piece of code to some values" operation.

    Rendello 20 hours

    Erlang's Joe Armstrong and Alan Kay did a talk/interview together:

    https://www.youtube.com/watch?v=fhOHn9TClXY

    lawn 20 hours

    Gleam doesn't have macros, which many Elixir libraries (such as Phoenix and Ecto) uses to great effect.

    Gleam for example has issues with verbosity of decoding/encoding json whereas in Rust you derive serde and in Elixir it's just a function call away.

    Elixir has a more mature ecosystem. While you can for example use Phoenix with Gleam (or some other Gleam framework) the experience just isn't the same.

    The big draw with Gleam over Elixir is the typing (where Elixir is now closing the gap) and being able to compile to JavaScript (which is also what Hologram is doing for Elixir).

    I prefer Gleam's typing system and the Rust-like syntax, but for now I feel Elixir is the better choice for all my web dev projects.

    rapnie 19 hours

    > and being able to compile to JavaScript

    Apparently it is not that difficult to add different compiler backends. There was a presentation [0] recently about adding wasm support as a compiler target. The implementation was quite far along, including support for the wasm component model.

    [0] https://www.youtube.com/watch?v=UQ0--ODjiDk

    _danielle_ 5 hours

    Just to add a slight correction here, support for the wasm component model wasn't added in the target I wrote

    rapnie 5 hours

    That was a misunderstanding then, thank you for correcting.

    josefrichter 20 hours

    Check Gleam website, they have the comparison right there.

    josevalim 19 hours

    Last I checked there were inacuracies. I am not sure if they have been addressed!

    lpil 19 hours

    What were the inaccuracies? I'm not aware of any, but we can fix any that are found right away.

    abrookewood 17 hours

    There are two possible locations for comparison that I can see:

    https://gleam.run/frequently-asked-questions/#Elixir Here’s a non-exhaustive list of differences:

        Elixir is gradually typed, while Gleam is fully statically typed.
        Elixir's type system does not have generics, while Gleam's type system does.
        Elixir has a powerful macro system, Gleam has no metaprogramming features.
        Elixir’s compiler is written in Erlang and Elixir, Gleam’s is written in Rust.
        Gleam has a more traditional C family style syntax.
        Elixir has a namespace for module functions and another for variables, Gleam has one unified namespace (so there’s no special fun.() syntax).
        Gleam standard library is distributed as Hex packages, which makes interoperability with other BEAM languages easier.
        Elixir is a larger language, featuring numerous language features not present in Gleam.
        Elixir has an official test framework with excellent support for concurrency, partitioning, parameterized tests, integrated error reports, and more. Gleam has no official test framework, but there are multiple community-maintained frameworks.
        Both languages compile to Erlang but Elixir compiles to Erlang abstract format, while Gleam compiles to Erlang source. Gleam can also compile to JavaScript.
        Elixir has superior BEAM runtime integration, featuring accurate stack traces and full support for tools such as code coverage, profiling, and more. Gleam’s support is much weaker due to going via Erlang source, resulting in less accurate line numbers with these tools.
        Elixir and Gleam both use Erlang's OTP framework. Both have additional modules for working with OTP, which provide APIs more in the style of each respective language. Both common use Erlang's OTP APIs directly, but Elixir can do so more conveniently and concisely due to having a less-strict type system.
        Elixir currently has superior deployment tooling, including support for OTP releases and OTP umbrella applications.
        Gleam’s editor tooling is superior due to having a more mature official language server, but Elixir has recently announced an official language server project which is in active development.
        Elixir is more mature than Gleam and has a much larger ecosystem.
        Gleam and Elixir compile at similar speeds due to using the Erlang compiler as their compiler backend. Elixir's macros are evaluated at compile time, so a program that uses macros will take longer to compile the larger the amount of work performed in macros. Gleam has no language features that result in slower compilation.
    
    https://gleam.run/cheatsheets/gleam-for-elixir-users/ This has to much content to reproduce.

    lpil 1 hours

    We should really remove the cheatsheets, they have not been maintained in many years.

  • alprado50 19 hours

    Maybe it is only my experience, but i feel that languages that were not typed since the begining never work as well as "true" typed ones.

    deterministic 16 hours

    Typescript is brilliant and should be carefully studied by anybody introducing a type system to a single typed ("untyped") language.

    malmz 18 hours

    I think Elixir is taking a very mature path to typing. No type-annotations (yet) just type inference from existing language constructs like function guards and pattern matching. Also trying to minimize false positives, only giving type errors when it would provably crash at runtime.

    dematz 19 hours

    I agree with you but an alternative view, "Why are gradual static types so great?" https://www.benkuhn.net/gradual/

    hocuspocus 18 hours

    Pretty weak argument as most points aren't inherent to gradual typing at all.

    sodapopcan 18 hours

    Elixir's heavy reliance on pattern matching has always made it kind of "dynamic language where you still have to think about types" vibe to it. It's also always had a spec meta-language (taken from Erlang) which a lot of people use. You should read up on how they have been implementing the type system, it's pretty interesting! I would not say it's "bolted on." It also has full inference so all codebases get the benefit of it whether you specify types or not.

    PapstJL4U 7 hours

    Yes, it is what I found works so well. It is easy to write short, specific functions in Elixir and adding Typespecs to theses functions is like typing a block of code. Within the functions everything is "easily" understandable.

    Input > Enumerable.Map(Input, type-speccd functionA) > Enumerable.Map(Input, type-speccd functionB)

    _s_a_m_ 18 hours

    So JavaScript didn't work well and is successful?

    arcanemachiner 18 hours

    Well, JavaScript isn't a typed language, so the answer to your question can't even be "no".

    bayesnet 18 hours

    To be fair I think the success of JS is in spite of it not working super well

    frollogaston 17 hours

    JS was designed well. Got a lot of things right that others copied later, and also made improvements without breaking compatibility. And the random weird things like [] == 0 don't come up much in actual usage.

    frollogaston 18 hours

    It was poorly bolted on in Python. Well I dislike types to begin with, but aside from that, Typescript somehow did it better.

    Waterluvian 18 hours

    > Typescript somehow did it better

    I don’t think JavaScript’s syntax was ever designed with the idea that TypeScript would one day exist. Yet somehow it feels like it left the perfect open spaces for TS to later occupy.

    frollogaston 17 hours

    They did get lucky with that. The Python type syntax ended up being similar, but the implementation of type-checking is confusing, also it was annoying how you needed to import the types of basic collections for a while.

    jamwise 19 hours

    I've experienced this, but it's mostly because languages like Python and TypeScript give you way too many escape hatches. I get the intent: allow devs to convert their code base slowly. But in practice it just lets developers opt out of the benefits of typing to "save time" in the short run.

    LudwigNagasena 18 hours

    I’ve never had a real problem with developers opting out. It’s not that hard to enforce coding standards.

    The real problem with Python is the inexpressiveness of its type system and the mess of typed dicts, dataclasses and pydantic classes.

    TypeScript may fail narrowing here and there or require a superfluous assert, but usually writing properly typed code, especially with zod, is the path of least resistance.

    rezonant 18 hours

    Once you are squarely in a Typescript program and not a "Javascript program gradually adopting Typescript", it would be a good idea to enable Strict mode which forbids implicit-any, effectively meaning the only places you can omit type declarations is where the language will infer the type. Typescript for instance does not infer types of function arguments via their usages (like Flow does), which means in strict mode you must explicitly provide a type for all arguments within a function declaration.

    I used to be a bit of a pragmatist when it comes to strict mode, but over the years that has subsided, nowadays I think it is plainly obvious that all Typescript programs should use strict mode unless there's a damn good reason. And I'm not sure there are any legitimate damn good reasons.

    True there is no ability to forbid an explicit-any type declaration, though.

    LudwigNagasena 18 hours

    There is @typescript-eslint/no-explicit-any.

    dns_snek 11 hours

    More generally you can use "no-restricted-syntax" rule to forbid almost any type of syntax by matching AST against CSS-like selectors.

    https://eslint.org/docs/latest/rules/no-restricted-syntax

    https://typescript-eslint.io/play/

    frollogaston 18 hours

    Well now Claude will add the types for me, so I don't need to use escape hatches

    galaxyLogic 15 hours

    I haven't tried that but so are you saying I could basically code in JavaScript and then ask Claude to turn it into TypeScript?

    frollogaston 13 hours

    Yeah, I've done it with JS, but more often with Python.

    dns_snek 11 hours

    As long as you're fine with the types being semantic gibberish because all agents I've used take the lowest effort approach to make the error go away.

    You probably have the same logical type duplicated in 3+ different places (at least partially), including inline casts using type literals like "maybeCat as { meow(): void }"

    frollogaston 11 hours

    So far I've seen it actually do the types well when I tell it to add types. But even if it didn't, I wouldn't care, it's just to check a box.

    chamomeal 19 hours

    It also takes a long time for the ecosystem to catch up. It can be hard to retrofit static types over something that wasn’t built with them in mind

    sodapopcan 17 hours

    I keep getting baited by these comments so this is the last one I'll respond to, lol.

    Elixir is always been sort of a "typed dynamic language" due to how baked in pattern matching is. Any good Elixir developer has always been thinking about types anyway, it's almost impossible not to.

    chamomeal 6 hours

    That’s a good point! And I suppose you don’t have willy-nilly reassignment of properties like you do in ruby/php/js.

    sodapopcan 2 hours

    Also, as I kept forgetting to mention, there are no overloaded operators (`+` only works on numbers, for example... unfortunately it does work on both ints and floats but that's another story). The one pain point is that comparriason operators works across all types, but the compiler has already been warning against doing that for at least a year now.

    chamomeal 1 hours

    Ok dang. Well. I guess I have no reason not to dive fully into the elixir now.

    I’ve toyed around with it a handful of times and I really like it. I like the clojure-ey immutability and threading operators and such. And of course I’ve heard so much about the magic of the BEAM and the phoenix framework. But between typescript and clojure I’ve never felt like I needed anything else.

    But if the type system is pretty good, that’s a huge plus over clojure in my book.

    c-hendricks 19 hours

    Conversely, TypeScript is my favourite type system because it has to support the wild things people did in untyped languages.

    kahrl 18 hours

    [flagged]

    siwatanejo 15 hours

    Haha, it is actually my least favorite statically typed lang for this very reason.

    awepofiwaop 2 hours

    I agree. The sheer amount of flexibility it provides makes it both hard to use/read and also not particularly safe/sound. No other type system in existence allows you to be as incoherent as TS.

    awepofiwaop 13 hours

    The issue with TS is that it's not really a type system, it's mostly just comments with a linter bolted on. It tries, but it's fundamentally broken in too many ways.

    Here's just one very simple example, there are many more. I've checked all the strict mode options and this appears to still "typecheck".

      var x: {a: number} = {a: 1};
      var y: {a: number|string} = x;
      y.a = 'FAIL';
      var n: number = x.a; // not actually a number
    
    Source: https://www.typescriptlang.org/play/?noUncheckedIndexedAcces...

    koito17 12 hours

    Two things to note:

    1. TypeScript doesn't aim to have a sound type system. i.e. there may be things the type system accepts that are actually unsafe.

    2. this is more of an issue with mutation. If those properties were marked `readonly`, then the assignment of y.a wouldn't work at all. You can also encapsulate mutation behind functions with your intended types.

    I tend to write TypeScript in a "functional" or "immutable" way, and in this case, most soundness issues come from things like array index access, which can't really be solved without dependent types anyway.

    With that said, TypeScript still gets one quite far *despite* soundness not being a goal of the type system. The problem is that writing imperative, mutable code will make you go through (intentionally!) unsound covariance of types. Similar issues exist for code with side effects, since TypeScript has no way to encode effects in the type system. This is why some language communities settle on ideas like "functional core, imperative shell", where the ultimate goal is absolute minimum amount of code involved in side effects and mutation, while everything else is designed to be easy to test (and, ideally, expressible with a sound subset of your type system).

    throwaway81523 19 hours

    You didn't like Purescript? It looked pretty cool to me. Its main competition back in the day was Elm, but Typescript has now taken over. From a distance Typescript seems to have too many gaps. I haven't used it though.

    rapnie 19 hours

    The Lustre [0] web framework in Gleam was directly inspired by Elm.

    [0] https://github.com/lustre-labs/lustre

    culi 17 hours

    I think TypeScript can feel like there's too many gaps because not enough people take it seriously enough to truly learn it. Hardly anyone reads a book about best practices/design the way many do about C/Java/Rust.

    It's actually a very powerful tool when used thoughtfully. Although it wasn't the first structurally typed language I tried, it's the one that made me fall in love with structural type systems

    galaxyLogic 15 hours

    I like the strutural typing as well. But I hesitate to use TypeScript because AI tells me this:

    It Catches: Mismatched function arguments, missing object properties, and typos in variable names.

    It Misses: Invalid JSON from an API, unexpected database outputs, and bad user input.

    culi 15 hours

    You use Zod if you want runtime features. I'd say it's pretty industry standard. On the type level there's no reason it couldn't account for any of the examples you pointed out. And since Zod supports all the expressiveness of the actual language, you can certainly have those as runtime checks

    I would also just like to point out that the "It Misses" your robot pointed out aren't actually flaws with TypeScript but flaws with JavaScript.

  • losvedir 19 hours

    Oooh, here we go! As a professional Elixir developer for... 10-ish years now, I've been super excited about types coming. I'm very excited that the beginnings have started to land here.

    That said, I would love to know how the state of what's in v1.20 compares to un-spec'ed dialyzer. I was under the impression that dyalizer's "success typing" approach (not flagging a function if there are some combination of parameters such that it works, rather than flagging it if some combination of parameters can make it fail) was like what Elixir is doing here, and I haven't found dialyzer terribly useful.

    dugmartin 19 hours

    I'm curious what it is going to find in my 10 year old Elixir codebase (still in active production use).

    felix_starman 3 hours

    I think the 300th episode of Thinking Elixir w/ José as the guest included a discussion on that point exactly, and if I remember correctly it was a "it depends", but I took away "probably not worth adding more labor into putting it in if you haven't already".

    I haven't had it catch something before the compiler in a while. I still use typespecs for their documentation benefit, though I've been using `defguard` w/ `is_struct/2` and complex guards a lot more in recent years.

    sph 8 hours

    I know this is blasphemy to the average HN reader, but as a professional Elixir developer for 10 years, never have I felt the need for stronger compile-time type guarantees. None of my production services have had downtime or crashes because of type errors. Sure, at times, for very data-intensive sections of the application I would have loved something a bit more complex than dialyzer, but the guarantees offered by OTP and its actor model are much more important than compile-time type checking.

    Of course people used to write server software in compiled languages feel the need for them because any runtime bug means downtime, but in BEAM land you'd have to work very, very hard to see your application crash in the classic sense, causing downtime and gnashing of teeth. And Elixir is strong typed enough never to cause the type of bugs you see in Javascript land, for example (i.e. a string is a string, not a number in some conditions)

    That said, I'm perfectly happy for José and team to work on this niche feature, because for me, the language is pretty much done and all the improvements are on the OTP and library side rather than Elixir itself.

    ken-kost 6 hours

    very true; & 4 years for this niche feature, I feel like it was built for hacker news people.

    But that's good! Indeed that was the most needed!

    & magnificently executed - that's the craziest part - takes away nothing. The compiler is faster!! It's awe inspiring to say the least, what Jose did and still does.

    __jonas 5 hours

    I wouldn't say it's blasphemy, but I don't really understand the argument about how this relates to 'the application crashing and causing downtime'.

    I don't have your level of experience with the language, but I have a personal project written in Elixir, and I do not feel very confident about parts of it that don't have complete test coverage, due to the lack of static typing.

    I'm talking about things like: Is this pattern match exhaustive or is there a possible permutation I forgot / specified wrongly, which may then cause a match error at runtime, breaking a particular feature? (of course not bringing down the whole app due to OTP!); or if I change some keys in a map / struct in refactoring, did I forget to change them somewhere else in the application, introducing another error that is only caught at runtime?

    Both of these have happened to me, I can even give you examples from code that is not my own – for my project I use a snapshot testing library by an experienced Elixir developer, and while using it I encountered two runtime crashes due to data being in the wrong shape and failing a (function clause) pattern match:

    https://github.com/zachallaun/mneme/issues/85

    https://github.com/zachallaun/mneme/issues/105

    Proper static typing would make it very hard to write bugs like this. In Gleam for example, the compiler checks the exhaustiveness of your pattern matches against the type of the data you're matching against, and forces you to handle all possible values.

    williamdclt 4 hours

    > people used to write server software in compiled languages feel the need for them because any runtime bug means downtime

    I keep hearing that but I don't think it's been true in many years? Whether it's Go, Java, C#, Rust... a runtime bug will only fail the request, not the whole server.

    FWIW, the main reason I like types isn't for the compile-time guarantees (although they're certainly nice). It's for documenting what are the data types I'm working with rather than having to guess them from the code, it's for knowing that something is a square hole therefore I should put a square piece in.

    kamma4434 47 minutes

    That’s my top issue with Clojure: I see what the function does, but is it expecting a list, a string, either, or a map? The function may apply correctly, but what was it supposed to do? Java may be boring, but it’s surprise-free. In Elixir this is less of an issue because of pattern matching and very clear errors showing the actual arguments passes, that are unbeatable for debugging - you look at the log and can “see” the issue.

    xlii 12 hours

    Dialyzer fails to successfully report errors when there are circular dependencies. Circular dependencies are nigh unavoidable in Elixir (IIRC bootstrapped Phoenix has 3 or 4) and outside of interfering with Dialyzer it impacts on compilation performance and stability (compilation races causing non deterministic compilation)

    simoncion 10 hours

    > ... circular dependencies ... compilation races ...

    Does Dialyzer understand Elixir? Last I knew, it could only process Erlang source code and BEAM files. Looking around, it seems like folks running Dialyzer against Elixir code are using some "dialyxer" thing.

    You talk about circular dependencies causing minor compilation troubles, so it doesn't sound like you're talking about types defined in terms of each other. I might be unaware of something important, given that I've never had the opportunity to do Erlang professionally [0]... but aren't the only "dependencies" of BEAM files the exported functions they call in other modules? If I'm not wrong about that, then what happens when you run Dialyzer against BEAM files compiled from Elixir that has circular dependencies? Do its reports become more reliable, or does the reliability of those reports become irrelevant because the transformations the Elixir build system makes to your code make the structure of the BEAM code difficult to trace back to the Elixir source code?

    [0] ...and have written nearly zero Elixir in any context...

    sabiwara 7 hours

    > Does Dialyzer understand Elixir? Last I knew, it could only process Erlang source code and BEAM files.

    Once compiled, it boils down to BEAM files that Dialyzer can understand, yes. And the [Dialyxir](https://dialyxir.hexdocs.pm) wrapper helps translating error messages in Elixir. But, there is a significant limitation compared to plain Erlang: Elixir protocols (which are quite used in core parts of the language) are not an Erlang construct, so Dialyzer will be clueless about them, just accepting any term. Enum.map(nil, & &foo/1) or to_string(%{}) will be invisible to it.

    felix_starman 3 hours

    Dialyzer (and Dialyxir) were written prior to compiler tracing, and also are based on Erlang's "Typespec" syntax which is a bit lacking.

    I still use the Typespec syntax for its documentation benefits, and for catching "dumb" bugs, but as the Elixir compiler has improved I have found Dialyzer to be less relevant as the compiler usually catches things before Dialyzer would as it's not built into the compiler and isn't able to be.

    xlii 8 hours

    There's dialyxir which is wrapper to Dialyzer and I found it work fine on pure (non Phoenix) code.

    As for how the problem manifests: even obvious contract violations stops being shown (making it feel like "Dialyzer is useless") but the second tell is very long check times (tens of seconds up to minutes).

    simoncion 7 hours

    Cool, cool.

      [W]hat happens when you run Dialyzer against BEAM files compiled from Elixir that has circular dependencies? Do its reports become more reliable, or does the reliability of those reports become irrelevant because the transformations the Elixir build system makes to your code make the structure of the BEAM code difficult to trace back to the Elixir source code?

    josevalim 10 hours

    You are mixing runtime and compile-time dependencies. Runtime dependencies (circular or not) have no impact on compilation performance and stability. Phoenix does include one circular dependency (the layout is rendered by your endpoint and it references your endpoint) but it is a runtime one.

    xlii 8 hours

    No I'm not. This is often brought up.

    I spent 3 months analyzing failures caused by - what looked like - dirty builds but was caused by unstable compilation order. Which is quite obvious.

    The solution is dynamic dependency resolution but this causes problem with macros.

    The problem is easy to validate. Compile application multiple time and compare hashes. I'm not sure if it's sufficiently visible in bootstrapped Phoenix but I saw it in as small as <1000 LoC toy apps.

    josevalim 8 hours

    Please file a bug report if you can indeed isolate/reproduce it (and please ping me on GitHub once you do)!

    xlii 7 hours

    I've made one for Phoenix (as per - it creates a cycle), though unstable compilation is a wider issue.

    https://github.com/phoenixframework/phoenix/issues/6697

    In case you want to see files affected I made extended writeup on my blog - for reference. https://xlii.space/eng/elixir-cycles/

  • 7bit 21 hours

    Found elixir intriguing and so Phoenix.

    Two reasons I put it aside again are:

    You need Beam and the Elixir. I find that really weird, because I'm used to just the language like in Python, Java, C, Rust. Not something underneath it, too.

    There is no debugger. The way to debug Elixir is to print stuff to the console, like 40 years ago. No thanks.

    lkuty 11 hours

    But then you have all the Erlang libraries for free which is huge. And you add to them the Elixir libraries and that gives you a lot of stuff, just like you get with languages with rich libraries e.g. Java, Ruby, ... I find it reassuring.

    freedomben 20 hours

    If you're used to Java, Elixir is like `javac`, Beam is like `java`. Mix is like a (way better) version of Gradle. You need elixir to compile your app, you only need the Beam to run it. Once you've built your project, you don't need Elixir anymore exactly like java/javac. C and rust compile to machine code so don't have a runtime dep, but otherwise they still require you to have a compiler at build time, just like elixir.

    wkrp 21 hours

    To be fair, there is more than just print debugging. You have access to tools like red(x)bug https://github.com/nietaki/rexbug, the Elixir-LS project has Debug Adapter Protocol support. And in my opinion, the REPL (and decent software architecture) makes it easy to investigate your code by just running the functions as needed (even if your live production system if you want).

    hmmokidk 21 hours

    I genuinely needed that laugh. Thank you

    7bit 20 hours

    You make me laugh as well, all is good.

    victorbjorklund 21 hours

    That is just wrong.

    > You need Beam and the Elixir. I find that really weird, because I'm used to just the language like in Python, Java, C, Rust. Not something underneath it, too

    The beam is a VM. You get that Java requires a VM too right? It’s called JVM for a reason. And Python requires an interpreter.

    > There is no debugger. The way to debug Elixir is to print stuff to the console, like 40 years ago.

    That is false. https://www.erlang.org/doc/apps/debugger/debugger_chapter.ht... and you have observer. And you have a lot of other debugging tools. I hear Java has a good one and maybe it’s better (I never used it) but it’s not true there exist no debuggers for the beam.

    Spixel_ 21 hours

    Almost nobody uses it though, which is too bad, especially since multi-head functions sometimes make it difficult to follow the execution path.

    I'd like to do step by step but I cannot plug the debugger to VScode from inside a docker container.

    ch4s3 20 hours

    I've used it, but I've very rarely needed to do so.

    seanclayton 20 hours

    No one I know wears the shoes I like to wear, which is too bad, because that means I can't enjoy them as much now.

    Spixel_ 19 hours

    I meant that it doesn't get much love from the community, it's pretty clear it's not used much, that's why things like `dbg` gets added to the language.

    cmoski 20 hours

    People use it.

    19 hours

    Spixel_ 19 hours

    Some do, but the DX was bad last time I tried, I did not find a way to use it with my setup.

    lionkor 21 hours

    Java has the JVM the same way that Elixir has Beam/OTP/...

    hackyhacky 21 hours

    And CPython runs Python bytecode, which is basically running in a Python virtual machine.

    I am not sure what GP is objecting to.

    rfgplk 19 hours

    > I am not sure what GP is objecting to.

    Elixir always felt like it would be a solid functional systems programming language, so not having a compiled backend is a genuine downside.

    7bit 21 hours

    Read again...

    Here's what you need to do for elixir:

    Download and run the Erlang installer Download and run the Elixir installer

    Here for Java: Download and run the Java SDK

    And for Python: Download and run the Python installer

    flexagoon 5 hours

    > Download and run the Erlang installer Download and run the Elixir installer

    No, you just install the elixir package from a package manager. Windows not including a proper one by default is not a fault of the language.

    sokols 21 hours

    To use Python/Java you have to download and install an OS. (Though some versions might run on bare metal)

    dematz 21 hours

    Is your issue something with the runtime itself, or just the difficulty of installing it?

    WolfeReader 21 hours

    I think the issue is "I have to install two things instead of one thing" which is a pretty weird way to judge a programming language.

    I guess we know how he feels about TypeScript.

    sbuttgereit 21 hours

    If you're going to try and use this analogy, you need to compare Elixir to Kotlin or Scala or Clojure rather than Java. Elixir is a language written for the BEAM which was created for Erlang. The BEAM happened to be useful VM for these other languages such as Elixir, Gleam, LFE, & Luerl.

    7bit 20 hours

    No, I don't. I'm not writing gleam etc for the same reasons.

    dematz 20 hours

    If you don't want to then fair enough :) that said if your problem is just installation, some of the gleam people realized it can be tricky and made a nice guide for various operating systems and package managers: https://gleam.run/install/

    Note this includes installing erlang as well

    While it is multiple steps, the frustration is a much more one time thing compared to the problems and frustrations you'd have using a language or its ecosystem for a long time or big project

    freedomben 20 hours

    For Java you need a JRE and JDK depending on whether you're just running or also building. That they are bundled (for Windows) is slightly convenient, but they're not bundled on Linux so what you're saying is OS dependent

    burnt-resistor 19 hours

    JRE or JDK, not "and". The JDK is a superset of the JRE.

    freedomben 16 hours

    Thank you, appreciate the correction

    WolfeReader 21 hours

    Here's what you need for Java:

    Download SDKMan/Jenv

    Install the version(s) of Java you need for your projects

    Make sure your JAVA_HOME environment variable is set

    Ensure your IDEs locate the correct Java home

    Compared to all that, Elixir's two installers are trivial.

    And if you have a competent package manager, you can just tell it to get Elixir and it'll handle Erlang for free.

    vips7L 20 hours

    No you don't. The process is exactly the same for Java.

    WolfeReader 19 hours

    Nah, I work on a team that has multiple microservices written over the years in different versions of Java. "Just click the installer" is not sufficient. That's why programs like jenv, SDKman, nvm, and others even exist (and are popular). Your lack of real-world experience is showing.

    vips7L 19 hours

    LOL LaCk oF eXpErIeNcE. Bro all you have to do is open intellij and it will prompt you to install the correct version of Java.

    WolfeReader 19 hours

    Don't tell OP this - he doesn't want to install multiple things. You'll scare him away from Java.

    7bit 4 hours

    Java is ugly anyway

  • sevenzero 22 hours

    Oh shit here I go (and learn Elixir for a whole year (again)) again.

    I love everything about Elixir, but Elixir constantly makes me doubt myself like no other language. My brain isnt made for functional stuff, but this makes me want to try again.

    Sucks that it's not really a beginner friendly ecosystem and usually, when having questions answered, people assume you already know a lot about the language.

    adamddev1 20 hours

    Do https://htdp.org and follow all the exercises carefully (yes, it will feel like baby work at first) - you will retrain your brain for functional stuff. :-)

    sodapopcan 17 hours

    Come hang out on Elixir Forum! Lots of friendly folks there who are happy to answer (and re-answer) beginner questions. It's not quite what it was a few years ago thanks to LLMs, but it's still quite active.

    EDIT: I see my cohort has already given you this suggestion :P

    cpursley 21 hours

    I find beginners respond well to this resource: https://joyofelixir.com/toc.html

    qaq 21 hours

    community is super nice I am sure you will get help.

    jimbokun 21 hours

    Comments like this always confuse me as object oriented programs riddled with state are much harder to reason about to me.

    sph 21 hours

    I'm working on a game engine right now (written in object oriented language, of course) and I keep itching to design a compiled functional language for games, because state spread in thousand of objects, eldritch class hierarchies, are complete hell.

    Once you taste Elixir/Erlang, there is no going back to the madness.

    isityettime 15 hours

    > I keep itching to design a compiled functional language for games

    Jank wants to be this, right? IIRC its author and chief maintainer was a game dev before he dedicated himself to the language.

    https://jank-lang.org/

    Maybe porting your engine would be a great way to prove out Jank 1.0 when it arrives ;)

    sph 7 hours

    Thanks for the pointer, never heard of this!

    isityettime 1 hours

    Awesome! Maybe it's even a language you could enjoy contributing to. :D

    sevenzero 21 hours

    The confusing state riddling here happens in the background as your whole app basically is a state. The thing that really throws me off with Elixir is having to handle (possibly) hundreds of thousands of processes. Doing this correctly seemed impossible to learn for me.

    sph 21 hours

    It's not like you're dealing with hundreds of thousands of ad-hoc processes. If you're writing a web server, for example, each of these processes might simply be a client connection and they all operate the same. The fact that there are 2 or 100,000 is only a problem for the BEAM scheduler.

    Sounds like there is some foundational knowledge of Elixir that you miss and everything seems more confusing than it should be. To me writing a 'server' in Elixir is orders of magnitude easier than doing it in Python, Rust or C++.

    As someone else suggested, bring your concerns to the Elixir Forum and surely someone will clarify them for you

    jimbokun 20 hours

    But would be even harder to wrap your head around if you tried to implement similar capabilities in Java.

    klibertp 18 hours

    > Elixir is having to handle (possibly) hundreds of thousands of processes

    OMG, why? Why would you ever have so many processes? All of them at the same time? Are you going to animate a 3D scene and run a process for each vertex, or something?

    No, I mean, if you're WhatsApp - across all nodes - then somehow maybe yes? At scale. But in normal code, slicing workloads too thinly is counterproductive, and having even tens of thousands of processes is a sign that you're slicing it way too thin. Message passing between processes is cheap, but not free. Schedulers do a good job, but rarely have more than 16 cores to work with. And so on.

    You can have that many processes if you want, to be sure. But if you're struggling with it, why would you want it?

    Reading your comments in this thread, I have a feeling you just didn't spend enough time reflecting on how you want to use Elixir. In effect, you also failed to consider how exactly you should learn it. For example: Elixir is a perfectly capable procedural language. Start by writing CLI tools, without spawning any processes at all. Then try to parallelize their processing. If the tool accepts a list of files as arguments, use a `Task` to compute return values for each file. Tasks are processes, but with a particular contract that simplifies their usage. Later, you can experiment with error handling and supervision by putting the tasks under a supervisor. And so on. You go from the familiar to the less familiar, with a useful, working tool every step of the way.

    toast0 12 hours

    > No, I mean, if you're WhatsApp - across all nodes - then somehow maybe yes?

    I mean, we had one process per client connection (which is 100% the way to go) and depending on the era, hundreds of thousands or millions of connections per chat node. I don't think we ever really summed the number of processes over a cluster.

    Other than client processes, there weren't that many processes per node; like you say, it doesn't make sense to spread too thin.

    There's a lot of client connections and so a lot of client processes, but it ends up being pretty simple to work with them. They all do the same thing... wait for a message, process the message, wait some more. Some of the messages are tricky to process (like the user just logged in again over here, so please transfer the state)

    sevenzero 12 hours

    I learned it for almost a full year by trying to build a live chat app. I went through Elixir in Action and the official guides and yet those questions were never really answered. I never said I want hundreds of thousands of processes, but thats definitely a thing you need to account for. Errors are often simply swallowed.

    klibertp 5 hours

    > Errors are often simply swallowed.

    That's a bit of a misrepresentation. Error handling on the BEAM has a few more layers than in other environments; specifically, the supervision tree can be used to "let things fail". That's not the layer where you should log or handle failures - that's a safety net that ensures your whole system won't go down if your error handling in a single process doesn't work.

    For error handling, there are roughly these layers:

        - functions can return {:ok, value} or {:error, error}
        - functions can raise errors (similar to exceptions) that can be caught
        - processes can be monitored from the outside, you get notified when they die
        - processes can be linked and exits can be trapped, also notifying you on failure
        - supervisors can handle process deaths in a configurable manner
        - higher-level behaviours often expose their own error handling callbacks
    
    So there's a bit more to error handling on the BEAM, and I get that becoming familiar with all of them and using them properly can be a challenge. The defaults skew towards high-availability, which is not always what you want in development - sometimes, failing fast and completely (up to stopping the app or the BEAM as a whole) is more convenient. You can have that; you just need to ask for it specifically in your code.

    toast0 12 hours

    > Errors are often simply swallowed.

    That's a choice, but it's not idiomatic.

    You're expected to write things like...

        ok = thing_that_might_not_work().
    
    (Well, that's what it looks like in Erlang anyway). If there's an error, it doesn't match, so it crashes. You don't have to check for success, but it's easy to, and 'let it crash' is the mantra, so yeah. Then you watch for crashes, and fix them with hot loading, and pretty soon you have a reliable system.

    Let it crash ends up not quite working, so you end up catching a lot of errors, but you should be logging them, not swallowing them...

    pdimitar 21 hours

    I invite you to ask on ElixirForum. I have never seen a truly hostile response.

    Sometimes posts don't get traction due to ambiguity, and some smelled like "do my homework" so people ignored them.

    But every post with a genuine curiosity in it gets answered, as far as I can tell.

    sevenzero 21 hours

    Yea I've posted there twice as far as I remember. You will absolutely get help, whether you understand the answers is a whole different story.

    Elixirs community is great. Its just hard to learn because it's not yet widely adopted, there are no (non senior) roles for it and it's a lot of work understanding all the BEAM concepts. A thing just being interesting isn't enough motivation for me to learn, I need a bigger goal but with Elixir there do not seem to be any.

    My last experience with it was building something with Phoenix Liveview until I noticed how easily you can hijack the websocket and just spam random commands to your server or temper with payloads (with regular webapps ive built i never had this issue). Which made me quit that project.

    ch4s3 21 hours

    > whether you understand the answers is a whole different story.

    You can always ask follow up questions for clarification, people there are generally really friendly.

    arcanemachiner 18 hours

    I haven't dug into this for a while, bit you should be able to define a catch-all event to return a respond to non-compliant requests . It should be built-in to some degree IMO, but I think it's not an unsolved problem.

    sevenzero 12 hours

    This will not work if a attacker guesses a function signature correctly as the catch all block usually is at the bottom of the module. If you use atoms in the function signature, attackers can just guess them, even if you never intended that function to be reachable from frontend code.

    That being said, I am not forced to use liveview, its just that most ressources nowadays use it.

    pdimitar 21 hours

    Fair. If you have this friction then it's not worth pursuing.

    One thing that really helped me pick it up was saying YOLO and rewriting one part of the business stack from Ruby on Rails to Elixir. It taught me quickly and well.

    The official guides are also great and IMO you can get through them all without a rush in two weekends. But again, if you don't want to then don't.

    You can also try asking right here in this HN thread. Maybe I or others would be willing to give you a more detailed response.

    sevenzero 21 hours

    When building I couldn't get "what if I have ghost processes", "what if I spawn too many processes", "what if this architecture is bad compared to...", "when to kill processes", "whats the correct restart strategy for this" out of my head... It's so confusing to build for the BEAM that I ultimately gave up on it.

    klibertp 18 hours

    > It's so confusing to build for the BEAM that I ultimately gave up on it.

    Every new paradigm is confusing if you don't put in the work to learn it. That's just how the mind works.

    What's important is what you get after you don't give up on it long enough. And that, on BEAM, is a hilariously OP superpower of effortlessly[1] parallelizing and distributing workflows. Then there are Elixir macros and the OTP supervision model. The addition of gradual typing is huge, and when the annotation syntax lands, I will definitely switch to Elixir for everything on the backend.

    In any case, the only thing I can tell you is that learning Elixir is worth enduring the confusion. From personal experience, it's just a matter of learning it bit by bit over time - there's a finite set of "confusing" ideas in the OTP/Elixir/BEAM mix, and learning about some of them every other day works wonders over a few months.

    [1] An exaggeration - I know! But it does make it much easier to implement parallel and distributed workflows. Recently, most of the important languages finally started getting their m-n concurrency models (from Java to Python), so the BEAM is not as much ahead on SMP, but for distribution (you can send closures to execute on different machines transparently!) it is still in a league of its own.

    toast0 20 hours

    > "what if I have ghost processes",

    I'm not sure what a ghost process is? I guess something that's living beyond its usefulness / isn't supervised, etc? ... I don't speak Elixir, but you can do the equivalent of this Erlang to see everything on the node:

        rp([{X, erlang:process_info(X)} || X <- erlang:processes()]).
    
    Then you'll know what's going on. Caveat: if you have a lot of processes, that's going to use a bunch of memory; for production you probably don't want to use erlang:process_info/2 with specific items instead of the default items. And you might don't want to output something for all the processes if you have a lot of "normal" processes that won't need to be listed.

    > "what if I spawn too many processes",

    The default limit is 1,048,576, if you want to have more, you can add +P X to the erl command line with a bigger limit? Have your monitoring alert you when you're at ~ 80% of the limit.

    > "what if this architecture is bad compared to...",

    This probably addresses the real question of your too many process question. If your architecture is bad or if you spawn more processes than a good architecture would, your performance will be bad. If your architecture is really bad, you'll have a hard time solving the problems you're trying to solve. Future you will look upon your system and despair; you may also despair in the present...

    Eh, you're going to make bad architecture. BEAM won't solve all your problems. But, if you've got problems it can solve, IMHO, it can be a very nice way to solve them.

    > "when to kill processes",

    Kill processes (or let them crash) when they misbehave. Kill them (or let them exit normally) when they've done their work and they don't have anything else to do or wait for. When you spawn a process, you'll often have a pretty good idea of the conditions that would lead to its death... Ex: if you spawn a process to handle a connection, it should probably die around the time that the connection ends. If you spawn a process to handle a request, it should probably die when the request is handled. If you spawn a process to listen for connections, it probably should die when you don't want to listen anymore. Etc.

    > "whats the correct restart strategy for this"

    Well... it depends. Almost never the default strategy. The default strategy is a big foot gun; at least it is for Erlang, maybe they changed it in Elixir. I need zero hands to count the number of times I actually wanted BEAM to stop because some supervised process failed 3 times in a small time frame; but it's happened to me a lot more times than that. For per connection or per request things, the appropriate strategy is not to restart at all; for other things, try to restart a few times quickly then maybe every minute or so is probably sufficient. You'll want some sort of alerting. And if the restart strategy isn't right, you can always console in and poke it.

    pdimitar 21 hours

    Ah, true. You are right this assumes some familiarity. Definitely a gap.

    Check this out: https://www.theerlangelist.com/article/spawn_or_not

    Written by one of the very best Elixir mentors. I believe it will dispel most (hopefully all) of your doubts and clear things up.

    macintux 21 hours

    I'd also suggest skimming this free ebook: https://erlang-in-anger.com

    ai_critic 21 hours

    What functional stuff is throwing you off? A whole bunch of it can be written procedurally when starting out.

    sevenzero 21 hours

    With Elixir specifically it was the learning experience I had with Phoenix. I didn't understand how a Phoenix app booted, didn't know where to edit my config. Syntax like:

    ``` socket "/ws/:user_id", MyApp.UserSocket, websocket: [path: "/project/:project_id"]

    ```

    Elixir gives you too much freedom on how to write something on a syntax level which really annoyed me.

    ch4s3 21 hours

    > Elixir gives you too much freedom on how to write something on a syntax level

    This is true perhaps compared to python or go, but not compared to Java, JS/TS, or some others.

    > socket "/ws/:user_id", MyApp.UserSocket, websocket: [path: "/project/:project_id"]

    Socket is a behavior, which is like a trait or interface. MyAppWeb.UserSocket implements the behavior. It's basically a convenience over having to write a bunch of repetitive WS or long poll handling every time you want a socket like thing. Its pretty well documented https://phoenix.hexdocs.pm/Phoenix.Socket.html.

    solid_fuel 21 hours

    I love Elixir and Phoenix, but Phoenix especially uses a lot of compile-time macros and it can be a steep learning curve when you need to pull apart the skeleton framework to figure out how things are actually wired.

    I pretty frequently find myself needing to open up the source to understand what's actually going on, the docs aren't bad but it often feels like they assume a lot of existing familiarity with phoenix.

    In this example, `socket` is a compile time macro and it's being called with

        path = "/ws/:user_id"
        module = MyApp.UserSocket
        args = [
          websocket: [
            path: "/project/:project_id"
          ]
        ]
    
    and what is does is register that data with the `phoenix_sockets` attribute inside the module you called `socket` from. At compile time that gets turned into a lookup inside your module, and presumable then the UserSocket module is invoked when a websocket request hits the specified path.

    Would you find it more clear if socket was called like this?

        socket("/ws/:user_id", MyApp.UserSocket, [websocket: [path: "/project/:project_id"]])
    
    
    Or, alternatively, would it help if the endpoint was more specifically defined like

        defmodule MyApp.Endpoint do
          use Phoenix.Endpoint, 
            otp_app: :my_app,
            web_sockets: [
              socket("/ws/:user_id", MyApp.UserSocket, [websocket: [path: "/project/:project_id"]])
            ]
        end

    sevenzero 21 hours

    I think the lack of parentheses is whats throwing me off regularly with Elixir.

    solid_fuel 20 hours

    I find the optional parentheses, and the way that keyword lists are defined to be the two biggest stumbling blocks when I come back to Elixir after a while way.

    Coming from other languages, I find that

        example("with", 3, extra: "arguments", as: "a", keyword: "list")
    
    being equivalent to

        example("with", 3, [extra: "arguments", as: "a", keyword: "list"])
    
    and

        example "with", 3, extra: "arguments", as: "a", keyword: "list"
    
    always takes some extra mental effort to get through, especially when there's no parenthesis. But I appreciate not having to write all the extra brackets and parens when I get going, so I think it's a fair tradeoff.

    arcanemachiner 18 hours

    Elixir has enough syntax sugar to cause diabetes.

    Personally, I like the flexibility, but yes there are a lot of rules to keep in mind.

    dqv 14 hours

    one more ;)

        example("with", 3, [{:extra, "arguments"}, {:as, "a"}, {:keyword, "list"}])
    
        iex> [{:extra, "arguments"}, {:as, "a"}, {:keyword, "list"}] = [extra: "arguments", as: "a", keyword: "list"]
        [extra: "arguments", as: "a", keyword: "list"]

    isityettime 20 hours

    > I love everything about Elixir, but Elixir constantly makes me doubt myself like no other language. My brain isnt made for functional stuff, but this makes me want to try again.

    I experienced this really painfully when I was in college and took a kind of "survey of programming paradigms" course and tried Haskell for the first time. I'd been programming for years by then, and I couldn't believe how helpless I was at trying to complete things that had long felt "basic" to me.

    But I don't think it's about the brain not being suited, I think it's that contrast of your experience level in imperative languages vs. the fact that when working in a pure functional style, you start out as a newbie again.

    I think you'll gradually improve. I think the thing that finally made functional programming feel comfy for me was realizing how much I love composing code that basically feels like more generously spaced Bash "one-liners". The data starts out in one shape, so you run a command to dump it. Then you think of a step that gets it closer to what you want, you pipe it to that next command, and you take another look. And you keep going and at the end what you're looking at is typically pretty close to a series of transformations of data that you never mutate!

    Part of what makes this feel comfy in the shell is that you build up that vocabulary of commands just by puttering around your file system every day. Over the years my library of familiar "functions" in a Unix-like environment has grown quite large. In a pure functional programming environment, you have to do the same thing but it takes a little more effort to learn the vocabulary. Your most frequently used "commands" will be functions like map, fold, and zip instead of grep, cat, or sort. But the core of it is really the same, and what I love about building pipelines applies equally to both: you can build it piece by piece, and for each puzzle you're on, you can forget about the previous steps and just think about the next transformation of the data that's in front of you. There is something refreshingly, relaxingly low-context about that.

    Anyway I hope you give it a try and enjoy it. When we can learn to enjoy being bad at something, that's how we finally get good at it.

    cubefox 17 hours

    > But I don't think it's about the brain not being suited, I think it's that contrast of your experience level in imperative languages vs. the fact that when working in a pure functional style, you start out as a newbie again.

    When I was in university, the introductory class was about Java, and an advanced class in the next semester was about Haskell. There were many imperative/functional newbies in both classes, but the Haskell class still progressed much more slowly. Haskell is simply much harder to grasp, independently of experience.

    You can also see this in the fact that even mathematicians use Python rather than Haskell for simulations. Despite the fact that there is no population that is better suited for Haskell than mathematicians.

    Even cookbooks are always written in an imperative style, never in a functional one. Why is that? Human brains find imperative algorithms simply more intuitive, and this is not explained by not being used to functional ones.

    zxter 12 hours

    Cookbooks are imperative, sure. But not every book is a cookbook.

    Religious texts, philosophy, ethics, and even self-improvement books often don't provide a procedure to follow. They teach things like how to handle conflict, how to act fairly, how to navigate difficult situations, or how to reason about competing values.

    People then take those ideas and apply them across many different situations in their daily lives. In a sense, they build a toolbox of reusable mental functions rather than memorizing a single algorithm.

    That's also why many people finish a self-improvement book feeling like they didn't get much out of it. They were expecting a recipe. Instead, they absorbed a collection of abstractions that only reveal their value when applied later in real situations.

    The fact that cookbooks are imperative mainly shows that procedural tasks are naturally expressed procedurally. It's not obvious that this generalizes to human reasoning as a whole.

    mihaelm 21 hours

    Do you maybe know some Rust? I'm also not that experienced with FP languages, but Gleam felt familiar enough, due to some Rust-isms, to allow me to focus more on the concepts rather than the syntax. Granted, I spent a few afternoons with it, but if I were to pick a FP language again to wrestle my brain into submission, I'd probably go with Gleam due to familiarity.

    sevenzero 21 hours

    I gave up on Rust even quicker than on Elixir haha.

    But yea I know about Gleam and I did build some fourier transform stuff with Rust a while back. I like Gleam generally. I am just much much slower with FP and think its extremely unintuituve compared to, say, Go for example.

    agluszak 19 hours

    why did you give up on Rust?

    sevenzero 13 hours

    Personally I find it horrible to write.

    pjm331 21 hours

    https://pragprog.com/titles/lhelph/functional-web-developmen...

    don't let the title fool you - the first half of the book is just elixir

    over the past 8 years this is the book i've used to ramp back up on elixir and it works like a charm every time - i've never finished it

    for me, a mark of a good programming book in this tutorial-project style is that I have started it half a dozen times and never finished it because at some point before the end I've been equipped w/ the tools to go off and do my own thing

    parthdesai 20 hours

    There's also the "bible": https://www.manning.com/books/elixir-in-action-third-edition

    mechanicum 20 hours

    FYI, that’s currently available in a Humble Bundle with 16 other PragProg functional programming books: https://www.humblebundle.com/books/ultimate-functional-progr...

    roblh 20 hours

    Great find, grabbed that. Thanks!

    kajman 21 hours

    I've heard that Phoenix has changed a lot since that book was written. How relevant are those framework specific parts still?

    arcanemachiner 18 hours

    As someone who learned Elixir during the Phoenix 1.7 release, let me tell you: If you downgrade to Phoenix 1.6 and learn from there, you should be fine.

    The upgraded versions are mostly the same, but the differences in Phoenix 1.7 are enough to break the tutorials enough to confuse a newbie. Now, in the post-LLM age, that's not nearly as bad. But it was a real pain when I was learning.

    sevenzero 21 hours

    Yea I've worked through Elixir in Action and appreciate all book recommendations. My issue is, tutorial style books rarely cover security related concerns.

    felixgallo 21 hours

    what do you mean by 'security related concerns'?

    sevenzero 20 hours

    How to properly build a liveview thats safe against hijacking the websocket phoenix uses for liveviews. You can just do it from the devtools on client side. With regular HTTP requests at least I know what to look out for, with liveview there are almost no resources on how to build a view securely. Like I was able to just call the functions in my module by just addressing them from my browsers console. Just to name an example.

    Kaliboy 18 hours

    Honestly just build it using the tutorials and sound mind and you're like 80% there.

    This may sound crazy but when any interpreter boots up, but I feel it especially with BEAM, that needs to be your "let there be Light" moment. That's your world, that state is yours and only your will decides what changes.

    So yes you can call all functions in your module, that's indeed how it works. But that's your module and that function mutates your world.

    Just like you filter what people tell you based on your knowledge, you do the same here.

    Most of my methods start with guard clauses.

    `return if condition_not_met`

    Don't touch my state if I don't agree with what you want me to do.

    In Ruby it's essential cause that's how we get RuntimeErrors all over the place. In Elixir it's way easier to do, with pattern matching. And easier since state is what enters the function and will be what leaves.

    If you keep this in mind you should inherently write safe code, because in protecting your domain through guards you basically close the door for exploitation by unknown means.

    I'll give you one example I just thought of. Where I work we run Rails since the time before time, and as such had a lot of technical debt.

    Around Rails 5 or 6 what we call `ActionController::Parameters` had a breaking change. Basically this module processes parameters received from HTTP requests.

    Beforehand it just wrapped all it got and handed it over to us. But now it expected us to tell it what to expect. And if didn't find what it expected it blew up with a bang!

    Horrible for our hundreds of controllers with `controllers * 4` html templates where all the form keys were hidden.

    We either had to add the conventiely available `permit!` call, or find the form keys for all the forms, and add `permit(:name, :address,...)`. A shitload of work before AI.

    I ended up monkey patching Rails to generate the lists for us instead of crashing. And for the point of this entire story...

    The defaults of most frameworks are very safe, but they require the most verbosity so the framework knows what to expect and to guard it. But there always exists easier and faster ways to the same goal, but it's generally a trade. You get ease, you sacrifice some security.

    Don't get in that habit and you'll be fine. And spend a lot of time thinking what could go wrong and guard against them.

    OkayPhysicist 19 hours

    [1] https://phoenix-live-view.hexdocs.pm/security-model.html

    There's a guide in the LiveView docs that walks you through the security model. To be clear, you need to always assume that the user can send you anything. That's a fact of any networked system: Clients need to be assumed to be completely under the control of an evil user, because at the end of the day it is impossible to know whether you're talking to the client you wrote, or some evil program written by an adversary. Any function that acts as a handler for an event/message can be called by the user, at any time. You have to use session/socket state to handle authorization.

    sevenzero 13 hours

    I am well aware of that, its much much easier to account for this with regular HTTP handlers in other stacks though. The issue here is that you can call random functions if you guess the signature correctly. Even authorized/authenticated users can and will missbehave if given the chance.

    OkayPhysicist 1 hours

    To clarify, when you say "random functions", do you mean arbitrary event handlers like "handle_event("my_event")", despite the intended UI not presenting a way to call that event at the moment? Or do you mean any function in the LiveView module?

    The latter doesn't seem to be the case, and if it is would be alarming. The former is absolutely the intended behavior. The client can send events to the server, that's how the whole thing works. If certain events shouldn't be available at certain times, you need to check that server side, and that's going to be true in any http handler.

    sevenzero 36 minutes

    >"handle_event("my_event")", despite the intended UI not presenting a way to call that event at the moment?

    Exactly this, didnt know how to phrase it as it was a while ago where i had this issue.

    And thats absolutely not true for any HTTP handler as there's no way for people to easily break out of the intended behavior.

    OkayPhysicist 14 minutes

    In most other HTTP handlers I've ever used, event handling would be handled by API endpoints, which are trivial for the user to target directly just by going to the Network tab in their browser's developer console.