Elixir protocols vs. Clojure multimethods
mattmower.com
mattmower.com
For what it's worth, Clojure also has a much closer fit to Elixir Protocols called... a Protocol.
https://www.braveclojure.com/multimethods-records-protocols/...
They too can only dispatch on the type of the first argument, but are more structured (you can add multiple pieces of behavior at a time) and performant than multimethods where that's the behavior you're looking for.
https://github.com/clojure/core.match -- pattern matching as a library
https://github.com/killme2008/defun -- using core.match to implement Elixir/Erlang-like function definition
https://github.com/noprompt/meander -- advanced pattern matching, for some fun and flavor :)
What's also fun is that core.match is implemented using a paper from INRIA on how to efficiently convert patterns into decision trees:
https://github.com/clojure/core.match/wiki/Understanding-the...
Here's Jose Valim talking about it:
> I’ve learned a lot also from Clojure because, at the time I was thinking about Elixir, Clojure was already around. I like to say it’s one of the top three influences in Elixir
[...]
> The main, the top three influences are Erlang, Ruby, and Clojure.
[...]
> I was like, no, but I’m going to call them protocols because there are a lot of similarities between Clojure and Elixir in terms of them being dynamic languages and in terms of the macro system. I was like, okay, I’m going to call them protocols because the closest thing we have today to what I want is Clojure
And he goes on talking about more inspiration and similarities from Clojure like Agents, etc.
Full exert is here: http://blog.cognitect.com/cognicast/120
While Phoenix was the killer app for Elixir, and Elixir has far superior readability (using the Ruby syntax); there were couple of things that were off-putting and I struggled with them.
1. everything is inside a module was an unnecessary distraction
2. And then the separation between anonymous and named functions simply were unnecessary
3. And that I would have to declare the data / record inside a module (??)
Elixir felt like a functional language un-necessarily trying to look like a class based language.
I sometimes feel that had Elixir had only supported functions outside of modules... oh that freedom.
But some of the thought that went into Flow, Channels (which has become the de riguere now), mix (developer ergonomics ftw), those micro-second latency responses, distillery are still too classy and amazing.
Not really. Named functions live in modules because that is how it is done in Erlang; Elixir is compiled to Erlang's abstract syntax. In practice does anyone define Clojure functions outside of a namespace? Haskell functions are always defined in modules too, do you think they got that from class based languages?
Clojure REPLS allow switching the namespace, and anything you define goes intoo the current namespace. So it is more ergonomic.
iex(1)> defmodule Foo, do: def bar(x), do: x + 1
{:module, Foo,
<<70, 79, 82, 49, 0, 0, 4, 192, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 135,
0, 0, 0, 15, 10, 69, 108, 105, 120, 105, 114, 46, 70, 111, 111, 8, 95, 95,
105, 110, 102, 111, 95, 95, 10, 97, 116, ...>>, {:bar, 1}}
iex(2)> Foo.bar(1)
2
iex(3)> import Foo
Foo
iex(4)> bar(1)
2In clojure, it is:
user> (defn bar [x] (+ x 1))
If you want it inside a namespace named foo, then it is: user> (in-ns 'foo)
foo> (defn bar [x] (+ x 1)) >bar = fn x -> x + 1 end
The most unfortunate thing though is then you have to have a different calling convention. Its the greatest flaw in Elixir by far but they didn't have a reasonable alternative given the constraints of Erlang.Still for defining a named function in the REPL its the same in Haskell and most other languages I believe that you can't define a new named function or add a function to a module in the REPL, though at least in Haskell the calling convention for a variable bound to a lambda is the same as for a named top-level function. LISPs have always had a different notion of how the REPL integrates into the development experience of a running program, and I don't think its really been replicated elsewhere.
bar = &(&1 + 1)
(def bar #(+ % 1))
or even (def bar inc)
but the difference is that in Clojure, all of these are `IFn`s, and have the same calling syntax, unlike Elixir.fn x, y -> x * y end
or
&(&1 * &2)
When used inside a map or reduce or when the function is a direct mathematical operation on its arguments, it can be a bit quicker to parse the capture syntax than the fn ... end syntax.
In Clojure too, the fns, records everything live inside a namespace but that is a modularity mechanism for code organisation.
It makes me structure my code and group related concerns at time of writing. I now code my functions as a working collective rather than individual items.
And with .exs files, you can have multiple modules in one file for quick scripting.[0]
[0]https://github.com/matteing/stack/blob/main/server/boilerpla...
The distinction b/w anonymous and named functions is especially icky.
I also agree that Elixir leads to more readable code, lots of people in the Clojure community tend to write dense one liners to appear "cool".
Why?
Because higher density makes it easier to see the entirety of a context: more code on the screen makes it easier to spot the relationships.
This happens in other languages too, of course, but it’s easy to see why Closure pushes flow in that direction.
You can just ignore modules/namespaces. While you have some procedures private to the file, you can expose them and they're just in the global namespace.
I too found the "." syntax for anonymous functions a bit jarring at first. Why treat them differently? In practice I don't even notice it now. It's never been confusing, it's just a wart.
Also, I found the one-struct-per-module thing a bit odd to begin with but in practice it makes a lot of sense. Also since you can put modules inside modules it's no encumberance if you want to declare a number of related structs. Again, once I was used to it I apprecitated the simplicity.
I disagree with your characterisation: I perceive no "class-based"'ness about Elixir. Do you have some examples? Perhaps there is somethign I have missed. So far, given that it's not exposing a class based system underneath, Elixir has seemed even further from this than Clojure.
And I have, in general, found the tooling support friendlier for Elixir. The only thing that I really gripe about is the inability to communicate between editor and REPL.
About the tooling definitely yes. Elixir is pretty pretty good; at compile, build and employ time.
[1] https://github.com/clojure/clojure-clr [2] https://github.com/clojerl/clojerl
[1] https://lfe.io/
defmodule A do
defstruct [...]
end
defmodule B do
defstruct [...]
end
defprotocol C do
def foo(a, b)
end
defimpl C, for: Any do
def foo(x1 = %m1{}, x2 = %m2{})
Module.concat(m1, m2).foo(x1, x2)
end
end
defmodule A.A do
def foo...
end
defmodule A.B do
def foo...Write a typical computation such as Fibonacci in Java and Erlang/Elixir and compare. Fortunately someone has already done this.
Elixir is 3x slower than C and 2x slower than Java for this single thread example.
https://github.com/drujensen/fib
Apparently this upsets people for me to point this out. However, I did not say that Elixir was slow in general or a bad choice. It's an excellent choice for problems which suit parallelization or which require reliable, consistent performance.
Since the parent poster had commented that adding this multi-module dispatch would not be performant, I merely pointed out that the single thread peformance was already slow (as in, why worry too much about the performance cost of the multi dispatch suggestion).
It used to be that on HN you down-voted for people who were obstructing conversation, being disingenuous or in some cases being excessively disrespectful.
Now having read 3 of your comments and seeing all 3 are heavily down-voted and yet the content of your messages is constructive and interesting.
If you disagree with something, fine, just don’t vote on it. Save the down-votes for bad actors, not someone with a different view.
The last thing HN needs is to become the kind of place where you’re actively encouraged to karma farm or whatever that term is for that behaviour on reddit.
I always made my statements the way I feel like, since the BBS days, and voting systems haven't change that.
There will always people that want to silence you, regardless of the medium.
Life is too short to care for them.
When was the last time you needed to write a Fibonacci for anything you’ve built in your career?
It’s a benchmark that doesn’t show anything useful for real world applicability.
I said per-thread, but I meant single-thread... as in CPU bound single process activity. That is undisputedly slow compared to languages like Java, C/C++, Go, etc. That is also very much not what Erlang is designed for. It adds a lot of overhead with the supervisor and other features which do not give a benefit for single thread CPU heavy activities.
And since the parent poster mentioned the multi-dispatch approach would not be performant, I attempted to suggest that the performance cost of that would be less relevant considering Elixir is already not very performant in single thread cases. In other words, it was a moot point.
I never said that Elixir/Erlang was slow for multi-thread/process distributed activities. Obviously that is where it really excels. But if you want to crunch numbers sequentially in a way that cannot be spread across multiple processes/threads, then you will find Elixir to be slow.
The benchmarks you are referring to are very much multi-thread comparisons. They are specifically NOT what I was was slow.
I don't agree that performance is a moot point if what you use is slower than some alternatives, and I think that's the main point where we disagree.
But if this really is a big deal, then it would be fair to consider the other language features which do not contribute to the reliability, scalability, and other core Erlang/BEAM features. For example, what is the cost of pattern matching in general? Doesn't that add considerable overhead, just for the benefit of making code cleaner? If that's acceptable, then I don't see why "just one more" feature - in this case a homegrown multi-method dispatch across modules - should be considered non-performant.
I assumed that, with Elixir macro support, someone could implement protocols with full pattern matching. Just way above my current pay grade!
I've been to Elixir conferences, and they felt like people were just encouraging each other to build solid software WITH each other. I've not seen this level of camaraderie for other programming language communities.
Elixir devs — and I am super biased here — are a special bunch :)
Sure, we have Luminus in the Clojureverse but its just not as easy and straightforward as the Rails-like experience of Phoenix. You don't have Hickey personally responding to comments on HN/Reddit etc.
The Clojurescript community is friendlier, IMO.
Defn: https://podtail.com/en/podcast/defn/
The REPL: https://www.therepl.net/episodes/ (Seems to have gone quiet)
ClojureScript Podcast: https://clojurescriptpodcast.com/
Functional Design in Clojure: https://clojuredesign.club/ (Also seems to have gone quiet since December)
LispCast by Eric Normand: https://lispcast.com/
Cognitect, the company behind Clojure also has their own podcast but I haven't found it to be that interesting most of the time, at least yet: https://www.cognitect.com/cognicast/index.html
Not specifically Clojure-related but has discussed a few times including wit Rich Hickey (as well as other unrelated great conversations!): CaSe https://www.case-podcast.org/
Completely not at all about Clojure but great software podcasts:
Go Time: Even though I hardly ever write Go, I find their conversations to be really great and having lessons beyond Go. It helps that I am interested in the language though: https://changelog.com/gotime
Web Development and Development in general - The Bike Shed: https://www.bikeshed.fm/
Software Engineering Radio: https://www.se-radio.net/
CoRecursive: https://corecursive.com/
Inside Java: https://inside.java/
I’ve listened to some Defn with mixed results. I really liked their interview with Daniel Higginbotham, especially when they argued a bit about what “simple” means.
Even if some of the others have gone quiet, there plenty of backlog to choose interesting episodes from. The Bike Shed also looks right up my alley. They probably should have had some more discussion before choosing that name, though.
It's not readily apparent from the ruby like syntax though.
require 'ripper'
Ripper.sexp('a && b')
=> [:program, [[:binary, [:vcall, [:@ident, "a", [1, 0]]], :"&&", [:vcall, [:@ident, "b", [1, 5]]]]]]
Ripper.sexp(<<-RB)
if a
b
else
c
end
RB
=> [:program, [[:if, [:vcall, [:@ident, "a", [1, 5]]], [[:vcall, [:@ident, "b", [2, 4]]]], [:else, [[:vcall, [:@ident, "c", [4, 4]]]]]]]]
Ruby is just way denser than most lisps.I love Ruby, Elixir, and Lisp(s)
One striking similarly Elixir has to lisp style languages is the macro system. You have quoted and unquoted expressions just like lisp.
And like lisp most of Elixir, down to the low level stuff like if statements, modules, and so on, is built up from a few primitives.
Here are the primitives that the Elixir language is built from:
Although Microsoft has made it from Microsoft Research into official Visual Studio, it has been mostly an up hill fight to keep it there, while .NET languages group mostly cares about C# and VB, and to certain extent C++/CLI for integration with Windows APIs.
F# when taken into account by management, comes always after those three.
Most of the Visual Studio tooling for C# and VB isn't available to F# projects, you are supposed to do it manually, e.g. GUI designers, EF database to code generation, .NET 5 code generators.
That said, now I've dug in and really used it for solving some problems I am finding it an elegant and enjoyable experience.
I'd happily use either although I think Elixir has a particular fit to web applications.
However, I'd rather Clojure got proper pattern-matching than Elixir multimethods. I find pattern matching a much more powerful, flexible and useful tool.