Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Using Node.js again instead of my usual Elixir and all I can think is "Y'all live like this?".

I feel for anyone using a Node.js backend. Y'all really don't know what you're missing.

Also dealing with bugs is infinitely harder. Try catch is infinitely more wordy and messy. And you lose out on so so many valuable CI steps the Elixir community has from sobelow to credo.

And then there's writing tests. In the Node.js community it is an actual joke. It is so hard to write tests and I have yet to see any framework create a nice sandboxed test environment that just works.

All of this stuff in Elixir is free.

And like someone else said, the best ORM is Ecto and it's hard to disagree with that.

Making a reliable and organized Node.js application is actually so difficult.

If my pet project was in Node.js I would not even be able to accomplish it. And that's a fact. Elixir makes me so much more productive and the code I write is so much less buggy and less complex.

I love that HN loves Elixir. If only it was more common in the workplace.



Were you using Typescript with Node? I really can't imagine working on a project that's even moderately complex without static typing. Having a static type checker is like having an assistant that writes automated tests on its own, and having those tests constantly running in the background.

Refactoring code without static type checking is a nightmare. If you're using a strict statically typed ORM like Prisma [1], you can change your data model then run tsc --noEmit --watch in your terminal and the compiler notifies you of every error with their associated file names and line numbers. In VSCode you can even click the line number and it'll open the specific file where the error occurred. Before adopting Typescript, I basically never attempted large refactors, and when I did it consisted of a long process of grepping for specific symbols in the project, and manually writing lots of tedious and verbose tests to do a fraction of what the compiler does automatically.

I remember developers from the Elm community years ago constantly advocating for static typing, and thinking "this has to be hyperbolic". Richard Feldman gave a talk about how he wrote code on an airplane for hours without an internet connection, and without actually running the code, and how the compiler ensured that the code just worked [2]. Typescript is not as sound as a Hindler-Milney type system, but the benefits are similar (especially if you're running Typescript in strict mode).

[1] https://www.prisma.io/

[2] https://youtu.be/sKxEwjKQ5zg?t=384


It's unlikely that the typing is their grief is around static typing, since the language they're comparing node to (elixir) is dynamically typed.


I'm not saying the lack of static typing is their grief, I'm saying it should be their grief.


Or in other words, the presence of a static typing ecosystem is one of the advantages Node has over Elixir. So while the original commenter might not care that Elixir lacks static typing, they should care because it increases productivity.


The only reason I would want types is for specific error types that I could ideally match against instead of them being raised (in Node.js or Typescript).

In Elixir you have the option of calling functions that raise vs functions that return an error tuple so there's no contention there (the difference between function() (this does error tuple) and function!() (this raises).

Elixir also has this idea of a typespec and I have never felt I needed more, personally.


Typespecs in Elixir (combined with Dialyzer) give you a very limited version of what TypeScript offers. From what I understand Dialyzer is designed to only raise an error when it's absolutely certain that you're mistaken about the types in your program. While this does prevent Dialyzer from complaining about false positives, it only catches the most trivial of bugs [1].

Also even with Elixir's typespecs, static typing is only fully useful if the entire ecosystem around a language embraces it. If some of the libraries you use don't ship with type definitions, it's going to be difficult for any static analysis tool to do a decent job. Almost every major JS library either ships with its own types, or has quality third party types available via Definitely Typed. It doesn't seem like the Elixir community has really embraced static typing to the same extent that TypeScript developers have. The creator of Phoenix for example hates dialyzer [2].

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

[2] https://elixirforum.com/t/do-you-use-dialyzer-in-your-projec...


> While this does prevent Dialyzer from complaining about false positives, it only catches the most trivial of bugs

Is this coming from experience? Because it surely doesn’t match mine


I was referring to Elixir's blog post on static types where the creator of Elixir José Valim wrote: "The Dialyzer project, implemented in Erlang and available for Elixir projects, chose to have no false positives. However, that implies certain bugs may not be caught. At this point in time, it seems the overall community would prefer a system that flags more potential bugs, even if it means more false positives." [1]

From my experience with TypeScript, you really want your type checker to be eager about complaining about type issues, which is why so many people turn on strict mode. In fact, occasionally I'll have to suppress a false positive error with a @ts-expect-error comment.

But even assuming Dialyzer is as good as TypeScript, have you found that the libraries you use actually have quality type definitions? The few times I've used a JS library without type definitions it ended up breaking type inference, and infecting the codebase with the any type, which renders the type checker basically useless. How has your experience been in regards to that with Elixir?

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


> have you found that the libraries you use actually have quality type definitions?

I have some experience with Typescript and it was way better than vanilla JavaScript. Typescript mappings we’re available for most libraries, though for a couple they were not up to date. Similarly for Elixir, the coverage for nearly all APIs is pretty good but also has some holes. In my experience it hasn’t been a big deal either. Any serious library developer puts types alongside their function signatures/implementation.

> The few times I've used a JS library without type definitions it ended up breaking type inference, and infecting the codebase with the any type, which renders the type checker basically useless. How has your experience been in regards to that with Elixir?

Being a functional language, Dialyzer can easily make inferences on untyped code based on ‘success typing’ (what Jose talks about), though it can make for some very obtuse and verbose warning messages. This inference is in addition to safety provided by typespecs which every Elixir library developer typically intermingles with their API surface.

If I would level criticisms at dialyzer it would be its sometimes difficult to read warnings, it’s speed (despite being multithreaded) and the race conditions in the VS Code plugin (which is looking for extra maintainers – if I had time I would help). For the weird warnings I find I just need to add extra typing to my code to narrow it down, which I should be doing anyway


> If I would level criticisms at dialyzer it would be its sometimes difficult to read warnings, it’s speed (despite being multithreaded) and the race conditions in the VS Code plugin (which is looking for extra maintainers – if I had time I would help).

One of the advantages of TypeScript is that VSCode is written in TypeScript, and both VSCode and TypeScript are developed by the same company, so there's a really nice synergy there. I imagine Kotlin users feel the same way using Jetbrains products, and Swift users feel the same way about XCode.

Dialyzer looks interesting, but I can't imagine giving up on the expressiveness of TypeScript. Some of the things you can do with generics, mapped types, intersection types, template literal types, conditional types, and utility types are almost mind boggling. It's difficult to reap all of the benefits of static analysis without some of these advanced type operators. The type manipulation section of the TS manual is really underrated.

Someone for example wrote an SQL parser in TypeScript that requires no runtime code [1]. It can infer the types of an SQL query's result based on an SQL string without any runtime code execution. There was a similar project where someone built a JSON parser entirely using the type system [2]. There's also an ongoing discussion on Github about the the fact that TypeScript's type system appears to be a Turing-complete language with some other cool examples [3]. My point is that the type system is incredibly expressive. You rarely run into an idiom that can't be typed effectively.

[1] https://github.com/codemix/ts-sql

[2] https://twitter.com/buildsghost/status/1301976526603206657

[3] https://github.com/microsoft/TypeScript/issues/14833


The SQL stuff sounds cool, but Ecto is so expressive I don’t even need to resort to raw SQL like I do in other languages. Elixir typing can handle a subset of intersection type which is rather niche, and of course handles union types which are more prevalent. Genetics and inheritance are replaced with a more permissive style of polymorphism with Elixir behaviors and are also part of the typing system.

All languages have their own flavor, and their own pros and cons, and if the advanced types of Typescript work for you, then great! In my own experience, I have found Elixir and it’s typing capabilities to work well for me


> The SQL stuff sounds cool, but Ecto is so expressive I don’t even need to resort to raw SQL like I do in other languages.

The SQL example I linked to isn't something you'd use to interact with a database in production, for that you'd probably reach for an ORM like Prisma. I was just trying to demonstrate the level of type inference you can achieve with TS. Going from a pure string of SQL or JSON to a concrete type without actually executing any runtime code is pretty crazy.

> Elixir typing can handle a subset of intersection type which is rather niche

I personally use intersection types quite a bit. If union types are like the logical or operator, then intersection types are like the logical and operator. Being able to define a type that combines one type and another is not a niche workflow for me.

> In my own experience, I have found Elixir and it’s typing capabilities to work well for me

Can't argue with that! Everyone has their preferences.


> Going from a pure string of SQL or JSON to a concrete type without actually executing any runtime code is pretty crazy.

Going from a JSON string to a type for it is actually one of the easier examples of inference I can imagine. JSON is a data description format in which all the base types are syntactically distinguishable, it has no variables, no arrows (i.e. functions), no generics. In the topic of type inference, you can't have a much easier example.

SQL is more complex, indeed, but still doesn't seem too crazy if you have access to table schemas. It's also a matter of whether triggers and stored procedures are taken into account, but I assume they're not.

There's a lot of prior art described in literature as well as practical programming implementations with much crazier, yet successfully working type inference.


I just want to make sure we're on the same page here. The JSON example I linked to isn't inferring the types of JSON that's already been parsed and deserialized, that would be trivially easy in any language (including TS). If I have an object that's been parsed from JSON, I can just use the typeof operator in TypeScript to infer the type of that object.

The example I linked to is taking a serialized JSON string, and parsing the literal characters in the string (characters like double quotes, commas, whitespace, etc) into a type, purely using type annotations. And the structure of that JSON can be of arbitrary nested depth.

All of this is accomplished using template literal types which allow you to make assertions about the contents of a string. In TypeScript you can assert more than just "this value should be a string". You can make detailed assertions about the structure of strings, and that's what allows these parser demos to exist.

When you combine these template literal types with recursive types, conditional types, and TypeScript's infer keyword you can do some pretty interesting type level programming.

Just to further demonstrate the point, there's an interpreter for the BF programming language, written entirely using TypeScript type annotations [1].

> There's a lot of prior art described in literature as well as practical programming implementations with much crazier, yet successfully working type inference.

Has any of this been demonstrated in Elixir?

[1] https://github.com/sno2/bf


I’m not the GP, but already Elixir can already accomplish compile time text processing with metaprogramming (like it does for inline html templating for ‘heex’ functions and files) and it’s not a huge stretch for it to be able to call a JSON parser on any string knowable at compile time and convert it into a type. That it hasn’t been done yet is probably because no one has deemed to worthwhile to implement. It does sounds cool though.

Metaprogramming is also why generics aren’t really needed in Elixir or Erlang. All specializations boil down to their reified types using macros


> I’m not the GP, but already Elixir can already accomplish compile time text processing with metaprogramming (like it does for inline html templating for ‘heex’ functions and files)

It's not the compile time text processing that's interesting, it's the fact that this compile time code can yield incredibly detailed types, and the fact that it's all done purely using type annotations. Almost every language that's used to build websites has some sort of server side templating language where you can sprinkle some code into your html templates.

> That it hasn’t been done yet is probably because no one has deemed to worthwhile to implement.

Maybe, or maybe it's less feasible without an advanced type system. The final version of the JSON example we're talking about ended up being 61 lines of code. Without some concrete Elixir code to inspect, this conversation is becoming very abstract.

Honestly even if some of this were possible with metaprogramming, my intuition is that it would be much more verbose and complex. I feel like if I tried to implement all of the features of TypeScript by mucking around with an abstract syntax tree using metaprogramming, I would end up with a difficult to maintain crude approximation of the real thing. I don't think we're giving compiler developers enough credit by saying everything they've worked on can be replaced with a quick macro. José Valim's blog post on static typing ended with an announcement that they've sponsored a PhD student to work on these problems.

After two days of discussion, I think we're reaching a bit of an impasse. Honestly just use what works for you!


> It's not the compile time text processing that's interesting

You've talked right past me. Metaprogramming is not compile time text processing.

> Honestly even if some of this were possible with metaprogramming, my intuition is that it would be much more verbose and complex.

No. It's probably even how the Microsoft team are achieving the typing you're talking about. The special sauce here isn't as special as you think.

> I think we're reaching a bit of an impasse

Not really. I was just saying that what you're talking about is totally possible with metaprogramming. Not making some rhetorical play.

> José Valim's blog post on static typing ended with an announcement that they've sponsored a PhD student to work on these problems.

That's specifically because strict static typing with guards^ is at least difficult, but maybe impossible, and that's what the PhDs are trying to figure out. However, dialyzer isn't as hobbled as you imagine/purport it to be – the crux of my counterargument – and actual experience trumps speculation or casual reading on the topic.

FYI, guards and pattern matching are one of the reasons why Elixir is so damn expressive, and this kind of function typing isn't available/possible in most non-functional languages

^ https://hexdocs.pm/elixir/guards.html


> You've talked right past me. Metaprogramming is not compile time text processing.

I'm not talking past you. I never said metaprogramming is compile time text processing. You said "Elixir can already accomplish compile time text processing with metaprogramming", and I was just pointing out that the text processing itself is not the most interesting part of the example, it's the resultant types.

> No. It's probably even how the Microsoft team are achieving the typing you're talking about. The special sauce here isn't as special as you think.

But I don't have to reimplement any of this, because Microsoft has already written it. With enough time maybe you could implement dependent typing using metaprogramming for example, but would you then say that Elixir is just as good at dependent typing as Idris, which has that feature built in?

> I was just saying that what you're talking about is totally possible with metaprogramming.

You've graduated from saying "I think someone more savvy with Elixir would know more." to "totally possible"[1]. This does not sound like an argument from experience.

> That's specifically because strict static typing with guards^ is at least difficult, but maybe impossible, and that's what the PhDs are trying to figure out.

A lot of what you're talking about has already been implemented in other languages. This is not untrodden ground. There's no syntactic sugar for guards in JS (which TS just adds type annotations to), but semantically it's very similar to type narrowing in TS [2], which I use daily. This is not something I've read about casually on the internet.

[1] https://news.ycombinator.com/item?id=35944988

[2]https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...


> This is not something I've read about casually on the internet

Was speaking specifically about your opinions on Dialyzer and Elixir. It's very much a cursory Google around and having stronger opinions about the limits of a language than people who spend time with the language.

> You've graduated from saying "I think someone more savvy with Elixir would know more."

It took further reflection on the idea. It helps to start with at least some epistemic humility, once again the crux of this thread. I'm willing to revert to, "It ought to be possible" given that macros boil down to the final generated code (including typespecs) which is then analyzed by Dialyzer

> Elixir is just as good at dependent typing as Idris, which has that feature built in?

Metaprogramming allows features to become 'built in'. e.g., being able to integrate type checking and compiler errors for HTML/XML or other DSLs.

https://hexdocs.pm/eex/EEx.html#function_from_string/5 https://hexdocs.pm/phoenix_live_view/0.17.0/Phoenix.LiveView...


> Was speaking specifically about your opinions on Dialyzer and Elixir. It's very much a cursory Google around and having stronger opinions about the limits of a language than people who spend time with the language.

I think your experience with Elixir and Dialyzer (or perhaps emotional attachment to these tools) is blinding you to the fact that there are features that your favorite language lacks. I gave you a concrete example of something that's a research topic in Elixir (inferring types from guards), which has an existent analogue in TS (type narrowing). You completely ignored that and fixated on the one sentence about experience. Your entire argument is that I should defer to your authority on the topic as an anonymous commenter, because you're supposedly very experienced. It feels like a very hollow appeal to authority.

> It took further reflection on the idea.

But by your standards simple reflection is not enough to establish authority on a topic. You need to have deep personal experience with using macros to parse text before you're qualified to make a firm judgement. You event went so far as to speculate on how TypeScript implements type checking and claimed that the "secret sauce" is probably metaprogramming, despite having never worked on the compiler. This all feels incredibly contradictory.

> Metaprogramming allows features to become 'built in'. e.g., being able to integrate type checking and compiler errors for HTML/XML or other DSLs.

Built in does not mean "I might hypothetically be able to write my own macro that does this". It means it already exists in the language today.

Honestly this discussion feels like it's devolved into repetitive bickering, which is why I let your original comment about metaprogramming stand unanswered, and you reacted by following me into a different comment thread to reiterate the point.


> emotional attachment to these tools

You've really misjudged me. I've been programming professionally for 20 years, I have used and still use a variety of languages including Typescript.

> Your entire argument is that I should defer to your authority

Never made such an argument. My argument is that you have less authority on the subject matter than someone who has spent years with the language.

> I gave you a concrete example of something that's a research topic in Elixir (inferring types from guards), which has an existent analogue in TS (type narrowing)

At best a subset of functionality than an analogue. This kind of research is being conducted by experts in type theory outside of Elixir. If it's a problem for Elixir, it's a problem for any other language that would attempt it, and absolutely I would defer to the authority of those experts who have spent years looking at type theory.

> despite having never worked on the compiler

Knowledge of meta-programming doesn't require the same skillset as writing a compiler. I'm certain you or any other capable software engineer, would be able to write a macro that parsed JSON, interrogated the typing of that JSON, and spat out a typespec with some knowledge of Elixir `defmacro`, `quote`, and `unquote`.

> Built in does not mean "I might hypothetically be able to write my own macro that does this".

Never made such a claim. My claim is that the JSON typing mechanism isn't really something that's dependent on the Typescript language and toolchain. As with my link on my last post of inline heex components, it's possible to take a text representation of something in Elixir and transform it into something that's available to the compiler (and hence also available to Dialyzer).

> bickering

Trust me there is no spite from my end.


> Going from a pure string of SQL or JSON to a concrete type without actually executing any runtime code is pretty crazy.

Elixir/Erlang might already be able to do something like this with metaprogramming. It’s certainly possible to generate and run Elixir at compile time, and map types are already a good superset for JSON objects, so a compile time JSON to map could then provide an inferrable type. I think someone more savvy with Elixir would know more. I’d certainly not something that I’ve needed.


> But even assuming Dialyzer is as good as TypeScript, have you found that the libraries you use actually have quality type definitions?

Strictly speaking, Dialyzer has two big benefits over more traditional type systems:

1. It doesn't require type definitions to be present, it can type check code having none at all.

2. "Dialyzer is never wrong", i.e. it never returns false positives, i.e. it's an underapproximating checker.

These are the design decisions taken by the tool authors. There's rationale behind them, though the experience of using checkers for other languages might not be 1-to-1 applicable because of that. These decisions come with downsides, of course, some of which are covered in this thread, so I won't repeat them, but in general they were taken deliberately due to characteristics of the language(s) (it was originally created for Erlang, not Elixir) and the state of type system theory at the time. Please bear in mind Dialyzer was created in 2006, some 6-7 years before TypeScript.


If you're into trying out static typing in Elixir, please check out https://github.com/esl/gradient. It's still experimental, but already functional. We're happy to get any feedback or, better yet, contributions.


Agree with all you said, except for the ORM part.

Ecto is good but I think the Django ORM does it better. The ORM in Django[0] is dead simple and easy to understand. The only caveat being that Django doesn't have changesets, which I do like.

It also integrates nicely with the Django Admin. You setup your models, and you can very easily generate an admin interface to CRUD on those models.

One annoyance I have with Ecto is that migration files are handwritten. There's a generator tool, but it only gives you a skeleton migration that you then have to manually fill in. This gives you 2 sources of truth, which is error prone. Automating this process like in Django solves this issue.

Ash[1] (which uses Ecto under the hood) does solve most of these issues, although at the moment the documentation can be hard to navigate.

[0] https://docs.djangoproject.com/en/4.2/topics/db/queries/

[1] https://ash-hq.org/


> Ecto is good but I think the Django ORM does it better.

Oh no, I strongly disagree. I know both very well, and Ecto is much better and flexible. SQLAlchemy was great, when I last used it two aeons ago, but still subpar compared to Ecto.

But also apples and oranges. Django has an ORM (i.e. stateful), Ecto is a stateless data mapper.


Django ORM is nice but too simple for some advanced examples. Tried to solve some Postgres CTE in Django? Good luck.


I so far agree with all you said and strong think already based on limited info available to me that you are a well-versed Elixir dev. However nothing beats the short term dev speed of scripting up some JS with npm. Have you seen the latest test runner efforts on node.js and advancements in deno? I think this gap is slowly closing but node is still not near Elixir in terms of long-term productivity, in future it will take over though I think if types dont get into the Elixir language fast.


I have to transpile my scripts if I want to copy-paste which I often need to do.

And there's no repl if you're using transpiled code. I have tried using `ts-node` but that didn't work. In Elixir this stuff just works.

Maybe there's some tooling I am missing out on with Node.js. I am not familiar with Deno.


> I feel for anyone using a Node.js backend. Y'all really don't know what you're missing.

This is why there was so much push in Rust to get async anything up and running.

There are a LOT of people fed up with the current web backend ecosystems.


Use a real NodeJS framework then. Also nowadays it's Typescript tooling that gave superpower to NodeJS backend.


What's the point of selling languages like elixir as silver bullets like this? Each language has advantages and disadvantages over each other. I wouldn't want to work in the same place as someone who makes such stupid comments. programming perspective should not be so shallow.


It isn't a silver bullet. But it does have a lot of distinct advantages. I can come into any Elixir project and very easily write tests. I can't do this in Node.

This is a huge disadvantage, and extremely dangerous, and in my opinion should render the entire language useless as a backend language for any serious project.

Elixir may not be a silver bullet. But Node.js is definitely a vampire...that isn't on your side.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: