Hacker News new | comments | show | ask | jobs | submit login
Show HN: A Travis CI Elixir client (github.com)
50 points by kddeisz on Jan 10, 2017 | hide | past | web | favorite | 23 comments

I haven't seen Elixir mentioned here in awhile. As it's the next language I'm interested in learning -- how's it coming along? How are people feeling about it after having more time with it?

I'm still quite a n00b at Elixir, but it's already making me annoyed by Ruby and ES6, the other two languages I'm using day-to-day. Ruby mainly because of performance/concurrency issues, ES6 because the syntax and dependency management are a mess in comparison and the (callback? promise? async?) clusterfuck.

Elixir is really in a sweet spot where I just have no major complaints about the language or platform - it all fits together, makes sense and works well for a wide range of useful projects. Most published libraries also seem pretty high quality for their young age, and the community appears to be generally friendly and growing/maturing rapidly.

Callback hell is gone with promises, and async/await is shipping in ES7.

what's wrong with es6 syntax and dependencies?

I haven't used elixir, but I see a lot of discontent with JS. Almost like its trendy to hate JS and constantly complain about JS fatigue.

There are some issues indeed, but I find they're generally exaggerated.

I'll bite. I like Elixir, but that's not why I think JS's concurrency story is a fricking joke.

JS allows you to say, in essence: "This code is allowed to run somewhere else." That's your hammer, and you'd better squint at your problem until it starts to look like a nail. This feature can be dressed up to look like callbacks, like promises, like streams, like async/await -- but if the low-level interface isn't what you wanted, you will not get what you want.

I mean, it's better than Python or Ruby, where you don't even get to point out bits of code that can run on the side. But it's still kind of crappy.

Experience in other languages with more control over the concurrency model often leaves JS not looking so good in comparison.

That leaves aside many other factors that make writing safe, concurrent code in JS harder than it has to be: for example, immutability of data, crash supervision, server clustering, shared-nothing architecture. JS doesn't have good stories here either.

>and dependencies?

My gripe with node dependency management has been the lack of a .lock file, until yarn was released. Also, the ecosystem has kind of a mess of transitive dependencies, which are sometimes in practice not resolvable to a working set of modules for a given dependency list.

>Callback hell is gone with promises, and async/await is shipping in ES7.

Sure, this particular problem is "solved" but due to the single threaded nature of js, even if you may async code look friendlier, its still the only tool you have for parallelizable tasks. And some of those tasks are a poor fit for async event loops. Tools like Elixir give your more concurrency tools, so that you don't have to shoehorn everything into async functions.

>async/await is shipping in ES7.

ES2017 / ES8.

I've been using Elixir for a little over a year now, running in production at modest scale (about 20 servers) for the last eight months. It's been a joy.

I've used a handful of languages and approaches to concurrency: from multi-process Perl-and-Fortran systems shouting at each other through named pipes, to Ruby + Resque, JRuby + threads, JS + callbacks/promises/streams, and Scala + Akka. Elixir has been easier than anything I've worked with before.

I like the quote on this page by rtdsc: "I think more about passing state around and making it explicit." Elixir makes shared state just difficult enough to achieve that you don't do it by accident, and the fact that everything is immutable means that you can trust state once you have it. This kind of by-default hygiene is a godsend when writing highly reliable systems.

I could go on and on about nice things in the language. I guess the best thing I can say is that it quickly becomes one of the least interesting parts of the stack. It just works. Wish I could use it everywhere!

I've used many languages in my lifetime, most because I've been required to for work or school, and I was perfectly content with the likes of JavaScript, Java, C, and Python. However, it wasn't until I started using Elixir that I realized how much I enjoyed programming with this language, and at the same time how I never really enjoyed programming in all of the other languages that I've tried. The multitude of major and minor gripes I had with Node, for instance, quite clearly now form a cohesive image in my head of an experience I didn't enjoy in retrospect.

Elixir forces me to think about what I'm doing, and how I'm doing it while I'm programming. I'm not really sure how, I'm sure it has something to do with the functional aspect of the language, but I'm keenly aware as I am writing code when I'm doing something that could be improved, something which could be optimized. As I prototype a new feature, I'm already thinking about how it can be improved. That's what I love most about Elixir, it's a language which is constantly facilitating me to write better code.

I've been learning it an enjoying it so far. It's going a little slow for me due to my limited time to commit to it and being fairly different from OOP, but there are a ton of really powerful concepts and features in it.

The biggest thing holding me back is really just picking a project and getting some momentum going on it.

> my limited time to commit to it and being fairly different from OOP

If it helps, you can think of Elixir/Erlang as extreme OOP with processes as objects. Objects are truly isolated and won't break each other by overwriting each others' memory. They communicate only by messages. Inheritance doesn't work there as well. Composition is different. Perhaps supervision hierarchies provide that.

In general I found learning Erlang that I became a better programmer in other languages as well (Python, C and others). I think more about passing state around and making it explicit. Using functions more than deep class hierarchies. I often find it makes the code more modular, debuggable and testable.

That's great to hear! Functional programming makes sense to me, but I'm sure it'll be a whole 'nother story once I start learning.

What's holding me back from learning it at all is a side-project I spend most of my free time maintaining. Labor of love (cause it sure ain't the money).

I love it! Dave Thomas' Programming Elixir 1.3 is the book to get if you're just starting out.

I'm learning Elixir at this moment.

As Elixir is built upon Erlang, I found "Programming Erlang, second edition" by Joe Armstrong to be a gentler introduction.

After 20+ years of OO (and my mental models built upon C and assembly), functional programming and Erlang's model built upon immutable variables, pattern matching broke my brain for a few days. I just didn't get it.

However, I didn't stop. I am now bouncing between both books and it's beginning to sink in.

I actually have that one and Programming Phoenix, I'm bouncing back and forth between them right now

It's my primary language nowadays; in situations where I'm able to choose the tech stack, Elixir is the default. I believe it's a sufficiently-mature ecosystem for production use at this point (as opposed to, say, a couple years ago when its maturity was still heavily dependent on Erlang; Elixir's starting to really hold its own, which makes for an even stronger mutual relationship between it and other BEAM languages, I think).

I'm starting to branch out into learning more about interfacing with external code (both via NIFs and via ports). I'd dabbled in it before (see also: the "rubix" package on Hex), but at the time I didn't really fully understand the conventional approaches; now, with a couple years to get a feel for the OTP way of doing things, I reckon I could make some significant improvements. I'm also using Elixir as a means to learn Rust - specifically, by toying around with writing NIFs in Rust. Rustler (http://hansihe.com/rustler/rustler/) looks to be a good start, though I haven't tried it yet.


EDIT: YMMV, but I ended up learning basic Erlang (via "Learn You Some Erlang For Great Good!") before I even knew what Elixir was. I'd recommend following the same approach if either of the following applies:

* You're not sure what Elixir offers over Erlang beyond having a different syntax

* You're struggling with cases where Elixir's "declarative-in-imperative-clothing" syntax is catching you off guard (i.e. because it's tricking you into writing code imperatively rather than declaratively)

In either case, Erlang will give you a baseline and help establish a more functional mindset. Elixir will then be a clear transition, and a lot of Elixirisms will make a heck of a lot more sense.

Again, though: YMMV.

Have you had any bad performance surprises ? To me, knowing that beam has poor pure computation performance made me look away. I don't want to build a magnificient immutable actor architecture only to end up writing parts of my system in C.

I've had a few here and there, though usually the sorts of performance bottlenecks I encounter are resolvable with concurrency (which Elixir makes very easy). I don't really run into many situations where something is both inherently sequential and prohibitively slow (but I came from much slower languages like Perl and Ruby, so I ain't the best judge).

My interest in ports/NIFs (and Rust as a specific implementation platform thereof) is less for performance and more for doing things that aren't really easy in a VM-driven architecture, like dealing with low-level device access or interfacing with native libraries/APIs.

Basically: I'd try it first before dismissing it for supposedly-poor performance. There's a reason why Erlang is considered to be a "soft real-time" language, after all :)

Well, the other reason is that i feel the need for things like full OTP has decreased a lot since we've been using containers and cloud services that offer autoscaling, load balancing and health checks...

Sure, but those are (IMO) pretty clunky and broad-stroked compared to OTP, which handles quite a bit of that (and then some) on a much more intelligent and fine-grained level. Microservices are probably as close as one can get to matching OTP without basically reinventing OTP, and microservices often involve a lot of complexity (that's often in turn managed by tooling around things like Docker, but still).

OTP is also not tied to a certain cloud provider; a couple of Raspberry Pi boards duct-taped to a WRT54G can use the same codebase, deployment practices, and application structure as a bunch of EC2 instances in a VPC with an Elastic IP. This is usually not true of "cloud" tools (things like Docker and Cloud Foundry are helpful here, granted, but a lot of the tooling around load balancing and such is often provider-specific).

Erlang, and hence Elixir, was never optimised for raw computation, it's all about the massive concurrency (we can handle literally millions of processes), fault-tolerance and scalability. This is not surprising as no language is good at everything, you have to pick and choose what you is most important. In most systems different parts will have different requirements so I think it is perfectly reasonable to use multiple languages in a system. Erlang is very good at communicating with other languages/systems.

> You're struggling with cases where Elixir's "declarative-in-imperative-clothing" syntax is catching you off guard (i.e. because it's tricking you into writing code imperatively rather than declaratively)

Would you mind providing an example of this type of syntax? I'm not sure I understand what you are talking about here and I'd appreciate some context to help me understand :)

One thing in particular I find myself wanting to do sometimes is assigning variables within a case/cond/if. In imperative languages, this is a reasonable practice. In Elixir, the practice is still allowed, so if I'm in "this looks like Ruby" mode, I'll end up resorting to it.

In reality, though, that sort of practice is unsafe, and Elixir's use of pattern matching alleviates the need for this sort of thing.

So, the following code, for example:

    if foo == :foo do
      bar = 1
      baz = "hello"
      baz = "goodbye"  # no bar!
Could and should be rewritten:

    {bar, baz} = if foo == :foo do
                   {1, "hello"}
                   {nil, "goodbye"}
Which in turn could be rewritten:

    def barbaz_for(:foo), do: {1, "hello"}
    def barbaz_for(_), do: {nil, "goodbye"}
    def whatever(foo) do
      {bar, baz} = barbaz_for foo
      # ...
Nowadays, the first version will actually throw a compiler warning and suggest the second version, but but only because of the missing "bar", and it'll still compile either way. Meanwhile, the third version is much more extensible (maybe you want to handle foo == :bar someday in the future?) and reads clearer, IMO.

A more complex example might be the "let it crash" philosophy inherited from Erlang. Elixir looking like Ruby has a tendency to encourage try/catch-like constructs, whereas the more Erlangy/Elixiry way would be to use ordinary message passing and supervision trees (in other words: just because you can throw in Elixir doesn't mean you should).

Another example is the fact that Elixir has for loops (though they're much closer to Erlang's list comprehensions than to imperative-style loops). It's easy to rely on loops instead of recursion, even though recursion gives you much better control (and value-based multiple dispatch / "pattern matching" in function arguments makes recursion perfectly accessible and readable).

Point being, it helped me to ground myself by learning Erlang first so that Elixir would then make sense in an Erlang context. Then I can look at these things and say "wait a minute, there's a more Erlangy way to do this" and do it.

I started newly learning elixir. It makes fun.

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