7 ms·
Are there no bad parts?
by oftenwrong 7y ago
Are there no bad parts?
- yelnatz 7y agoI've used it sparingly, I think deploying is pretty bad with it. I like the convenience of deploying a golang app, just one executable.
- srpablo 7y agoOTP releases let you build something akin to this, where the runtime and all the dependencies are bundled in; so long as you built it for the right architecture (much like a binary), it's a single command to run your app. This used to be done with Distillery[1], but I suspect its use will go down now that Elixir has native OTP releases.[2]. In either case, there's still work behind it, but it's not like deploying Ruby, Python, or Node, where you have to be pretty careful about library paths and runtime versions. [1]: https://github.com/bitwalker/distillery [2]: https://elixir-lang.org/blog/2019/06/24/elixir-v1-9-0-released/
- eeZah7Ux 7y ago> I like the convenience of deploying a golang app, just one executable. This is crazy. What about configuration, systemd unit file / initscript, documentation, manpage, shell completion. And the ability to tell what is installed, where, and at which version. HN forgot how to do software distribution to the hype of statically compiled languages.
- inferiorhuman 7y agoDeployments are a mess and as it is interpreted, performance is closer to Ruby than Go. I found the community to be pretty disappointing (but YMMV obviously) — it seemed more alternative oriented than the Rust community (which seems more solutions oriented).
- erokar 7y agoElixir is compiled, not interpreted.
- inferiorhuman 7y agoCompiled into bytecode to be interpreted by the Erlang (BEAM) VM.
- erokar 7y agoAre we doing semantics now? Java is also compiled into bytecode, executed by the JVM. Do you consider Java an interpreted language also?
- ralusek 7y agoI think most people consider Java to be an interpreted language, it's interpreted by JVM. It's obviously somewhere in between, in the same way that JIT languages are, but it's still not native.
- didibus 7y agoNot sure about the BEAM, but Java isn't considered an interpreted language at all. First, discerning the compilation step to IR is relevant, which is why for example, Python can be compiled or interpreted. That distinction matters. Even though it is compiled to intermediate representation, it is still compiled. Now, with regards to the intermediate representation, Java Byte Code is also not interpreted. Java Byte Code is compiled to native machine code either Just in Time by the JVM, or Ahead of Time by the SubstrateVM. Both of these make it a compiled language. An interpreted language never gets translated into native machine code, it gets executed by a native interpreter, and that's very different. It is more akin to using a language where you read and parse texts, and based on the text, you might execute one or more things. Now if that parsing to execution branching is powerful enough to allow Turing complete behavior, you have yourself a full blown interpreted computer language. This is not what happens in the JVM. The JVM translates Java Byte Code to native machine code Just in Time, and then the native code is run.
- true_religion 7y ago
- Existenceblinks 7y agoIt's not a mess. Mostly It was just unclear mixed of runtime and compile time configuration that contribute the inconvenience. Elixir 1.9 just comes up with a simple built-in release mechanism with more clear on how you put configuration.
- foobarbecue 7y agoSounds compilicated.
- TylerE 7y agoAren't most things that actual survive encounters with the real world? Make it as simple as possible, but no simpler.
- foobarbecue 7y agoIt was a pun.
- inferiorhuman 7y agoIt's not a mess. Mostly It was just unclear mixed of runtime and compile time configuration that contribute the inconvenience. Elixir 1.9 just comes up with a simple built-in release mechanism with more clear on how you put configuration. From the sound of it things have gotten better (by throwing out oft-touted features), but Elixir deployments are inherently far too complicated. With Go and Rust you get a single binary and a single program to run. With Java (and other languages targeting the JVM) you get a single jar archive. With Elixir you get to bundle the whole runtime and spawn a few separate supervisor processes to run even a hello world app. Lord help you if the only maintainer for the deployment tooling takes an unannounced vacation or if the various BEAM processes stop talking to each other because the stars are misaligned (and the deployment tools are minimally tested) and you can't even run an Elixir based database migration as a result. When I started toying around with Elixir and Phoenix I'd just come off of a Clojure kick. You can get most, if not all, of the syntactical sugar that Elixir promises with other languages that all offer infinitely simpler deployments with more mature tooling. In the case of Clojure you get whatever JVM tooling you'd like.
- wolf4earth 7y agoPerformance is definitely not close to Ruby, this is just blatantly incorrect. Elixir and Erlang can easily hold their ground vs Go. The BEAM was originally designed to run software for telephone switches. It's often referred to as soft realtime because it is so responsive. It has been battle-tested for now over 3 decades. Where the BEAM falls flat is pure number crunching but it's blazing fast with binary processing (e.g. string parsing). A fresh phoenix project where you render a nontrivial template easily reaches sub millisecond response times, without any kind of optimization.
- mtarnovan 7y agoFor CPU intensive tasks, performance _is_ close to Ruby, while both Ruby and Elixir are far behind Go. Soft realtime doesn't mean fast, it just means good std deviation on response time and not a lot of missed deadlines. Fortunately as the article points out, there's always Rustler if you need to optimize while still keeping BEAM crash proof. That being said, I agree that performance for certain IO intensive applications like web servers is very good, way better than Ruby. Subjectively Phoenix feels way, way faster than Rails in development and is much more performant in production.
- enraged_camel 7y ago>>For CPU intensive tasks, performance _is_ close to Ruby, while both Ruby and Elixir are far behind Go. Yeah, but how many companies do you know of that perform heavy computations in such large scale that their choice of programming language has a noticeable effect on the result? I mean, it might matter for tech giants like Google, who need to squeeze every bit of processing power out of their CPUs. For almost everyone else, it will be a non-issue.
- mtarnovan 7y agoIt's just that when you say "performance on par with Go", people might imagine you could write a 3D engine in Elixir, which obviously isn't the case.
- 7y ago
- owens99 7y agoFor web development the performance is within a breath of Go and a kilometer ahead of Ruby on Rails.
- phtrivier 7y agoOf course they are, but thé article seems to be from someone interested by elixir, rather than someone who used it long enough to discover them (although I might have misunderstood the article.) From own experience, the problematic parts are : - deployment being a bit messy if you try to follow 12 factors (might be improving in 1.9) - absence of a decent debugger - younth of the ecosystem and size of the community (aka hard the find a maintained lib that works for a few common issues.) - compiler being a bit slow to my taste (YMMV) - then, of course, the elephant in the room: the absence of the kind of static type checking that facilitate refactoring ord relatively large codebases. Nothing unbearable or permanent, but some things to keep in mind. Go try it !
- dnautics 7y agoCan you describe a situation where you would have wanted a debugger that io.inspect couldn't get you out of? I haven't found one yet.
- innocentoldguy 7y agoUsing a combination of IO.inspect and IEx.pry, I've never found myself wanting or needing any other debugging tools.
- olikas 7y agoBEAM based languages lack "good" debuggers, because they historically depend on tracing instead of debuggers. I recently wrote a blogpost about the tracing landscape in Elixir. https://www.erlang-solutions.com/blog/a-guide-to-tracing-in-elixir.html https://www.erlang-solutions.com/blog/a-guide-to-tracing-in-... But the main reason is that BEAM languages are mainly used for concurrent computing which is a very difficult problem to debug with classical debuggers. I know it is not a mainstream way to "debug" but a very useful one once one learns it.
- owens99 7y agoWow, since moving over to Elixir/Phoenix I have been doing all my bug fixing this way but wasn't sure if I was doing it right!
- nickjj 7y agoThere might be (and probably are), but I haven't really come across anything yet that made me think I chose the wrong tech or I'm deeply upset about a certain design decision. On the other hand, there's so many nice things that you can take advantage of without being a functional programming / Elixir god and every time you figure something out you think "how the heck was I able to survive before being able to do this?". I'm hardly an Elixir veteran but I'm past the Kool-aid phase. I still get amazed almost every day working on this project for 2-3 hours a day.
- Thaxll 7y ago- deployment is terrible like really bad - 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
- m45t3r 7y ago> - dynamic language ( this is the worst part ), working on large code base means problems ahead 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.
- 7y ago
- skrebbel 7y agoThe needlessly dynamic typing. In practice, Elixir data structures are rarely dynamic in nature. All structs have the same fields. Virtually all lists and maps and whatnot you end up writing will have the same type for all elements. Unlike in the JS ecosystem, there is no culture of having functions with, say, optional arguments in the middle, or arguments that can be either a number or a function or a Date object or stuff like that. Unlike the Ruby ecosystem, there's no runtime monkey patching, runtime attribute generation, runtime anything generation. Everything is as robustly typed as your average C# program, but there's no way to write that down. Or, well, there is, typespecs. And typespecs are imprecise, hard to write, borderline impossible to typecheck (dialyzer errors are shit), and generally just barely worth it because they're so hard to use. It just feels like.. such a waste. Dynamic typing is nice if it lets you iterate faster, but that's nearly never the case in practical Elixir code, because there's so little dynamism and magic abound (this is a good thing!). At the same time, not being able to see what values a parameter can be, not being able to ever make proper IDE support, for a language and ecosystem that is so static in nature, is just a missed opportunity. I still really like Elixir, and I agree with the GP's love letter, but I wish it had a friendlier way for me to communicate data structures to the program, future me, and my colleagues. I still hold hope that someone (future me, maybe?) will bolt types onto Elixir in the same way TypeScript bolted them onto JavaScript. It should be easier than TS because of how static-y the entire Elixir ecosystem already is. (Sidenote: the usual argument against static types in Elixir/Erlang is that static type checking across process boundaries is hard, if not impossible. Genius Haskellers tried it and failed. My answer is that not having type checked messages would solve 95% of my itch. In the end, most lines of Elixir code is single-process, just transformations and function calls like in any other language. I'll be happy to tell the compiler what type I expect a received message to be and crash at runtime if it isn't - just like I do when I get sent a JSON payload over a REST API or IPC or whatever)
- nickjj 7y agoI've never used a proper statically typed language for web development but with Elixir can't you solve the 90% with pattern matching and guards? For example (taken from the docs on guards): def foo(term) when is_integer(term), do: term def foo(term) when is_float(term), do: round(term) If you tried to pass a string into foo at run time, no function pattern would be matched and it would blow up. The above should also give IDEs a way to potentially say "hey, when you call foo, it accepts a term and expects an int or float", and it's also very human readable to figure out what you need to provide as arguments without IDE support. What's interesting about guards is you can also put expressions in there. Like `when foo > 3` and now suddenly you have a new type of integer that only accepts integers greater than 3, except it's not an explicit IntegerGT3 type you have defined somewhere. In practice, how much worse is that vs a "real" statically typed language, other than putting guards on things is optional?