HNHacker News
TopNewBestAskShowJobs

josevalim

4,367 karma · joined January 12, 2008

submissionscomments
josevalim··on Data evolution with set-theoretic types
> This compounds when a library releases a new breaking major version, presumably - `%SomeStruct{}` for v2 may be different from `%SomeStruct{}` v1r2.

You should never reuse the revisions. If you launch a major version, then it means you only support r5 onwards. Do not reset it back to r1.

I am also not sure if the user needs to know the version. Remember that if I am in r5, because of subtyping, the code naturally supports r1, r2, r3, and r4, and those already have to be pretty printed. All of the work here is to "generate automatic proofs" via type signatures, no new capability or requirement is being introduced on the type representation of things.

> Documentation seems like it would be more complex, as well. If `%SomeStruct{}` in v1.0 has some shape, and you publish `v1.1`, then realize you want to update the revision of `%SomeStruct{}` with a modified (or added) field

I don't think documentation is more complex because that's something already factored in today. Functions add new options in new releases. Structs add new fields. And today we already annotate the version they were added. Perhaps this approach can automate some of it instead.

---

Other than that, we should be skeptical indeed. :)

josevalim··on Data evolution with set-theoretic types
Thank you for the comments. Your questions at the end resonate a lot with what I have been asking myself!

> Structural sub-typing with inference

Can we have structural sub-typing with inference that is relatively fast and will generate reasonable error reports? We have been bulking up the amount of inference for dynamic code in our system and sometimes the inferred types get quite large, which can make trouble shooting daunting. In any case, better inference is a win even without taking data evolution into account.

> but tbh I'm just not sure how much this offers vs actual structural typing

The bulk of the work is definitely achieved by structural typing. The revisions help generate automated type signatures that guarantee you have not widened the output for old versions. If all you have is inference, you could accidentally introduce breaking changes?

I guess there may be some automated way where we could check old inferred types against new ones but I am not sure how it could be done without annotating that _something_ has changed?

> The "you must accept all intermediate changes" part is in some ways natural, but you'll also be (potentially) forcing it on your users, and/or writing a lot of transitioning code to avoid constraining them.

Theoretically, you do not need to support all revisions, only support more than one revision at once. A library that provides r1-r2-r3 can be supported downstream through the pairs `r1-r2` and then `r2-r3` and that should hopefully provide a smoother upgrade experience to everyone compared to `r1`, `r2`, and `r3` being part of distinct major versions.

josevalim··on Data evolution with set-theoretic types
The work to develop the base theory, which this article presents, takes 15 months, but it doesn't take 15 months to read it (and hopefully it won't take as long to use it either). Whenever you use a programming language, you may work with data structures that took months to formalize and several more years to optimize, yet no one is saying "throw our data structures away". Even things like pretty printing and formatting a float have collectively several years of research behind them, yet the API is often a single function call.

Of course, you can still not like types, and making it harder to evolve libraries over time is a good reason. But using the time it takes to formalize its underlying concepts is not a strong argument against them. The goal is that someone will spend this time, precisely so you don't have to. :)

josevalim··on Data evolution with set-theoretic types
Author here. This is probably the article that took me the longest to write, roughly 15 months, and I may still not have explained all concepts with the clarity they deserve and I intended to. If there is any feedback or questions, I'd be glad to answer them!
josevalim··on Gleam: A Basic Introduction
Yes, it all goes through the same pipe, but we enter the pipe through different places, which leads to different outcomes. You get different limitations and results depending if your input is Erlang source (Gleam), Erlang AST (Elixir), or Core Erlang AST (which is what LFE used in the past but I am not sure if it is still the case).
josevalim··on Gleam: A Basic Introduction
Transpilation usually refers to the fact the target of your compiler is another language source code, which then has to be parsed again. Which leads to issues such as mismatched lines in stacktraces at runtime. Elixir and LFE use the same compiler tooling as Erlang, but the source is parsed just once, and all line information pertains to the original Elixir/LFE source code.
josevalim··on Gleam: A Basic Introduction
Although if you want to get into Erlang/BEAM and static types are a requirement, I still strongly recommend checking it out. :)
josevalim··on Ask HN: Why Isn't Elixir More Popular?
You are right. There are many positive things about TechEmpower and keeping it open to all and running takes an incredible amount of effort, which is definitely appreciated and I failed to recognize. That was unfair of me.

Although I still strongly disagree with the initial observation that they are "real world". Most applications in the real world receive heterogeneous traffic, from a varying and unknown number of clients, with logging, potential contention on resources, and other aspects that are not taken into consideration.

josevalim··on Gleam: A Basic Introduction
There are several domains where 0 is different from none, for example, most computations involving rates.

Imagine that you are running some A/B tests and you want to track conversions. If one of the experiments received 10 users and had 5 conversions, you want to show 50%. If it received 10 users and had no conversions, you will show 0%.

However, if it has received 0 users, while you could show zero conversions, the correct answer is to say that you don't know the conversion rate. Because maybe, if you had had 10 users, they could have all converted, and the rate would be 100%. You simply don't know.

Same logic applies over computing ROI, interest, velocity, etc.

josevalim··on Gleam: A Basic Introduction
I know you are quoting the docs, but Gleam absolutely throws implicit exceptions, for exactly the same reason why it returns 0 when dividing: the Erlang/VM does not support Infinity/NaN, which means floating point operations can also overflow/underflow. For example, any division with a subnormal will raise:

    1.0 /. 5.0e-324
Or addition between really large floats:

    1.0e308 +. 1.0e308
In fact, even the `float.divide` function, which is meant to be safe, will raise:

    float.divide(1.0, 5.0e-324)
In other words, most functions that returns floats have an unmapped codomain and because of how floats work, and it is not simply a matter of checking if one of the inputs is equal to 0.0.

If Gleam wants to be consistent with division, all float operations would have to return a `Result` type (which I assume would have a direct impact in both performance and user convenience). Plus `let assert` provides a hatch for any function to raise too, and that includes matching on unmapped floats:

    let assert <<a:float>> = <<0x7FF0000000000000:64>>
josevalim··on Gleam: A Basic Introduction
Yes, unfortunately transpilation comes with real downsides. Up until recently, logger events, error messages and stacktraces were displayed in Erlang formatting. It has improved in few cases, but not all, and the line numbers in stacktraces do not align with the source code. And if you want to use a REPL, you must use Erlang/JS ones, etc.
josevalim··on Ask HN: Why Isn't Elixir More Popular?
YMMV. In benchmarks like cached queries, it already outputs a solid 5.5x faster than Rails, and it is worth remembering that the Rails app is way too stripped compared to what folks are actually running in prod (which was one of my original criticisms).
josevalim··on Ask HN: Why Isn't Elixir More Popular?
Understandable! The documentation provides quite more guidance now (and there is nothing wrong with naming your context after your "models").
josevalim··on Ask HN: Why Isn't Elixir More Popular?
> The database pool needs to be at 512 to 1024? Yet Rails seems to get away with far less than that and still have better performance.[2]

Rails concurrency model is per process. So you will have `n_workers` pools and the amount of connections will be `n_workers * 3` based on the code you shared. The production machine has 28 total threads [1], which means Rails is starting 35 workers, according to their auto tuning code [2]. Overall, Rails uses 35x more pools than the Phoenix application and has twice the number of connections (105). It is absurd to imply that Rails is actually running with far less.

Second of all, the database pool needs to be 512 or 1024 if your app server can push traffic quickly enough to the database. If you are increasing the Rails pool size and it is not getting faster, then Rails is your bottleneck. Based on the runs I did last week, it seems the pool is the bottleneck in Elixir's case. We will know for sure in a couple weeks once the PRs land.

> If these people can't get good results in tuning Elixir, who can?

You can look at the results from the latest community efforts in main: https://www.techempower.com/benchmarks/#section=test&runid=1...

If you want to compare with Rails in particular, we beat Rails in every benchmark except two:

1. Single query: which will be tackled by better sizing our pools

2. Plain text: where we are currently sending the wrong payload (but when we sent the proper payload, as in the last published round, we beat Rails too)

And it does not include any of my proposed changes yet.

My point is exactly that the feedback cycle is long, so it takes time, and I was not able to invest time before. But given how intent some folks are to misinterpret benchmarks (and assign malice to the whole community), it is probably better to continue looking into it.

[1]: https://ark.intel.com/content/www/us/en/ark/products/120474/...

[2]: https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...

josevalim··on Ask HN: Why Isn't Elixir More Popular?
> I’m still looking back at Elixir with nostalgia, wondering if I’m going to have a chance to go back to it and really make it an extension of my arms/brain like Ruby had become.

You should give it another try. :)

You jumped in quite early, when we were still collectively figuring out what it meant to build Phoenix applications, and many things were in flush back then. You probably went through Ecto 1 -> 2 -> 3 and Phoenix was migrating to contexts.

But I also have to say there was a lot of FOMO in relation to OTP back then: people felt they had to build these amazing supervision designs, otherwise they were not using the language correctly. But the truth is that they are building blocks for frameworks and certain libraries, in the same way threads/mutexes are for other languages. Of course, GenServers are higher-level, more expressive, and bring more properties, but the overall idea is that they are about infrastructure, and not design. Phoenix, Ecto, Broadway, etc should be spawning 99% of the processes that you need for you.

josevalim··on Ask HN: Why Isn't Elixir More Popular?
You don't have to learn the Erlang language (and this is commonly echoed by the community). At a certain scale, you have to learn the runtime, but this is the same for any technology. You have to learn JVM if you are doing Kotlin, there are plenty of discussions on V8 internals if you are running Node, etc.
josevalim··on Ask HN: Why Isn't Elixir More Popular?
> And they say "Oh we didn't care to put much effort into it", Yet Jose Valim himself tried to work on it and didn't fix it.

This is inaccurate. I have started looking again into solutions only last week [1]. My suspicion was always the database pool size was too small but, when I tried to contribute 4+ years ago, fine tuning was hard because it took too long, so I didn't pursue it further [2].

My discontent with the benchmarks is that they are not measuring what people effectively run in production. Since you mentioned Rails, here is how a Rails application looks like:

https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...

But almost nobody runs a Rails application like that in production.

And then you look at the configuration of most stacks and they have been explicitly fine tuned to match exactly the concurrency traffic and patterns that the benchmark sends through. But, for most web applications, your web traffic is not homogeneous and you don't have a predetermined number of concurrent requests.

I still believe those benchmarks are not indicative of what you will actually see in production. Most companies who have gone from Rails to Phoenix, for example, report a 10-20x reduction in operation size and costs. But it is clear at this point people put way too much stock on these benchmarks. The irony of it all is that, if someone copies these setups into their actual applications, they will most likely perform worse. Oh well.

[1]: https://github.com/TechEmpower/FrameworkBenchmarks/pull/9302

[2]: https://github.com/TechEmpower/FrameworkBenchmarks/pull/5432 - here you can see me increasing the database pool to 40... but most benchmarks today run with 512-1024 connections (which, once again, is most likely not what you would do in prod). In any case, we need to bump our numbers accordingly.

josevalim··on Typing lists and tuples in Elixir
Thanks for vote of confidence!

We need to type every data type and every function, so the type system will be rolled out over a long period of time.

The 1.17 release meant that we now have a gradual type system, which runs in every code being compiled, but it only supports a handful of types (including the dynamic one). The full list of supported types and examples of typing violations it can now detect is on the announcement: https://elixir-lang.org/blog/2024/06/12/elixir-v1-17-0-relea...

There is no support for type annotations, that comes in a later stage. The overall stages have been described in an earlier article (and I believe also in the paper): https://elixir-lang.org/blog/2023/06/22/type-system-updates-...

josevalim··on Typing lists and tuples in Elixir
We (Elixir) would rather define lists on top of non empty lists:

    list(a) = empty_list() or non_empty_list(a)
So you should pass non-empty lists everywhere a list is expected. But you can’t pass a list where a non-empty one is expected.

But overall, you are right: our concern is exactly all of the extra function calls that may now suddenly become necessary (and the tension mentioned in the article). We will review our design decisions as we keep on rolling out the type system!

josevalim··on Soft Deletes with Ecto and PostgreSQL
Author here.

This can be addressed with PG views. You create a view where deleted_at IS NULL and now you query the view instead. Furthermore, if the view is “updatable” (which is the case when it only has filters), you can also do updates and deletes directly against it, and it all just works. So as long as your ORM/query builder can work with views, you are good to go.

I understand many devs want to avoid moving more logic to the database (I also personally avoid stored procedures and similar) but views are quite declarative and simpler to manage than indexes.

josevalim··on Soft Deletes with Ecto and PostgreSQL
Author here.

You are right, the main downside of the approach in the article is that you have to remember to filter the deleted_at column on constraints and consider how foreign keys cascade.

About triggers, we do link at the bottom to an article that uses them: https://brandur.org/fragments/deleted-record-insert

Since I am exploring a sync engine, having a separate table for deletes means I’d need to run additional queries when catching up, which led me to explore the single table approach. Let’s see how it evolves.

josevalim··on Elixir Anti-Patterns
Elixir macros are hygienic, so you need to be more explicit if you want to override variables as above. You also need to require macros before using them, but once you do you can kick all the puppies!

There is also a guide on meta-programming anti patterns: https://hexdocs.pm/elixir/macro-anti-patterns.html (and unnecessary use is one of them).

josevalim··on Elixir 1.17 released: set-theoretic types in patterns, durations, OTP 27
Sorry for the late reply but I wanted to double check before following up. And I am glad I did because I was told my first response was inaccurate. :)

It is fair to call both TypeScript and Elixir as set-theoretic type system. The big difference is that Elixir's implements semantic subtyping. Here is the paper you can use to dig deeper. I hope it helps: https://www.irif.fr/~gc/papers/lics02.pdf

josevalim··on Elixir 1.17 released: set-theoretic types in patterns, durations, OTP 27
Hi elbasti, sorry for the late reply. I didn't find your comment critical at all. I appreciate the feedback, especially when it allows us to improve things.

We should always improve the learning curve, regardless of its steepness! Any work in this direction is always welcome. I don't have anything in mind at the moment but if you do stumble upon anything, feel free to PR. :)

josevalim··on Elixir 1.17 released: set-theoretic types in patterns, durations, OTP 27
Thanks for the link. This is my fault because the sentence is ambiguous. Where it tries to explain that "you need to remember to perform auth checks both", it rather means that you need to protect controller routes and LiveView routes the same way, but for a single LiveView, you don't need to do both. I will try to clarify it!

If you have other footguns in mind, feel to shot me an email at jose dot valim on gmail!

josevalim··on Elixir 1.17 released: set-theoretic types in patterns, durations, OTP 27
While both Elixir and TypeScript are structural, they are different type systems. A set-theoretic type system is not one that has unions, intersections, negations, but rather one where the foundation of the type system is represented on top of unions, intersections, and negations. Even relations such as subtyping, compatibility, etc. are expressed as set-theoretic. We also have a very different approach to dynamic/gradual typing (which is sound): https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...

I recommend reading Giuseppe Castagna's work for those who want to dig deeper: https://www.irif.fr/~gc/

josevalim··on Elixir and Machine Learning in 2024 so far: MLIR, Arrow, structured LLM, etc.
That's precisely my point. Because Chris runs it in production, it doesn't mean the project is production-ready for everyone. And the fact you ran into roadblocks, doesn't mean they are "mostly for hobby" or are "half-baked". Your initial comment lacked this nuance and rushed into generalizations, which I did my best to clarify.
josevalim··on Elixir and Machine Learning in 2024 so far: MLIR, Arrow, structured LLM, etc.
I have always considered helping the community grow into a diverse ecosystem to be my main responsibility (the Python community being a great example here).

This particular effort started because some people got together and realized that we could do it! Do it in a way that felt part of Elixir and not just a bunch of bindings to C libraries.

We honestly never had the expectation that we had to beat Python (otherwise we would simply not have started). Early on, we were not even sure if we could be better at one single thing. However, 3 years later, we do have features that would be quite hard or impossible to implement in Python. For example:

* Nx Serving - https://hexdocs.pm/nx/Nx.Serving.html - allows you to serve machine learning models, across nodes and GPUs, with concurrency, batching, and partitioning, and it has zero dependencies

* Livebook - https://livebook.dev - brings truly reproducible workflows (hard to achieve in Python due to mutability), smart cells, and other fresh ideas

* A more cohesive ecosystem - Nx, Scholar, Explorer, etc all play together, zero-copy and all, because they are the only players in town

Of course, there are also things that Python can do, that we cannot. The most obvious ones being:

* In Python, integration with C code is easier, and that matters a lot in this space. Python also allows C to call Python, and that's just not possible in the Erlang VM

* Huge ecosystem, everything happens in Python first

At the end of the day, what drives me is that the Erlang VM offers a unique set of features, and combining them with different problems have historically lead to interesting and elegant solutions. Which drives more people to join, experiment, run in production, and create new things.

josevalim··on Elixir and Machine Learning in 2024 so far: MLIR, Arrow, structured LLM, etc.
Correct. When I did the presentation, I did mention we attempted to type Elixir and failed. Therefore, the idea of saying the language was "complete" was precisely to signal the community that most of the work would happen in the ecosystem and not by directly changing the language. Especially because:

* The language was designed to extensible - see how Numerical Elixir can compile Elixir to the GPU and it didn't require changes to the language (we improved the language based on some needs but it was never a blocker)

* A lot of the hard work that makes Elixir better every year is actually happening on the Erlang VM, which we mostly get to leverage by doing nothing (for example, Elixir "got" a JIT since I said we were complete and we had to change nothing)

I'd say the definition of "completed" still holds, because we haven't announced changes to the language yet, but if the work on the type system succeeds, then we will introduce a new typing annotation, which is definitely big enough to void the label after 7-8 years.

josevalim··on Elixir and Machine Learning in 2024 so far: MLIR, Arrow, structured LLM, etc.
Thank you.
← PreviousPage 3 of 15Next →