1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
-module(tut15). -export([start/0, ping/2, pong/0]). ping(0, Pong_PID) -> Pong_PID ! finished, io:format("ping finished~n", []); ping(N, Pong_PID) -> Pong_PID ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1, Pong_PID). pong() -> receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end. start() -> Pong_PID = spawn(tut15, pong, []), spawn(tut15, ping, [3, Pong_PID]). |
It's not pretty, it's Prolog-ish. The Clojure article claims how close they got with lightweight threads (true green threads) and this is the same exercise done in Clojure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
(defsfn pong [] (if (== n 0) (do (! :pong :finished) (println "ping finished")) (do (! :pong [:ping @self]) (receive :pong (println "Ping received pong")) (recur (dec n))))) (defsfn pong [] (receive :finished (println "Pong finished") [:ping ping] (do (println "Pong received ping") (! ping :pong) (recur)))) (defn -main [] (register :pong (spawn pong)) (spawn ping 3) :ok) |
People that like the Lisp-y aesthetics of programming directly in the AST representation through the structure of parenthesis based blocks will find this very pretty.
I personally spent years looking into code like this (Common Lisp, Elisp, Scheme, etc) and I still can't get used to it. Once you have a competent editor such as Emacs, that can deal with the proper parenthesis handling, it's easier, yes, but I still can't find the joy in doing this kind of hackish syntax.
Elixir is not just a new syntax on top of Erlang, as the great book Metaprogramming Elixir will teach you, it opens up the entire Erlang BEAM AST through the usage of the quote/unquote mechanics, making programming directly into the AST through "Hygienic Macros" a breeze. It's really the best of both worlds of having a modern, good looking, joyful syntax and the same power a Lisp-y language gives you in terms of well behaved macros.
Now, this is the same example as above, in Elixir:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
defmodule ExPingPongTut15 do def ping(0, pong_pid) do send pong_pid, :finished IO.puts("ping finished") end def ping(n, pong_pid) do send pong_pid, {:ping, self} receive do :pong -> IO.puts("Ping received pong") end ping(n - 1, pong_pid) end def pong do receive do :finished -> IO.puts("Pong finished") {:ping, ping_pid} -> IO.puts("Pong received ping") send ping_pid, :pong pong() end end def start do pong_pid = spawn(fn -> pong end) spawn(fn -> ping(3, pong_pid) end) end end |
Because of the power of pattern matching in the functions arguments signature, you can define 2 separated functions, avoiding the "if" as in the Clojure example. Of course, because it's Clojure and it has a complete macro system, and because it's core.match has the proper pattern matching mechanisms, you can emulate the same thing through external libraries such as defun though.
This was just a simple exercise, I hope it shed some light on the basic similarities between these 3 languages. And as "ugly" as Erlang may feel, I still feel more comfortable with its quirks then nested parenthesis.