Elixir at PagerDuty
pagerduty.com
pagerduty.com
All that said however, it kind of bugs me when I see posts like this (no matter the language) that go somewhere along the lines of "I managed to introduce language X and it was all rainbows and unicorns! Everybody loves it and work is fun again". Like, you had absolutely no problems with it? In my experience, people don't immediately grasp the actor model or how to manage processes (i.e. should I spawn a process for a short-lived task? should this monitor another process? should I link? should it need supervision?).
Given that you're basically changing a methodology amongst your devs, it can be expected that things won't get written "right" the first time. I'm not referring to little idiosyncrasies like coding convention or best practices but literally misunderstanding of gen_servers and supervisors. Sure, perhaps transferring from Ruby to Scala to Elixir helped made the transfer less painful given their similarities but surely there were complications?
That said, I wouldn't advocate jumping into Phoenix with zero Elixir knowledge. You'll have a tough time. And knowing about OTP/supervision is also recommended because you'll be much better off in the long term. I don't think you can avoid it forever.
I have mixed feelings here. From a purely implementation standpoint I totally agree - I don't particularly enjoy using certain tools at work but that is not a valid excuse for me to not get work done. At the same time, as a psych grad turned developer, I'm intimately aware of the effect that dreading a journey can have on attaining a goal and this is no less true of mapping developer experience to productivity - thus we should always be striving to make sure that the goals of the team are not undermined by the requirements to get there. If I had an objective that couldn't be solved in Elixir I would invest time in groking Erlang but its syntax does deter me from wanting to make that investment until I need to.
> All that said however, it kind of bugs me when I see posts like this (no matter the language) that go somewhere along the lines of "I managed to introduce language X and it was all rainbows and unicorns! Everybody loves it and work is fun again".
Totally agree, especially with your points on the gen_* family which are all vastly useful once you understand them but took me some time to bridge that gap. I feel that it's just as important to understand the pain points such that others reading this post and wanting to be the catalyst for similar change can prepare for these issues themselves should they ever want to take the leap.
The difference between readability and usability of Elixir and Erlang syntax is night and day, especially as someone coming from 20 years of working with imperative languages. I can feel comfortable handing out small tasks to a lay developer in elixir and getting back a response pretty quickly, it has a much easier on boarding. It has it's flaws of course. It's much easier to write programs that will crash in elixir, etc. but that concession with a more approachable syntax and methodology coupled with the benefits of OTP and Beam seems like a much more compelling sell than pure erlang.
This has happened at my company with a service called Firehose, which is basically PubSub over WS and HTTP written in Ruby Event Machine. It’s unstylish for a lot of technical reasons and an eng even tried to rewrite it in Elixir. We ended up scraping the rewrite project because the Ruby code just works and rarely gives us any problems in production.
The problem with that story though is it won’t attract engineers who like new shiny stuff.
If your Ruby code just worked, why did you try to rewrite it in Elixir?
Elixir:
"Hello,How,Are,You,Today" |> String.split(",") |> Enum.join(".") |> IO.puts
Erlang: -module(tok).
-export([start/0]).
start() ->
Lst = string:tokens("Hello,How,Are,You,Today",","),
io:fwrite("~s~n", [string:join(Lst,".")]),
ok.However what you show is not focusing on syntax differences but rather function differences, even `|>` is an (macro) operator. By syntax I'm talking about things like the `do`/`end` and `fn`/`end` and `,do:`/`do...end` mismatches, things like atom key short-form of `someatom: ...` (being short for `:someatom => ...`) only being useful at the end of it's list/map context instead of everywhere (which is not ambiguous nor have any other reason not to do it that I can see), having functions be callable with or without parenthesis (of which thankfully the formatter default puts parenthesis) when functions really should be defined with or without parenthesis and their usage enforced as such, mis-matches in the AST (when creating macro's) by special casing things like 2-tuples among others, and the really weird multi-arity functions of `for` and `with` that really should have been done via body expressions instead of `,` separated expressions (and thus would not need to be special forms but could then just be normal macros, but they seem extremely out of place for the rest of the language syntax), etc.... etc... etc..
It's just a lot of little inconsistencies like that.
Also, if you want pipes for erlang look at the https://github.com/rabbitmq/erlando parse transform (there are others as well, but I like this one), and if you want a shorter erlang form then look at erl2 (just a layer on top of erlang to shorten constructs like module definitions), and of course nothing beats `lfe` on the beam for succinctness and defineability (being a lisp after all).
Also, your Elixir and Erlang codes do not do the same thing. Your Elixir code is being run at compile-time where your Erlang code is not executed at all, only compiled, and thus will only be run if something calls the `tok:start/0` function. The equivalent elixir would be (formatted by the Elixir formatter):
```elixir
defmodule :tok do
def start() do
"Hello,How,Are,You,Today"
|> String.split(",")
|> Enum.join(".")
|> IO.puts()
end
end
```
And you can leave off the final `:ok` on both as both `IO.puts/1` and `io:fwrite/2` both return the atom `ok` in the end. And even then these are not equal as you are working with charlists with one and binaries with the other, so a more direct translation of the Erlang code to Elixir would probably actually be: ```elixir
defmodule :tok do
def start() do
'Hello,How,Are,You,Today'
|> :string.tokens(',')
|> :string.join('.')
|> (&:io.fwrite('~s~n', [&1])).()
end
end
```
That would make the functions equal.As an aside, a few other bits, Elixir defines if a function is public/private via `def`/`defp`, meaning you have to search through a file to see what is exposed, where in Erlang you can see exactly what is and everything that is exposed by just looking at the top.
And yes, `do`/`end` may be longer by one line than Erlang's sequence operator `,` usage, but I don't mind it to be honest, and you can replicate it in Elixir anyway kind of like:
```elixir
def start(), do: (
lst = :string.tokens('Hello,How,Are,You,Today', ',');
:io.fwrite('~s~n', [:string.join(lst,',')]);
:ok)
```
Essentially just replace erlang's `->` with `,do: (`, replace erlang's `,` with `;`, and finally replace erlang's `.` with `)`.Comparing 'function' differences between them is useless of course, they can call each other functions (though macro's are another issue, erlang cannot 'usefully' call elixir macro's, but then again elixir may not soon be able to use erlang's parse transforms either), the differences are the syntax, and Elixir just has a whole ton of oddities.
Overall I find the Erlang syntax (which is similar to SML/OCaml/etc...) far more uniform, sensible, readable, and that it has far less surprising syntactical cases (like why can't I do something like `%{some_atom: 42, "string key" => 6.28}` in Elixir, blah... you have to do `%{"string key" => 6.28, some_atom: 42}` instead for who knows why reasons...).
I do of course use Elixir for my day job, have for over 2 years now, and these are just a few constant things that bug me on a day-to-day basis. However the tooling, macro's, and community (come join us on the Elixir Forums!!!) are top-notch!
If I had my 'choice' of mythical language though, I'd pick something like OCaml with Rust-style ownership semantics with a full staged macro system (Elixir's is a staged macro system) if it were not otherwise possible to safely add in a full Lisp-style macro system (I really don't think so in a full setup like these though, you need a staged system for various corner-case reasons).
I believe the term "core language" may be more appropriate than "syntax" in your argument: it's true that the pipe operator (|>) is a macro, as well as fn/end or the match operator (=), but that's an implementation detail, they still are syntactic forms.
As with any programming language (except of course for Scheme, which is perfect :D) Elixir has some inconsistencies, but I don't think they make the language "hard-to-read" in any realistic way.
Things like a function call is language syntax. You cannot replace language syntax with anything else. Special forms are things like `for` and `with`, and you can replace these with syntax (though that's going the wrong direction in my opinion). Kernel are the standard library bits that are auto-imported into every module (unless explicitly told not to via `except:` or so), these cannot replace special forms or syntax. Etc...
The Pipe operator is just a normal macro in the Kernel, it is redefinable and in fact there are a surprisingly large amount of libraries that do (to make it more monadic interestingly, which makes me think Elixir needs a binding operator in kernel, perhaps `~>` or so).
Things like `fn`/`end` are language constructs, just no way around that at all.
The match operator `=`, interestingly enough, is a special form and not a language construct, so there are times that you can override it as well.
> except of course for Scheme, which is perfect :D
Take a look at Racket, it is scheme refined into bliss. ^.^
> Elixir has some inconsistencies, but I don't think they make the language "hard-to-read" in any realistic way.
Not overall 'hard-to-read' (not like Java for example), but definitely harder than Erlang I'd state.
Elixir's pin operator is a good example of unnecessary complexity.
In addition, naming variables like that in Erlang is a mis-design, the names should be more descriptive.
I'm so much more happy writing the equivalent Ruby code:
puts "Hello,How,Are,You,Today".split(",").join(".")
It's value.method.method vs value |> Module.function |> Module.function. Alias is not a general solution because of conflicts.Disclaimer: I use Elixir in projects for customers and I like it. Still, it's too verbose and I'm not using it in my own projects (no need for heavy parallelism there.)
IMHO "this is a string" |> String.split(" ") doesn't fully separate the data from the function. It's String.split after all and it won't work with an input of any other type.
However, at least in the case of languages with duck typing, some_object.split(" ") works with an object of type String and with any other class that implements a split method that takes a string as argument. Then the rest of the code must be able to keep handling the duck typed object.
Probably this isn't the way you are thinking about data/code separation, still it's a kind of separation.
Using String.split() instead of an method call on the string is just one of the reasons I like this rather stricter separation between data and code, and I do see how in isolation it's not all that great.
That said, I still enjoy the more OO languages and rather like using some of the FP things I've learned inside those. And sometimes it does feel convenient to not be pushed into one particular style of coding. So perhaps 'liberating' is not the best choice of words :).
"Hello,How,Are,You,Today" |> String.replace(",", ".") |> IO.puts()io:fwrite(string:join(string:tokens("Hello,How,Are,You,Today",","),".")).
The one that I still feel uncomfortable with, and where I haven't heard a proper justification, is how you can leave out [] for a keyword list when it's the last function parameter (I think they call it an options list?).
It's already a bit confusing how a keyword list translates to a list of tuples, so adding that bit of sugar just further confuses things. It reminds me too much of Ruby's implicit block parameter (or whatever it's called), which I never was a fan of.
Other than that Elixir might be my one of my favorite programming languages.
> Elixir comes with one of the nicest and most helpful communities around
Using Elixir, it's easy to write gorgeous code, with a language that for the most part is really minimalist, and you get to play with the great piece of software that OTP is without all the warts of Erlang (which is mostly the developer experience). The BEAM community is fantastic and really supportive (disclaimer: I'm a GSoC student working for them on making Elixir bindings for Barrel, a replicated document database).
Maybe we could think that it's best in terms of the character of the community if Elixir stays small, however Rust community seems a great example of something bigger to follow that hasn't evolved into a soulless graveyard of enterprise code scattered on the githubs and maven centrals of the world.
There has to be a way between hand-rolled craft parsers you write with a beard and an IPA in hand and another AbstractObservableContainer written by Dilbert that doesn't compromise the ecosystem or contort it in any funny way.
Also, while I recognize it's a third party project, the hyper API keeps changing faster than I can adapt my code. If you are aware of a more stable HTTP server, ideally one that has proper TLS support and support for unix domain sockets, I would be very interested.
EDIT: on the other hand, after working with rust daily for ~1.5 years, I have only run into one compiler bug and had zero actual bugs in my application due to language/compiler updates so far. There have been a few occasions were essential features were missing from the standard library/language though and several times were we had to backport some code to an older, stable rust version that we're one (when a developer had mistakenly tested their code locally with a newer version).
The highest up on my wish list would be that - as I understand it - you can not currently exit the process cleanly (EDIT: early) with a non-zero exit code in stable rust. That makes it pretty hard to implement good command line utilities. Happy to be corrected if I am wrong on this one though.
You're writing a server with hyper directly? Is there a particular reason you're not using one of the frameworks written on top? I may have some advice there, depending.
I do need a lot of pretty specific other features though. Unix domain sockets are currently a must (luckily that works with hyperlocal).
The code runs on a pretty resource constrained system so I like the low level of control that hyper gives me of the request/response chunking behaviour (I can not afford to buffer large requests in memory and what exactly I do with the body payload differs between API calls).
One thing that would be lovely is proper TLS/HTTPS support. I need support for x509 client certificates and I need to get access to the full client certificate data, ideally including the full chain that signed it from the request handler. I have to admit that I currently run a hacked together nginx in front of my app and put this stuff into HTTP headers (hence the unix domain sockets) because I could not get it to work with rust ecosystem libraries.
EDIT: The TLS stuff is also true for the HTTP client case. Currently using the libcurl rust binding because it's the only thing that implements all the features I need (--cainfo, --cert, --key, --resolv). Also it needs to run on OpenSSL or something else that supports x509v3 extended attributes (subtree constraints).
I don't know if rustls supports your use-case for TLS, but you may want to check it out.
Thanks again. It's really invaluable to hear about these kinds of things.
fn main() {
if let Err(e) = real_main() {
let status = status_from_error(e);
// print the error message, or whatever
std::process:exit(status);
}
}
fn real_main() -> Result<(), TopErrorType> {
// ...
}
And simply returning Result<_, SomethingConvertibleToTopErrorType> from functions called by real_main that might need to exit the program.I believe that in a recent stable version of Rust, you can make this even simpler and have `main()` return a Result directly.
edit: not quite as simple as I described, but maybe more powerful because of the Termination trait: https://github.com/rust-lang/rfcs/blob/master/text/1937-ques... see: https://github.com/rust-lang/rfcs/blob/master/text/1937-ques...
fn main() -> Result<(), io::Error> {
let mut stdin = io::stdin();
let mut raw_stdout = io::stdout();
let mut stdout = raw_stdout.lock();
for line in stdin.lock().lines() {
stdout.write(line?.trim().as_bytes())?;
stdout.write(b"\n")?;
}
stdout.flush()
}EDIT: I assume you mean RFC1937? Looks like it isn't implemented yet, so we will probably have to at least another year before we can get it in stable rust. But yes - without having read the entire RFC - I think that was what I was looking for!
The rest of it makes it even better; it's a bit limited in ways right now.
You don't need any fancy features to do this. Every single one of my Rust CLI programs has done this on stable Rust for years. All you need to do is bubble your errors up to main.
If you have destructors that you want to run, then put those in a function that isn't main.
EDIT: I should have said explicitly in my initial comment that I knew about std::process::exit and panic!, but did not consider them to be a clean solution for exiting the program under normal circumstance -- more of an abort() mechanism.
EDIT: to expand on this: a suggested workaround is to only call std::process::exit at the very end of the main function. But consider stuff like "env_logger::init". What about that? Ok, that doesn't use Drop and if it did you could put it into it's own scope I guess - so there are workarounds - but it in my opinion that gets pretty ugly. Comparing to c++, std::process::exit or panic! is like abort(), but what I want is a "return 1" from main.
panic! does return a non-zero exit code, but isn't really designed for good end-user output.
That most of my programs have some global (i.e. "for the runtime of the program") stuff that is setup at the beginning of main. And that some of that might want to Drop when the program exits, for example to delete a temporary directory. Now, if I want to return a non-zero exit code I can not do so while still getting all this global stuff destructed correctly (or use a workaround like having a wrapper-main).
> Calling `std::process:exit(1)` at the end of main is identical to `return 1`.
The thing is that it is actually not the same with respect to destructors -- the documentation explicitly calls that out. See also the C++ comparison in my other comment.
I'll also add that I've actually been really impressed with how much attention Rust takes to not break stable packages. Between crater[1] and how aggressively they've cut point releases to fix any issues that (rarely) happen to slip by.
I've been using bindgen for ages on stable and just grabbed the latest version on an empty project and it still works for me.
You shouldn't be using rustfmt-nightly at this point either; install the rustfmt-preview component through rustup. It works on stable!
So yeah looks like I'm tethered to the nightly toolchain after all. :/
Before I ended up going down the TryFrom rabbit hole I ran into two issues with bindgen:
1.) Bindgen segfaults with the ancient LLVM on OSX 10.9. Issue #1006. Solution: use LLVM >= 5 binaries from the LLVM site.
2.) Stable (deprecated) rustfmt causes bindgen to segfault. Solution: cargo install -f rustfmt-nightly. It could be as simple as "stable rustfmt requires --force to run because it's been deprecated", but more helpful error messages would be a great thing here.
(TryFrom is being stabilized fairly soon, it got caught up in some silly stuff but is basically good to go)
Custom { kind: Other, error: StringError("Internal rustfmt error") }
I get the same results if I call the bindgen executable or have build.rs call the bindgen API. I've stuck with having build.rs call the bindgen executable because if I use the bindgen API, compilation is SLOW.
rustup component add rustfmt-preview $ cargo uninstall rustfmt && rustup component add rustfmt-preview
Removing /home/alex/.cargo/bin/cargo-fmt
Removing /home/alex/.cargo/bin/rustfmt
info: downloading component 'rustfmt-preview'
info: installing component 'rustfmt-preview'
$ bindgen bindings.h -- -I/usr/local/include/libr > /dev/null
Custom { kind: Other, error: StringError("Cannot find binary path") }
$ rustc --version
rustc 1.28.0-nightly (29f48ccf3 2018-06-03)
$ cat bindings.h
#include <r_lib.h>
#include <r_asm.h>
#include <r_reg.h>
#include <r_anal.h>
#include <r_bin.h>The whole point of Erlang and Elixir is robustness in the face of high concurrency. All other design choices (eg functional programming, immutable data), follow from that goal. Core is that if one green thread ("process" in Erl/Ex lingo) crashes for whatever reason, the rest keep on running. Interop with native code is the sole exception here, which makes interop very scary. If a C function that's called into from Erl/Ex crashes, the entire program crashes. Boom, gone. Sure that holds for most other languages too, but Erlang/Elixir people get extra nervous because they're more accustomed to thinking about error scenarios and because they don't always have the same seven restart/recover layers outside the VM process that good devopsers wrap eg Node processes with because hey, no need, we have Erlang, we never crash.
As a result, the idea of a technology that allows writing native code that has a tremendously small likelihood of crashing is very appealing. Until recently, no such technology was available but Rust changed that. Rust allows writing native functions that can be called from Erlang/Elixir with much fewer worries than C/C++ native extensions ever did, because of all the safety guarantees provided by the compiler. This is a big thing and I hope that as the interop story improves, it means that Erlang/Elixir will in practice become much faster because more libraries will be rewritten in native code.
It's not just crashing either, it's taking a long time to execute a given function in the native code - that can also cause problems for the Erlang system running it.
Oh, this is cute statement xD No, ports also carry the same risk as NIFs if you use port drivers, because it's still the same way of running the code, just with different API. You need ports backed by external process to be isolated from the crash, though there's still the problem of such ports eagerly reading all the available data without any backpressure whatsoever, so your BEAM can trigger OOM killer. This is much easier to avoid, fortunately.
No such technology was available, barring maybe Ada or some other languages.
We’ve also made some small changes that in theory can break code but weren’t observed in the wild. The tolerance for that has dropped over time, of course.
I still don't quite understand what makes Rust community this way, but it's one of the best things about the language.
I desperately want it to become more popular. To this end, I started writing a book that teaches Elixir (Phoenix) in combination with Vue.js. It's still in the early stages, but the thing I realized is that even writing the book is fun! This was very surprising to me because I typically don't find writing technical documentation/tutorials pleasant.
Anyway, if you're on the fence about Elixir, definitely check it out - especially if you're coming from a Rails background!
I was a huge Rails fan back in '07 and built a number of apps with it successfully, but I can't help feel like it's transition to a legacy framework has already begun.
While Rails was never top in the performance category, languages are catching up in tooling and readability. So, now you can get performance and a great developer experience with multiple other languages like Elixir, Crystal, Go and more.
I love starting new projects in Rails, and don't feel the need to look anywhere else: it handles everything for me in a very nice and battle tested way, and I'm always one Google / StackOverflow question away from finding out exactly how to achieve my goals quickly.
But, static typing and being able to compile and deploy a binary is a pretty sweet developer experience. And the syntax is nearly identical to Ruby with a few nice additions. Many companies like ThoughtBot (a huge Ruby adopter) are jumping into Crystal. The future is definitely in compiled/static languages IMO.
Supposedly someone is making a Typescript-like precompiler for Elixir (with ML syntax of course). We are all hoping it succeeds!
It's not compiling to elixir, but the `alpaca` language compiles to the BEAM (via erlang core I think) so it gets you close, although they are not black boxing messages sadly.
If you know of someone else doing typing experiments on top of elixir, please link me (my playground code is on my github repo somewhere)!!!
The idea seems just like Typescript, a very interesting project!
It is based on Elm so it has a severely limited type system but it is the most complete on Elixir so far. :-)
They are super useful and given a complicated enough problem/ sophisticated enough programmer they will look like a good enough solution for all problems. But ... Wordpress
I don’t mean no one will or should use static languages but believing they will once again dominate like in the 80s and 90s is an illusion imo
(that said, I do agree that often PHP is a fine choice, and people might overcomplicate things needlessly by choosing something else)
There are legitimate problems of legibility, maintainability, and training when introducing new stuff into the stack. Then there's the balance that we have to strike with re-writing old with new. How much do we gain with the new vs how much time is it gonna cost?
Lately, these have been the things I worry about instead of algorithm design or product design. Precisely the things we don't get taught in school or get interviewed for
In Java or .NET shops, there is a significant investment in the surrounding infrastructure where the code is deployed. Has a huge impact in cost of switching. That's before even factoring in some of the inside baseball related to "discounts" on products like SQL Server if you have a certain number of employees with Microsoft certs.
For just about everything else though...it's not nearly as much of a headache.
Environment is definitely the key. We don't use either of those languages but we do run our site on a dozen or so servers. There's a fair bit of coupling that can eventually add up when you run the same apps on the same servers for years
The quote that resonates...
> ...some of us are secretly hoping that one day, we’ll only have to deal with Elixir code
Also, thanks for sharing your story. Elixir is a great language and I love seeing it being adopted more and more everyday.
* Haskell influence on the language, especially early on, encouraged the omission of dots and parentheses wherever possible. Just about every piece of sample code was written as an undifferentiated stream of tokens. There is a mental burden.
* Haskell influence on the community encourages category theory solutions to all problems. This harms interop with Java libraries, in addition to requiring a number of concepts that are safely ignored in many/most other languages.
* Scala's rich OO system (classes, interfaces, traits, singletons, case classes, implicits, etc) results in frequent design paralysis, not to mention taking a while to learn.
Elixir suffers from none of these problems, while retaining some of the things that made Scala a pleasure to use, notably the actor system of concurrency and the use of immutable data structures.
> Haskell influence on the language, especially early on, encouraged the omission of dots and parentheses wherever possible.
This is considered an anti-pattern in the Scala community. The last library that used this at large scala was sbt, Scala's build tool, and now everyone discourages the use of infix operators.
> Haskell influence on the community encourages category theory solutions to all problems.
This is simply not true. First, there is no Haskell influence on the language. Scala is a functional programming language, all similarities between the two are just fundamental to how their type systems work and the foundations of the paradigm. Second, I contest heavily that the Community encourages category theory to solve all the problems. There are many subcommunities in Scala and many styles, but if you talked to some Scala developers nobody would agree that category theory should be applied to all code. In fact, most people don't. They just use Scala because it's a better Java that boosts their productivity.
> Scala's rich OO system (classes, interfaces, traits, singletons, case classes, implicits, etc) results in frequent design paralysis, not to mention taking a while to learn.
Absolutely agree that for someone not experienced, it may be difficult to know what's the best way to design libraries (this is more of a problem with libraries than applications). This is a big problem of Scala that I think we're solving by making the language more opinionated and being more open about what's encouraged and discouraged.
If you ask for my opinion, I think we also need to make a better job at communicating all this knowledge that advanced Scala developers learn but that is usually kept to closed circles.
I spent three years on the 2nd largest Scala 'team' in the U.S (first as an engineer, then leading a sub team), the problem is that using Scala as a 'better java' doesn't really buy you much productivity wise. A small percentage of sub teams tried using Scala this way and hit a lot of road blocks with the unfamiliar syntax, immature tooling, and other quirks.
The teams that had huge productivity gains were the ones who leaned heavily on the functional features WHILE caring about overall readability. That means yes, we used Scalaz but we discouraged the omission of dots and parentheses (as you mentioned), discouraged operator overloading/symbolic methods, and were very deliberate about when to reach for features such as HKTs or some of the more unfamiliar features of Scalaz.
Overall it lead to a very productive and fun development experience, and at the same time illuminated many ways in which the Scala ecosystem could also be improved.
Since Scala has no build-in way to define FP constructs(monads, functors.. ), cats and scalaz are practically inseparable from the language itself when you try to go beyond the basics in FP. So, it is very easy to see where all the confusion is coming from
You need to sort of put your foot down on using out there FP stuff like some parts of scalaz and turning your code into line noise. The non-FP parts are nice and useful though.
I can go on for days on how someone wrote some unreadable Scalaz that had atrocious performance (even with @tailrec) and someone rewrites it in plain Scala for a 100x perf win.
Still.. would I use scala again? Yes, but mainly because it allows you to use high quality Java libraries that are unmatched by any other language. It’s also just much nicer than Kotlin.
We also really like Akka...
There are other syntax quirks as well. A very very very opinionated take on Elixir syntax: https://medium.com/@dmitriid/in-which-i-complain-about-elixi... :)
(Note: I haven't touched Elixir in a while, so some of the things there may be wrong)
Optional parentheses were one of my main issues with Elixir, but once I understood why this choice was made, and what with the formatter judiciously adding them back in, I'm much happier.
(I recall having tons of issues when using the pipe operator + leaving out parentheses. Happy that these aren't much of an issue anymore.)
2. I mentored a couple novice developers working on a Play codebase, the rough guideline was to use libraries that depend on Scalaz, Cats, Shapeless ... but never import any of them in our codebase. No HKT in our code either. (Nowadays I work with all that on a daily basis, you can keep things reasonable).
3. I think you're mixing up a few unrelated things. Scala's OO model is cleaner than Java's. Implicits abuse is avoidable with frameworks like Play.
Things can get rough when you take FP or OOP to their logical ends, the sweet spot lies somewhere in between.
I think it has to do more with the latter. To me, Scala is too powerful - it rivals C++ in complexity, despite being half it's age. It almost feels like every Scala codebase is written in a different language, and I always felt I spent more time parsing Scala than understanding the code. It's a powerful language to write, but hard to read - almost the complete polar opposite of Go. As a result it can be very hard to bring new engineers up to scala, and even engineers that know Scala have to learn "your flavor" of Scala.
I have to say though, coming from a C++ background, Scala still feels much more beautiful, clean, and safe than C++... but then, almost everything does ;)
1 + "some string" + [A,List,Of,Somethings]
Compared to javascript where: 1 + "2" == "12"
Statically typed means that variables and functions hold or return specific types which can be determined before execution.The "+" (operator) case is only special in languages that do not implement operator overloading -- it is completely orthogonal to the type system.
If it were type coercion, we would expect the add function to have either of these two behaviours:
- coerce the input arguments to strings, i.e. implicitly accept two strings and return another string
- coerce the input arguments to integers, i.e. implicitly accept two integers and return an integer
However, that is not what is happening here.Of course, in Javascript it is implemented as a function that takes the theoretical "Any" type and decides what do do at runtime. But in a statically type language, the equivalent mechanism would be dynamic/multiple dispatch (or a static overload, possibly in combination with a generic/template method) -- not type coercion.
Sometimes useful, but the type system ends up offering few meaningful protections as a consequence.
However, C does allow you to write code that can not proven to be legal with respect to the formal system of the language and get it to compile, I think that is what you are referring to.
But just because you can write an invalid program does not mean the type system doesn't exist or is flawed. I can also write an invalid Haskell program.
The difference between these two would be that the haskell program can be proven to contain only operations that map to well-defined operations in the formal language model by an automated process, while the same can not be done for C in the general case.
However, just because you can not always prove conformance to a formal model using automated means, doesn't mean that no formal model exists (because it does!).
Elixir is a dynamically typed language, so all types in Elixir are inferred by the runtime.
I don't think anybody ever did. The definition of "strong" and "weak" seem to be entirely subjective; and of course everybody considers their favourite language to be "strongly typed". It's like a reverse no-true-scotsman.
It's also interesting that he wants the ability to "run your tests for the intermediate refactoring steps that wouldn’t typecheck and then let you fix up things later."-- I'm not sure what it means to want to run code with ambiguous types in a type aware language like Scala. Type information is deeply involved in runtime program behavior. (E.g pattern matching on types, resolving typeclass instances)
I certainly agree that nontrivial sbt work is rough.
Sometimes there are split clients for what I wish had a community recommended or be in the std lib. For example HTTPotion[2] vs HTTPoison[3] (names are _super_ similar and sometimes confusing).
[1] https://github.com/whatyouhide/redix/issues/77
Since Elixir is used for (1) functional programming, and (2) it uses Erlang's OTP Server for massive parallelization.
But, I don't seem much activity in this space. What is the probability that Elixir will emerge as the "killer app" for AI development?
But I've been reading a lot about this topic too. There are a lot of people out there doing exploratory work to see if Elixir can be a viable solution.
With serverless the advantages that Erlang has don't matter much, since the process doesn't live along to utilize them. Instead the bottleneck is cold start times. So these types of languages (including Java) that are meant to start and stay running for a long time aren't a great fit. Go, Rust, possibly Pony, are better fits. Even Node isn't a superb choice, cold starts can be slow because of loading so many files.
Seems to be down at the moment, but here's some previous discussion: https://news.ycombinator.com/item?id=5243360