IMO, I the fact that Elixir is a dynamic language is not that much a problem thanks to pattern matching.
Elixir's pattern matching is really powerful, allowing for things like:
def move(_state, _x, _y, kind) when kind not in ~w(forward backwards) do
{:error, :invalid_move_kind}
end
def move(state, x, y, kind) do
get(state, x, y)
|> do_move(state, x, y, kind)
end
In this case, I am asserting that "kind" is either string "forward" or "backwards". If not, I return the an error :invalid_move_kind. I don't need to check if pos is nil or not string, since I can assert using pattern matching that everything with my inputs are right.Also, there are things like structs:
defmodule RobotVsDinosaurs.Robot do
alias RobotVsDinosaurs.Robot
@enforce_keys [:pos]
defstruct name: nil, pos: nil
def set(state, x, y, robot = %Robot{}) do
case Matrix.elem(state, x, y) do
%Robot{} -> {:ok, Matrix.set(state, x, y, robot)}
_ -> {:error, :invalid_type}
end
end
end
What I am saying in the code above is that %Robot{} must have a :name and a :pos entries, and by default they're nil. However, to create a new %Robot{}, :pos must be different them nil. And of course, I can pattern match if my input is actually a %Robot{} and not a simply dict.You can also do crazy things like pattern matching if a byte array have some specific characteristics, like a magic number.
So while I think it would be nice for Elixir to have types (there is Dialyzer however it is kinda a PITA to use), it is less necessary than Python/Ruby/Javascript thanks to the above.
If typing is something you really want to catch at compile time, and pattern-matching and guards aren't enough for you, you can always define Dialyzer specs and run Dialyzer during your build process. I'm currently working on an Elixir codebase that does this, but I've worked on others that don't, and I haven't seen much of a difference either way. Personally, I think strong typing is overrated.
I have come to enjoy the more self-documenting nature of static typing (in PHP of all languages). Yesterday I worked on a legacy project with no typing, and to determine what was passed to an untyped method (with no documentation) required running the interactive debugger and inspecting what was passed in different scenarios...
I've seen this stated many times as one of the big drawbacks of Beam languages. But using Distillery with Elixir, it's very easy to generate a release that's completely self contained. Just extract a tarball on a freshly installed system and you are ready to go. If your target platform differs from the one you are compiling your project on, there are various options for cross compiling too
mix edeliver build release production && mix edeliver deploy release production
I've set this up as an alias in mix.exs to I just have to type: mix deploy
1.9 makes configuration easier than Distillery, I think.Urgh, that's a disaster.
- average performance ( like 10x slower than Java even worse for CPU intensive tasks )
- dynamic language ( this is the worst part ), working on large code base means problems ahead
- lot of features from BEAM / OTP that are not that useful and done better on modern cloud platforms ( Kubernetes for instance does a lot of similar things but better and more flexible, apply to any languages ) People like to talk about hot code update which imo is a terrible idea, you should have a proper CI/CD pipeline to do that and not rely on dangerous features like that.
- lack of ecosystem / libraries
- it's FP, I count that as a personal cons