Show HN: A Travis CI Elixir client
github.com
github.com
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.
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.
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.
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.
ES2017 / ES8.
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!
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.
The biggest thing holding me back is really just picking a project and getting some momentum going on it.
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.
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).
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'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.
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 :)
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).
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 :)
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"
else
baz = "goodbye" # no bar!
end
Could and should be rewritten: {bar, baz} = if foo == :foo do
{1, "hello"}
else
{nil, "goodbye"}
end
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
# ...
end
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.