Hacker News new | past | comments | ask | show | jobs | submit login
Elixir for Ruby developers: the three most important differences (phoenixonrails.com)
175 points by arrowsmith on July 23, 2023 | hide | past | favorite | 76 comments



The article section about interpreted vs compiled is incorrect.

.exs files are compiled just like .ex files are. The only difference between `elixirc` and `elixir` is that the former creates an artifact on disk, and the latter does not. See https://medium.com/@fxn/how-does-elixir-compile-execute-code....

Also, Ruby is as compiled as Elixir is.

Compiled vs interpreted is blurred the moment you compile to bytecode run by a VM. An artifact is not a fundamental difference.


To add to your excellent points: Elixir is compiled ahead of time while Ruby bytecode is generated as the code is loaded, which is probably the source of confusion in this case.

This leads to differences such as Ruby meta-programming happening at runtime, Elixir’s at compile time. The Elixir compiler (the Erlang compiler really) can also afford to do more work at compile-time which then leads to different approaches at the JIT level too.


I'll add that Ruby meta-programming has 2 pseudo-phases(at least in my mind): load time and 'true' run time. They're both technically run time, but smth like a has_many method in rails ActiveRecors runs once when the code is loaded, defines some other methods and doesn't run again while the process is running, normally.

You could also be defining methods pretty much whenever, for instance in response to user input, mutating the state of the process from that call onwards.

The former is much, much more common than the latter.

e. A blurring of the 2 is something like ActiveRecord defining field accessors dynamically once a DB connection is eatablished. You connect to the db and now your User model has email, first_name etc methods, unless you'd defined them already.

I'm guessing nothing quite like this could exist for Ecto(I vaguely know this isn't a super good comparison, Ecto is quite different from AR from what I remember).

That being said even that would still happen as part of a prod app's 'loading' phase so to speak rathet than during a request cycle.


Another difference between the two comes from Ruby having open classes, so there is no callback that says "no further changes will be made to this class". This means you need to postpone some meta-programming to certain events, like Rails telling the app has done initializing, or until something is invoked for the first time.

So you are right there are distinct phases but they are established by convention and practices. And sometimes it is different between dev and prod (lazy loading vs eager loading). Or at least it was back then. :)

> I'm guessing nothing quite like this could exist for Ecto

Correct. :)


This is a good point because in elixir metaprogramming you can actually have some things happen at compile time. The frameworks use this for performance in a few places.

So if you had different behavior for ex and exs that would cause developer confusion.


Yeah, if you don't want the code to run when you're compiling it, don't have it do anything until some function is called.

And it would be the same as in another language if you run a program that has no side effects. For instance "python abc.py" will gladly run a abc.py that is empty or has "class ABC: pass" in it.


I stand corrected! I've updated the article now (and have added a link to that informative Medium post.)

Thanks for keeping me factual.


IMHO a more fundamental difference is that because of pattern matching and pipelines you'll write code in a very different way and any expectation that Elixir and Ruby are similar will evaporate before the end of the very first day.

However many Class.method names in Ruby's standard library have a match as Module.function in Elixir, by design, to ease the migration of developers. Language developers take note, copy from the language you want to lure developers from.


I wrote a similar article for Pythonistas: https://joyyo.app/elixir-for-humans-who-know-python


Nothing personal but I hate blog posts without a date of publication of said post.


Same here. And maybe mention the versions of the languages at the moment the article is written. Some of them add/remove features with each release.


Looking at archive.org it could be from today

https://web.archive.org/web/20230000000000*/https://phoenixo...


There's a date at the bottom of the page.


I don’t see one?


> Last updated • Jul 23, 2023

after the Learn Phoenix Fast red box.

However the convention is to put dates at the beginning of an article. Readers shouldn't have to make an effort to look for them.

This reminds me when I had to look for how to check for inequality in Lua after I wrote != and it didn't work. <> didn't work too.


Oh, I was talking about the joyyo.app link that Mizza posted.


Ok.


I'm curious, what are the chances to python you don't like?


I would give seasoned Ruby engineers a lot more credit than this article expects. A lot of its examples are just…functions?

A lot of the things that are ‘elixir’ are what I learned when I was writing PHP and modding forums.


A lot of Rails developers don't actually know Ruby.


Well a lot of developers think Rails is a programming language. :shrug:


Really???


I must have missed where they discuss the type system. They go to great length to explain that there aren’t classes, methods, etc. and showcase modules such as String, but it’s not clear to me how you know what can be passed to String.whatever.


Elixir doesn't currently have a type system built into the compiler. There is a separate package Dialyzer which does type checking but it would be fair to characterise it as a bolt-on rather than part of the core tooling. Work is now underway[1] to create a type system based on "set-theoretic types" though this is still considered experimental and may never be added to Elixir.

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


My experience with Dialyzer wasn't great a few years ago. The error messages are often counter-intuitive, to the point where hating dialyzer became a meme at work.


Trying Gleam that also runs on Erlang VM but has static types, a fast compiler with Rust-style elaborate error messages made me hopeful about how much nicer this experience can be when static types are added to Elixir too.


I’ve always found dialyzer errors to be quite straightforward and easy to understand—the challenge is finding where a poor typespec originates in a codebase (or dependency) with poor typespecs.


The type system has moved from the research phase to in development: https://elixir-lang.org/blog/2023/06/22/type-system-updates-...


The basic data types work with modules of the same name generally. String module functions work on UTF8 but strings. If you aren’t sure you can run the h() function in an ice shell on the module and function, e.g. h Kernel.+ or whatever.


I imagine they expect that if you’ll be using Elixir at any capacity, you’ll have the docs in front of you, which details all the functions available in the modules, as well as the parameters they expect.

https://hexdocs.pm/elixir/1.15.4/String.html


No classes, but there are types. When looking at the docs (which you'll do often) you'll see the doc.

I say you'll be in the docs often because of things like

Enum.map which returns a List not a map

Enum.map_reduce takes either a List or a Map, but returns a List.

Kernel.get_in takes a Map and a List


this is missing the big one:

@ does not work at all the same way in ruby vs elixir, it's not even close

I would probably also put in

"Please for the love of God don't try to turn a genserver into an object"

Because many ruby programmers will try.


I love Elixir and I have worked with it in past 2 years. (Specially in China)

But I have been free because the company was broken. I really want to keep writing Elixir, are there any remote job opportunities here?

P.S. I also have some experience of React/Nextjs, Ruby and Scala.


Here's something that might interest you :) https://remote.com/openings


Mostly the dev cycle in Elixir is different. There are no "binding.pry". We're back to putting logs in the code.


Use `dbg` for prying. You can also use `break!` in an `iex` session to manually set breakpoints without modifying your source. Additionally, you can use Erlang’s `:debugger`for visual debugging, and `:observer` for understanding the entire VM when prying is not enough.


`dbg` is relatively new but [IEx.pry](https://hexdocs.pm/iex/IEx.Pry.html) has been around for a long itme


This is not nor has not ever been true.

Now, you've got `dbg()` and running whatever you're running via `iex -S <whateveryouweredoinghere>`.

Prior to `dbg()` you had `IEx.pry()`.


What kinds of cool things are people doing with Elixir these days, where they feel it's a really good fit for the problem domain?


Just wrapped up a live translation feature that watches an HLS live stream, live transcribes with whisper and then translates into 18 languages. Heavy use of OTP: supervision trees for each stream, ports for managing ffmpeg and receiving audio, async tasks for concurrently submitting chunks to translation API, etc. Transcription and translations provided in real-time via LiveView to about 4,000 viewers.

Little over 3 weeks from `mix new` to the live event. Not sure how I could have done it so easily without OTP and LiveView.


That's really cool. Managing ports is something I've done in elixir yet, but managing ffmpeg through elixir could be very useful for a few things in my current area of focus.

Can you share any details of how that works? E.g. do you operate on a single HLS chunk at a time, or can you get ffmpeg to separate a continuous audio stream, etc?


davidw is right that ports are somewhat limited, but I haven't had much trouble doing what I need with FFmpeg in particular. I used the bash wrapper from the docs for Port [^1] and it has worked well.

When a stream starts I start a supervisor that then starts a GenServer to manage the port. On init a port is started for FFmpeg (using the above bash wrapper) with args that sends 16-bit PCM audio back to the port through the `handle_info/2` callback.

When a new live HLS segment is downloaded by FFmpeg the entire segment's audio is sent to the GenServer all at once (could be a few handle_info/2 calls, but it happens quickly). Since I want to work in small fixed chunks, I send the segment's audio to an AudioBuffer GenServer (started as a sibling under the same supervisor). This buffer uses binary pattern matching to segment the audio in chunks exactly 2 seconds long while keeping any remainder in the GenServer's state for the next buffer event. I then send the chunks to another ChunkBuffer GenServer that pops chunks at 2-second intervals for processing.

Since everything is supervised, if (when...) FFmpeg crashes the supervisor just restarts it. Meanwhile, the audio in the buffer is still processing and nothing goes down. There might be a duplicate word or two in the transcription if the restarted port processes a segment again, but everything keeps running smoothly.

For even more reliability, I have the application running clustered across four locations in the US, EMEA, and APAC using libcluster[^2]. The stream supervisor is started under a Horde.DynamicSupervisor[^3] with a custom distribution strategy. The strategy prefers the region closest to the company HQ, but if it goes down, the processes will be restarted in another region.

[^1]: https://hexdocs.pm/elixir/1.13.4/Port.html#module-zombie-ope...

[^2}: https://github.com/bitwalker/libcluster

[^3]: https://github.com/derekkraan/horde


Absolutely fantastic write up - thank you so much. I will go away and do some further reading!


https://github.com/saleyn/erlexec is pretty good for handling external processes. The builtins aren't quite there if you have more complex use cases.


We built a social network for social justice work, with posts, likes, shares, and the like. Elixir and Phoenix are a really good fit for the problem domain i.e. lots of concurrent users, high scalability via built-in balancing and queuing, the "let it crash" concept for lots of backend APIs such as image processors that sometimes fail. And LiveView is superb for fast interactivity.


Got a link?


I'm building a multiplayer real time web based game (io style, 2 ticks per second, so not 64 like a true realtime game). It seems to work quite well so far. I'm using https://github.com/woutdp/live_svelte for the frontend with pixi.js for the rendering.


How's the svelte + LiveView integration been working out for you thus far? I still find a lot of issue with trying to do a bit of work in JS-land for things where I want to have no latency, but find it can sometimes be a challenge managing everything through various LV hooks.


I'm biased since I made LiveSvelte, but it works quite well! I made a video to showcase some of its features here: https://www.youtube.com/watch?v=JMkvbW35QvA

Working with it in a real project now, instead of toy examples like in the video, it works really well for when you have highly complex components that rely on quite a lot of local state.

What you'll find is the developer experience is quite nice with LiveSvelte compared to JS hooks.


Thank you for your contribution and for this video! Going to give this a try in the near future.


This is quite cool. Can it enable optimistic UI updates?


Yes :)


It's particularly good when you have concurrent users. Also, if you just have a web app that won't need other clients then LiveView gives you SPA-style development without the need to write a web-API layer.

Personally, I use it for any web app I'm making. It's a very simple language at its core and is just as good for tiny things as it is for large ones.


I know a lot about what the BEAM is good for, I'm curious about actual things people are doing with it.

The last time I used it seriously, we used Erlang as the high level language for these: https://www.icare-world.com/us/product/icare-eidon/ and it was a great fit for that kind of semi-embedded environment.


Ah, well, personally I'm not doing anything other than web apps.

A slightly cool thing its enabled is that I have a monolith with a single microservice. All the microservice does is process messages from the main app to process images, upload them to S3, then notify back when it's done. The cool thing is that they can be run on two separate connected BEAM instances communicating via the built-in message passing with no need for an HTTP API. I haven't deployed this, yet, though.

Maybe that's still not what you're looking for but I was really happy when I figured out I could do that :D


There's Nerves for semi-embedded.

https://nerves-project.org/


Anything soft real time is a great fit. We push data to clients every 3 minutes or when specific events arrive. It was super easy to build with out of the box tools and just works.


Wrote the basics of a MMO emulator for metin2 in it with horde for multi node Game Server distribution (per map a process) and ranch for connection pooling, basic stuff like login and walking around, and having monsters spawn (it's a hack n slash MMO) multiplayer and PvE combat works, stopped at that point to further develop it deeper, since I kept musing about to advanced feature wishes like behavior trees for monsters /bosses

Elixir felt all around like s perfect fit for it , apart that some faster deep mutations likely would have been had to be ported to rust or c because the way I wrote it having 250 players on a single map walking around and seeing each other close by caused huge latency but no crashes Scaled well across multiple machines and lots of players as long as they did not all bog down the same map process :) Didn't even had to think s lot about how I structured it for the current state of development it was basic rapid iteration with mostly live recompilation on changes without having to restart anything https://gitlab.com/zen_core/zen_core


I built a real-time updating internal test bed reservation system for the company I am employed by. It’s mostly a LiveView app with some external modules and js hooks scattered throughout.


I’m building a product in the media world. Elixir powers our webserver, that’s connected to NVIDIA Jetson devices, all using elixir for coordination and talking over UART to serial components. We also plan to do vision and ML workloads in the applications in the near future


We use Elixir as the backend for a lot of services that need reliability and performance in healthcare specifically on the pharmacy side. One such use case is pricing medications at the point of prescribing to enable conversations between a provider and a patient.


Anything with concurrency or where you'd normally use LiveSockets, but man, I like using it for everything.


I love Elixir, Phoenix and LiveView. A drawback is the size of the community, and as a function of that, availability of libraries for various things. It is not bad. It is also not great. That said, the community itself deserves high marks for their willingness to help and do so in a friendly manner.


The community is small, but that means that a reasonable question on elixirforums (not a stateless discord/slack, pun intended) has a good chance of:

1. receiving an answer from a core contributor (valim, McCord, etc)

2. Having the answer become part of the documentation for the language or library

As an aside, the elixir community has a different didactic approach to many others: the documentation in elixir is genuinely instructive (vs a descriptive reference).

As an example, I've been able to download the elixir, Phoenix, and ecto docs and work on an app entirely offline, on an airplane ride, and never felt like I needed to Google anything.

A common elixir answer to "where are all the blog posts" is "read the docs. No, seriously."


> A common elixir answer to "where are all the blog posts" is "read the docs. No, seriously."

This is indeed very common the forums, ha. Also common is for people to actually then go read the docs and be like, "Oh crazy, I'm not used to this!" Not to over-sell the quality of the docs, of course they aren't perfect but they really are particularly good.


As a convenient side effect, GPT-4 is actually quite good at coding in Elixir. I tasked it once to find a bug in an Ecto query and it even improved the query‘s structure. I‘d been worried that, as a small-ish language, Elixir would suffer from bad LLM support, but that’s not the case.


My experience has been the total opposite. It can give simple answers but makes up total nonsense for anything non-trivial.

That said, with Ecto I just ask it to give me the SQL query. From there it’s pretty easy to manually write the Ecto equivalent.


Are you using GPT-4 (the paid version) or GPT 3.5?

3.5 is as you describe. 4 isn't perfect but it's good enough, in my experience.


That was right after the release of GPT-4, when it was slow but good. Haven’t gotten around to much Elixir coding since, it could be that they crippled GPT-4 too much to be of use in the meantime.

Oh and the chat is better for me than the API. API sometimes gives me the complete opposite of what the chat (correctly) gives me.


Thanks to pointing that out ( about the doc )

I will take your word for it and try.


Too late to edit this comment, but as evidence: at time of writing this, the top comment has a very thoughtful reply by...Jose Valim (creator of the language).


I worked on a Phoenix project for a few years. We had to write our own libraries to access some services from Google and Stripe which have libraries for other languages but it was not difficult. We have to implement only the few HTTP calls we need and we can choose the functions that suit our application instead of learning somebody's else tool.


GPT-4 is excellent at this - give it code from an existing SDK in Ruby (or whatever) and tell it to rewrite the functions you need in Elixir and it'll get you at least 90% of the way there.

The other day I used this to create an Elixir wrapper around, appropriately enough, the OpenAI API.


It's also great at "here are the so and so docs, write an adapter for Elixir"


I am writing a lot of the code that might be found in libraries for my project; I'm doing it mostly because: third party, open source libraries really need to be treated as hostile code until they're vetted... and then will continue to need vetting with every update. For a fair number of uses cases, writing the library is less than or as labor intensive as a good audit and writing them in Elixir tends to be pretty simple. For the larger more complex and essential libraries, there are solid, fairly well endorsed packages out there (they still need vetting though).

Of course, there's the option of taking an appropriately licensed open source license and making an internal fork... not expecting to really receive updates from upstream after the point of departure.


It's indeed relatively small, but what it may lack in numbers, it makes up for in quality. It's one of the most dedicated, helpful, and friendly communities I've come across.

I've personally been surprised by a pro of having a more intimate community: in my experience, this leads to a higher signal-to-noise ratio in discussions and resources. It feels like what I need to get online is more "condensed".




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: