Happy to help anyone out if they're interested in learning!
Happy to help anyone out if they're interested in learning!
Note: I like Clojure, F#, and other functional languages, so that's not a hurdle.
Any interest in qualifying that opinion?
There're other things I don't love, but that's a good chunk. Philosophically, I prefer explicit code to implicit (so, Rails is out). And I prefer functional programming to OOP in general, and find that Ruby greatly favors mutable OOP. I've had a tough time navigating around the Ruby parts of most code-bases I've found, largely due to the implicit nature, and annoying language features that allow clever generation of methods in a way that makes them impossible to search for (delegate ... prefix: true for example).
Superficially, `do ... end` blocks look like English, because they are English words. On the other hand, they don't really correspond to any real grammar construct of the English language. They live in the the uncanny valley where you try to read it like English, but you can't actually read it as such. For some reason, the use of keywords like `def`, `defmodule` or `defmacro` doesn't bother me as much. Maybe because they are not block delimiters?
In my opinion, braces, delimiters or parenthesis create less visual noise than endless cascades of `end` keywords:
def f(x) do
...
end
end
end
end
There are other block keywords besides `end`, which serve no purpose other than simplify some keyword list arguments at the cost of higher complexity.In general there is too much syntax sugar for things that don't really benefit from it.
Parenthesis in function calls are optional in some places but not others, which creates some unnecessary confusion.
The syntax for anonymous functions is
fn arg -> do ... end
The `end` keyword is strange and I keep forgetting it (that's more a problem with me than the syntax, I know).The language is subtly whitespace sensitive in some places, while being mostly whitespace insensitive.
Macros can't take a variable number of arguments, which leads to the proliferation of Special Forms (it's a kind of macros that is treated as special by the compiler) which could perfectly be macros if it weren't for this limitation.
For a situation where this causes problems and requires some extra weirdness in what should be a simple DSL, look at this Github issue in the new testing library scheduled for inclusion in the standard library: https://github.com/whatyouhide/stream_data/issues/21
This is not true.
We have only two variadic special forms: "for" and "with".
"for" needs to be implemented as a special form as it emits some optimizations that are only available at the Core Erlang level.
"with" needs to be implemented as a special form as it has different lexical properties than a bunch of nested cases.
None of those could be implemented with macros.
Regarding whitespace sensitiveness, most languages have subtle issues, to varying degrees but especially if you have optional line terminators. Although it is true one or two extra cases appear in Elixir due to optional parentheses.
That's interesting. I wonder if a future Elixir version could have a way of defining macros which sould emit these optimizations. Is this a likely direction for future developments?
> "with" needs to be implemented as a special form as it has different lexical properties than a bunch of nested cases.
Could you expand on this a little? What are those different lexical properties?
To do so, we would need to compile to core, and that means tools like Cover and the Erlang debugger would no longer work with Elixir. It is unlikely we will go to this direction.
> Could you expand on this a little? What are those different lexical properties?
If you compile a "with" to a bunch of cases, a variable from the outer case would be available to both success and failure cases. Take this code:
a = true
with true <- a = false do
a
else
_ -> a
end
In Elixir it returns true because `a = false` has no impact on else. But if it compiled to a bunch of cases, we would get: a = true
case a = false do
true -> a
_ -> a
end
And that returns false.Not perfect, but it does allow you to leverage the advantages of Elixir (e.g. Phoenix and the related web-dev community) while still letting you get most of your work done in Erlang.
https://gist.github.com/macintux/6349828#alternative-languag...
Even if you don't like Elixir, it brings more to the table than just syntax and macros, such as tooling, Unicode support, protocols, better error messages, better debugging, tasks, etc.
If Elixir is not your cup of tea, definitely Erlang all the way.
No, you're not wrong. Elixir also supports meta-programming, whereas Erlang does not. I like both languages though, primarily due to the BEAM VM. You can't go wrong with either one.
The only similarity that Elixir honestly has with Ruby is clean syntax and general emphasis on productivity. Everything else is different. Same with Phoenix. On the web side of things it has routes, controllers and views...but there's no magic involved. It's all very clear, explicit and flexible in how it does things. The view layer is honestly one of it's biggest strong points because the way it's handled is the core reason for all of those "My site was faster without caching" articles...but you don't have to work differently to enjoy those gains.
Big Nerd Ranch did a solid write up on the ins and outs of why.
https://www.bignerdranch.com/blog/elixir-and-io-lists-part-2...
This Choosing Elixir for the Code post that came out last month does a really good job of emphasizing a lot of it.
https://pragtob.wordpress.com/2017/07/26/choosing-elixir-for...
The Phoenix Channels implementation for websockets and the view layer for web requests are pretty phenomenal IMHO.
Both are useful libraries, but the fact that they work in server clusters as well as single-node deployments is the real kicker. That's just how Erlang rolls.
We're experimenting with a truly OTP driven web application right now(No API) where we connect the React app directly to the Elixir backend and only persist to a database on log out. Everything else is persisted in memory and run through genservers which are self healing and self hydrating.
The language and Erlang's virtual machine are generally a joy to work with, and I have no regrets pushing past the syntax.
Can I ask what you use on the front-end (if anything)?
There's not really an Elixir equivalent of Clojurescript to Clojure (there is an Elixirscript, but it's a work in progress and I'm curious whether it can ever capture the nice experience of using the Erlang VM). I've seen interest in the Elixir community in Elm and Bucklescript(OCaml) and other compiled-to-js languages, but I think a lot of people just use JS.
* Most of the Elixir community comes from the Rails world, so most of the libraries focus on the web.
* Coming from the Ruby world, a lot of these new developers tend to be ignorant of the underlying Erlang machinery, believing that Elixir is the magic that makes Erlang a "modern" and usable language, which is simply not true.
* The tendency to favor frameworks over libraries.
* I'm probably alone in this one, but I find the syntax off-putting. It adds lots of sigils and complexity to Erlang's very succinct syntax.
* In the end, I think Elixir adds very little besides the excellent tooling to the Erlang ecosystem.
I agree with most of your points, from what I've seen, except the last one.
One thing that Elixir seems to get right is a sane string implementation out of the box.
But mostly you shouldn't expect people to grasp functional programming, concurrency and OTP when they are just getting started.
This sounds very subjective, and doesn't agree with my experience. I don't know a single Elixir dabbler that doesn't give Erlang credit where credit is due. It's just that the syntax looks like Prolog and ML got into a nasty car wreck sometime in the 1980's, and the tooling is more like a box of parts that can be assembled into various tools. That's why I use Elixir instead, anyway.
> The tendency to favor frameworks over libraries.
I know of exactly one framework in Elixir; I assume you mean Phoenix. Phoenix is little more than a few blobs of glue around other, mostly core Elixir, libraries. The Elixir community at large is quite wary of language and framework magic. The fact that lots of us did time with Rails means we've been hurt by the downsides of that approach like few others. :)
> In the end, I think Elixir adds very little besides the excellent tooling to the Erlang ecosystem.
That's good enough for me!
Personally, I like Elixir better than Clojure because memory in the BEAM VM is isolated. The JVM uses shared memory, which has caused me issues before. I also like the tooling and build tools for Elixir a lot better. Clojure is weak in this area, I think. I like Elixir's syntax much better too. For me, it feels a lot cleaner and easier to type, but that is subjective.
Regarding Rails vs. Phoenix, I cannot really speak to that. I don't do monolithic apps anymore, so I don't use Rails or Phoenix. Elixir has a library called Plug built into it that lets me easily make REST APIs for micro-services, so that is what I typically use. For me, this eliminates a lot of the issues I had with Rails.
I came to Elixir from Scala but also having a Rails background. I was quite sick of Railsy magic, because I'd gone deep with the Ruby metacrap and I got to taste the other edge of the sword. Elixir was a breath of fresh air.
If you enjoy concurrent programming with nice syntax and few surprises, you might end up liking it. Elixir is basically Erlang with modern syntax and vastly improved tooling.
Dynamically typed, functional language with immutable data structures and a strong philosophy about how to best handle concurrency and scaling.
The key difference is JVM vs. Erlang VM as the base and all that implies.
(Say what you will about Struts, but at least it makes JavaScript/React almost palatable by comparison.)
"Holy @#$% that's amazing." - notamy
There are ways to set up these systems in symbiotic configurations, like having an Elixir/Erlang/BEAM cluster that has auto-joining thanks to external service discovery. However, to use the hot-swap functionality, you are going to be working with the BEAM VM directly, which means going through the Docker container boundary.
Know that if you choose to go with BEAM and no Docker, you will still have access to amazing deployment features (like hot swapping, as you mentioned), but also clustering, service discovery, monitoring, etc. You're just going to be spending a lot more time at the command line without the pretty interfaces and the sleek modern tools.
To get a better understanding of running BEAM, I highly recommend "Designing for Scalability with Erlang/OTP". You'll have to learn Erlang to get through it, but IMO it's the best introduction.
Can you elaborate on this statement? My understanding is that you can have these things (minus hot swapping) cleanly on Docker.
Code hot swapping is not really docker friendly (obviously). I haven't done that, but work's general philosophy has been to have recoverable processes such that starting up the app will bring up the correct processes loaded with the right data. However, it's definitely possible to setup hot swapping but is going to be a learning curve to it.
Sure, a telco switch that has to be up 24x7, hot swapping makes a lot of sense. Most applications can spare the brief downtime to reload the old-fashioned way (or have multiple servers to allow upgrades to be done without downtime).
Code hot swapping can often trivially be done (by simply compiling and copying the fixed module, then 'l(module)' in the shell) if the module does not change the state record or break function signatures. I have used this countless times to fix a module function with no downtime. You don't always need the full code upgrade procedure, which is indeed far more onerous.
Getting hot code reload to work is harder, but for a "normal" basic deployment it's just this. App restarts take less than one second of downtime for my app, and that's a cost I'm willing to pay.
You shouldn't do this by hand of course. Write an Ansible playbook or something else that does this for you
Additionally, most of the cluster nodes run on Spot Instances in AWS so they are relatively inexpensive also. When a new instance comes up it connects to the cluster and starts serving requests. When an instance is killed, traffic is routed to the remaining nodes. Works great.
Debian packages are easy to work with and we keep them as our primary artifact for any given release.
The number of cluster nodes varies between 10 and 35 depending on the workload at the time.
Nodes join the cluster by contacting one of the three "static" nodes that are not spot instances. If they can talk to at least one of those they get knowledge of the entire cluster. When one of the spot instances is going to be killed there is a notice is the instance metadata. The nodes just watch for that and initiate a normal shutdown if they see it.
Yes we do use ELBs but just as TCP proxies and for TLS termination.
as a Ruby developer from Rails 2 > Rails 5.. about a year ago I made the switch to non-monolithic applications to Elixir API(no phoenix) and ReactJS front ends.. Match made in heaven.