Hacker News new | ask | show | jobs
by quaunaut 3826 days ago
How ironic, I was wondering just last night when 1.2 would be released.

I'm a newcomer to Elixir, and have really been enjoying it. As a Python->Rubyist, it's been really interesting to finally hit a functional language, and some of Elixir's most basic features just seem crazy in comparison to what I've come from. Some neat examples:

* Pattern Matching. In other words- you don't assign things to variables, you match things. Elixir/Erlang is just doing algebra behind the scenes. I'm sure this is a gross simplification, but it's enabled me to write some really condensed code that still makes a bunch of sense.

* Streams. I know Node developers would laugh at this being a new concept, but I hit Streams when I was doing Node, and I didn't get it. Streams in Elixir feel much more self-evident, and feel much easier to read.

* The Pipe Operator( |> ). This effectively lets you simplify code by just passing results from one thing to the next. For example(taken from the excellent "Programming Elixir" by Dave Thomas):

$ (1..10) |> Enum.map(&(&1*&1)) |> Enum.filter(&(&1 < 40))

=> [1, 4, 9, 16, 25, 36]

So there, it takes the 1..10 range, maps the squares of each element to an array, then filters the array to just the elements that are less than 40.

It's a surprisingly enjoyable language to program in, and best of all coming from a Rubyist- the performance gains are automatic, especially once you grok some of Elixir's crazier(but easy to understand) powers.

-----

The other thing I really like about it, is how friendly the community seems. I feel personally predisposed toward extremely friendly communities- Ember.js was the first community that made me feel like I had a home- and the care with which Jose Valim and the core team treat people, and the general "Give back everything you can" attitude of the community is really just inspiring.

My newest side project was something I dropped because I thought that the hardware necessary would make it not worth the effort of building it, but now I have complete confidence in it.

I encourage you to take a shot at it if you're looking for a fast, functional language with a clear syntax and easymode concurrency.

5 comments

> I know Node developers would laugh at this being a new concept

I hope you're not somehow implying that node developers came up with this concept.

Ah, that wasn't my intention! Just, the Node community is probably the closest to the Ruby/Python crowd in terms of overlap, and what I assume might be reading a post from a developer like myself.
Thanks for the clarification. I figured better to ask you than to hit the 'downvote' button without verification. Happy 2016 :)
It's also worth mentioning that the Node community took a long time to get streams right, and "Streams 2.0" is still poorly documented.

For example, a major feature of the redesign is backpressure support, but it's not at all obvious how it's supposed to work, and there are no mechanisms available in the standard library to tweak it.

Streams also still have odd issues you would not expect in a mature release. (Error propagation, for example, is quite broken in practice; if you string together a series of pipe() operations, emitted errors won't propagate up the chain properly.)

I haven't looked very closely at Elixir's streams, but the fact that they are based on lazy function evaluation makes them inherently better than what Node.js offers.

    $ (1..10) |> Enum.map(&(&1**&1)) |> Enum.filter(&(&1 < 40))
This is the equivalent of this piping op done in terse style JS:

    [for (i of Array(10).keys()) ++i].map(e=> e*e ).filter(e=> e<40 )
This is not as concise as the example you provided but it's still very neat and powerful.
Are list comprehensions still part of ES7? It's strange that Babel removed the list comprehension transformer recently, but I haven't been following closely.

The Elixir pipe syntax is reminiscent of Clojure's ->/->>:

  (->> (range 1 10)
       (map #(* % %))
       (filter (partial > 40)))
It's nice that in languages like Haskell or ML this is trivial:

  infix |> 
  fun (a |> b) = a b
You could modify this to let NONE fall through, etc.
Wouldn't it be "fun (a |> b) = b a"? (call b on result of argument a)?
Oops, that's right.
Last time I checked no, but it's been supported in FF for quite some time now.
Not knowing Elixir at all, what's with all the & operators in your pipe example? Looks very unappealing to me.
This is a shorthand for writing anonymous functions. The &() activates the feature for the expression inside, which can then use &1, &2 etc to apply parameters.

For example `&(&1 + 1)` is equivalent to `fn x -> x + 1 end` .

It's the super terse anonymous function syntax. You could use the slightly more verbose version with named instead of positional params or put a named function in there instead.
We call it the "capture" operator. What it does is it captures the nth parameter of the anonymous function. Like some have already said, the number just signifies which parameter you're making a reference to in the function body. It's terse and comes in handy for ad-hoc stuff. I prefer verbosity and often write Elixir code with the standard syntax and find myself using the capture operator a lot more in the REPL.
It's a shorthand. Once you get tired of the long form, the shorthand will be appealing :P
I hear Elixir is great, but I understood and still enjoy what you described in Scala, so I don't see what makes it unique. On top of this, it runs on the JVM, so you get to use the entire history of Java libraries whenever you need it.

Your example:

>> (1 to 10).map((x) => x*x).filter(_ < 40)

Of course, Scala is an extremely complex language, but I just take the features I feel are useful and/or relevant to what I'm trying to accomplish.

Another up and coming JVM language with similar capabilities to Scala is Kotlin. If it can take off on Android, I think it will become quite popular.

https://medium.com/this-is-not-a-monad-tutorial/interview-wi...

That link mentions some of the tradeoffs that Elixir and Scala would have just due to the VMs they run on.

Thanks for that! The explanation is concise yet easy to understand.
With Elixir, you can use Erlang libraries(or just plain Erlang) right inside of it, very similar to what you describe with Scala!

I've never used Scala personally, and my Java programming was all school-related, so sadly I just don't have the relevant knowledge to explain the differences.

The functional aspects are not really that different. I think the key one is that Erlang has tail call optimization baked in, whereas Scala achieves something close, but not exactly there, by working around the JVM's limitations. Scala's implementation works really well when you have a function calling itself, or two functions mutually calling each other, and that basically supports recursion. But the inability of arbitrary function A -> arbitrary function B -> C -> D, etc, to be used, without risking blowing the stack, does lead to some very simple, easy to understand patterns for control to be unavailable (see Scala actors 'become' below).

From what experience I have (played with Scala, done real dev work in Erlang) -

The lack of inheritance in Erlang/Elixir is a difference. It's one I like (and from what I've seen, the more a person uses Scala the less inheritance they end up using), but others may not.

Actors behave similarly between the platforms, but have a few differences. Whereas in Scala actors (by this I mean Akka) struck me as reactive, in Erlang they struck me as proactive. What I mean is that Scala, at least when I looked at it, it seemed like you create an actor system, and then actors in it, and they do nothing until you send a message. In Erlang, you just create a new process and at any time it can pause to receive a message. That is, the 'actor' is just a thing that does work and has a mailbox, rather than this reactive interconnection of things. Fundamentally there may not be much difference, but I found the abstraction in Erlang easy to initially grok, and playing with Scala led me to some irritation and difficulty in structuring my program. That may have been just due to my own expectations rather than anything innate, but just pointing out, they are a bit different.

Scala actors also have the idea of 'become' when you want to change an actor's behavior (and in general the behavior of an actor feels more rigid to me). In Erlang, an actor is just a process running whatever code. Between that and tail call optimization, the idea of 'become' doesn't exist; if at the end of the function that is executing in the actor's process I want to perform the same action I just recurse; if I want to perform a different action I call a different function. That makes for a very simple mental model to work from; I start an actor and it immediately starts executing (though that execution might just be "wait for a message"), and it runs pretty predictably until either it gets "wait for a message", hits the end of a function (in which case it terminates cleanly), or dies. If you want it to run the same logic more than once you recurse, if you want it to run different logic you call a different function. So the 'actor' part is nothing special, no hand waving or magic, it just is a single process with a mailbox you can check, and I find that delightful to reason about; in Scala actors felt like special ~things~, that I have to configure and then set off.

Performance is a difference; while the JVM is more performant in just sheer number crunching, Erlang has a memory model that better fits actors; there is no stop the world garbage collection. In general, barring something like Azul (which I have no experience with), you'll see a smaller deviation of latencies in Erlang (useful for web servers and things, where every call taking ~5ms is better than having some calls return in 1ms, and others in 120ms).

Scala borrowed a lot of the distribution and fault tolerance mechanisms from Erlang, but I don't like them as much. They feel a bit more bolted on, and the multi-paradigm mixing means a greater degree of rigor is required to ensure you do things properly. That said, Scala seemed to provide a different level of abstractions for supervision strategies, that I didn't really delve into.

The availability of all the JVM libraries is a double edged sword for Scala; while there probably is a JVM library for whatever you want to do, it also probably behaves in ways that won't play nice with your actors, possibly forcing you to deal with multiple concurrency, distribution, and fault tolerance models simultaneously. In Erlang (in which, I might add, I've found libraries for basically everything I wanted to do, that wasn't something extremely esoteric like talk to a piece of hardware that is only used in (X) business domain...of which there was also no Java bindings, only C), unless it's calling out to external code (NIFs and the like), it will at least be informed by the concurrency model you're using (since Erlang it's actors or nothing), though there may be assumptions and expectations and such that the library makes that you will need to understand or modify.This generalizes to the language as a whole, too; in Scala it's very easy to make tradeoffs that will end up hurting you. In fact, it's practically encouraged, as the language is often sold as a way to slowly move into a more functional, safely concurrent world. The problem I have with that is without being forced to move, a lot of devs won't, and mixing paradigms gets you complexity that is usually unnecessary, and oftentimes leads to you not seeing the benefits you'd have gotten had you limited yourself to one.

Another fault tolerance difference (that won't affect you 99% of the time) is that Erlang has better process isolation. An uncaught exception in one process won't affect another one, unless they've been linked together or otherwise been entwined such that they should cause the other to crash. Scala tries to do the same, and largely succeeds, but it doesn't have the same technical underpinnings due to the nature of the JVM.

The profiling/tracing ability of the Erlang VM is better than the JVM (yes, really). The ability to attach a remote shell to poke and prod your running instance is also flat out amazing; I don't recall if Scala had anything similar. I always found Erlang apps to have a fairly small footprint on my box, whereas my experience with the JVM (though mostly using Java) saw them tending to take a fair amount of resources.

I'd stress the fact that 90% of network interacting Java code is blocking and is therefore not suitable for use with Scala's concurrency model. Worse, it will work fine in testing and small workloads, but quickly you run out of threads in your execution context and find yourself with a broken production system.

Erlang differs for two main reasons. First, BEAM has a scheduler that will prevent blocked processes from tying up OS threads. So even if a library only supports synchronous calls, it will still work without interfering with other parts of your application. Second, everything is culturally designed around OTP (the standard library for concurrent applications). Libraries tend to support async modes in ways that jive well with the rest of your codebase. There aren't really competing standards like Akka vs Finagle in Scala. Again, even if the code does not support async it will still work. Async is just an optimization (not just for performance, but the wide swath of runtime inspection tools work better on async designs).

I'm actually in the process of writing a scraper using Akka. It's quite complex at first glance but I'm hoping I'll be able to use the main features without digging too deep.

I appreciate the in depth comparison between the VMs. I guess I need to dive into Erlang eventually.

Your love for Scala should not prevent you from exploring Elixir. You might discover you like it. And it is not either or...
i write both scala and erlang/elixir professionally

i like the idea of scala's type system (the implementation leaves much to be desired but...) and it's definitely nicer writing process heavy tasks in scala with it's wider access to libraries and the performance benefits of types and the jvm but erlang/elixir are vastly, vastly superior for writing services and long running tasks that are mostly i/o bound

concurrency and parallelism are the core of erlang and everything is oriented around that. there are blocking apis in erlang/elixir but they are mostly implemented as wrappers around asynchronous apis. akka is a noble attempt but it's a very poor facsimile of erlang's actor model