Learning Elixir: My side-project(adrian-philipp.com) |
Learning Elixir: My side-project(adrian-philipp.com) |
I also have my own social-good oriented learning-phoenix/ecto project[2], OpenPantry, which I started some months ago after I'd learned Elixir pretty well and done a little Phoenix micro-service, but still hadn't done anything full stack or involving Ecto or Channels... We're actively looking for help building out the feature set and it's for a great cause if anyone's interested, helping those with food-insecurity to get food more easily and with more choice than many pantry programs are currently able to provide!
[1]https://github.com/stride-nyc/remote_retro [2]https://github.com/MasbiaSoupKitchenNetwork/open_pantry
If you learn to harness the power of Erlang, while writing it in Elixir, then you will see the beauty of it. Get away from Phoenix as fast as possible. Seriously it will do you wonders. It's so unnecessary and cumbersome.
OTP, OTP, OTP.. can't say it enough.
The moment that the lightbulb went on for me was when I ran `iex -S mix` in my Phoenix project and fired up erlang's observer to see its processes. I realized that phoenix is just a supervision tree made up of different microservices, much more complicated than the toy projects I'd built, but still relied on the same OTP tools and patterns. And once you understand that, it becomes trivial to use OTP to expand the functionality that Phoenix gives you out of the box.
I think what you're trying to say (and I agree if you are) is that Phoenix and Elixir are great, but you are missing out on what makes Elixir so powerful (Erlang/OTP and BEAM) if you stop there.
At the end of the day, they are both built on the same foundational concepts. You might find that you need to look up Erlang docs or find an Erlang SO answer so might as well learn the basic Erlang syntax as well. Then you have access to a larger community. Listen to Erlang Factory or Joe Armstrong's videos and so on.
I build all my front ends as stand alone ReactJS apps, so I found it very strange to need Phoenix at all. I started learning to work with OTP & Plug and realized that I can build a json-api spec API in complete Elixir without any phoenix requirements and if I need to add sockets it's very very trivial to do so.
The amount of code needed to make this all work in pure elixir is minimal.
Essentially I just didn't need it for the way I build applications these days.
EDIT: Looks like the repo is pretty abandoned actually, scratch that!
[1]: https://pragprog.com/book/wwgraphql/craft-graphql-apis-in-el...
Though that's cool a book is coming out! I remember when these projects were just getting started.
I have to say things like this tickle my funny bone though:
> def backlog(board_id) when is_integer(board_id) do
What happened to good old fashioned function foo (int bar) :)
Is there an equivalent to typescript for elixir?
I was accused of spreading misinformation when I made the same point recently on HN in another Elixir thread [2].
So my question is: Does Robert Virding's point still hold about Elixir's macros only able to use its predefined syntax, and not create new syntax like a Lisp can. I like Elixir, I just prefer Lisp's syntax. However, if Elixir is making these type of improvements I may want to revisit it, and to ensure I am not repeating old, incorrect information.
Robert Virding uses this example (look at the thread for context):
>> (defmacro kl >> ((list* k v rest) `(cons (tuple ',k ,v) (kl ,@rest))) >> (() ()))
>> which I can use
>> (kl a (+ 2 7) b 6) ==> (#(a 9) #(b 6))
Can Elixir do this or not?
Thanks!
[1] https://groups.google.com/forum/#!topic/lisp-flavoured-erlang/ensAkzcEDQQ
[2] https://news.ycombinator.com/item?id=14390916Stack: Elixir, RethinkDB, Inferno (react alternative) + Redux (The goal when I started was to learn new things, hence the unusual stack)
That plus extensive use of list comprehensions when working with arrays.
E.g
############
# no args
def my_func() do: (something) end
# an array arg, assigning the first to "head and the rest to "tail"
def my_func([ head | tail ]) do: (something) end
# two args, so you'd call this my_func/2 instead of my_func/1.
def my_func([], %{key: val}) do: (something) end
# definition with a guard clause, so it checks the value of the arg when determining whether it matches
def my_func(val) when (val > 10) do: (something) end
# wildcard matcher for any single arg passed
def my_func(_) do: (something) end
############
Pattern matching was my gateway drug for getting into Elixir, and it's just the tip of the iceberg when it comes to what makes the language great to work with. It's particularly elegant when pattern matching responses from HTTP requests, using switch statements. No need to put try catch blocks around everything, just pattern match against a tuple with :ok and a tuple with :error
right_age(X) when X >= 16, X =< 104 ->
true;
right_age(_) ->
false.
http://learnyousomeerlang.com/syntax-in-functions#guards-gua...But if you don't mind me nerd-sniping you ... someone's done something a bit crazier recently.
It's called Elmchemy[1], which lets you write Elm that compiles to elixir. Recent blog post[2].
---
[1] https://github.com/wende/elmchemy
[2] https://hackernoon.com/elmchemy-write-type-safe-elixir-code-...
def backlog(board_id) when is_integer(board_id), do: something
and
def backlog(board_id) when is_list(board_id), do: some_list_thing
function foo: [int: bar] do
bar * bar
end
But, no the Erlang type system is not what you'd normally expect. I really like this paper on success types that kind of goes over the type system: http://user.it.uu.se/~kostis/Papers/succ_types.pdfIt doesn't let you do things like:
"for (int bar) and 0 <= bar <= 3" while the "when" guard construct can do that. Moreover it lets you have multiple function heads as well. So you can defined multiple for (int bar) with different conditions. Or even a "foo" with a string bar.
The point I think he was trying to make was that Lisp's syntax is less restrictive, but that seemed to get confused by:
- mentioning the some people say it has no syntax (which it definitely does have)
- saying macros can create new syntax (which they can't in Lisp)
Elixir macros are approximately the same as Lisp macros (apart from not being able to take a variable number of arguments).
This is a straight port of that macro into Elixir:
defp kl_i([k, v | t]) do
quote do
[
{unquote(Macro.escape(k)), unquote v}
| unquote kl_i t
]
end
end
defp kl_i([]), do: quote do: []
defmacro kl(l), do: kl_i l
# usage
IO.inspect kl [a, 2 + 7, b, 6]
# => [{{:a, [line: 16], nil}, 9}, {{:b, [line: 16], nil}, 6}]
Note: Elixir's syntax does get in the way a bit here, the `a` and `b` are converted into nullary calls, you could use a different escaping function to fix this defp escape_key({v1, v2}), do: {escape_key(v1), escape_key(v2)}
defp escape_key({:{}, pos, els}) when is_list(pos), do: {:{}, pos, Enum.map(els, &escape_key/1)}
defp escape_key(els) when is_list(els), do: Enum.map(els, &escape_key/1)
defp escape_key({sym, pos, nil}) when is_list(pos), do: Macro.escape sym"What I mean here is that elixir has a predefined syntax. It has a number of predefined syntactic constructs, operators and function calls. And while I can with macros change the meaning of the constructs I cannot add new constructs or operators as the handling of macros is done after parsing. So if I want to add something completely new it ends up syntactically being a function call. Now elixir has many ways of disguising function calls at the end of the day they are syntactically still function calls. For example parentheses around arguments are optional, there is special syntax for property lists where the keys are all atoms, special syntax for using these property lists as function arguments, special handling of do ... end when it is a function argument, etc. Elixir's uniform abstract syntax makes it easier to work with these but I am still limited by only having the existing syntax."
Sure does: http://erlang.org/doc/apps/dialyzer/dialyzer_chapter.html
> I'm not sure how that competes with types much less is so different from any other runtime conditional.
You can write:
f(5) ->
"got a 5";
f(X) when is_integer(X), X > 1000 ->
"got an integer greater than 1000";
f(X) ->
"not 5 and not an integer greater than 1000".
Sure you can do it with an if statement in some languages. Except in Erlang can also have the same guards on process message receive expressions. There would have to process all messages then check the type and somehow re-insert it back into the message queue.http://learnyousomeerlang.com/dialyzer
Erlang is a dynamic, strongly typed language. Dynamic means the compiler doesn't do static type checks. "Strongly typed" means once a variable has a type it usually doesn't get automatically coerced to other types. So adding a string "5" and an integer 1 doesn't return a string "6" or integer 6 but throws an exception.
In addition to the compiler there is also a tool called Dialyzer. It's based on the idea of Success Types (paper on it: http://www.it.uu.se/research/group/hipe/papers/succ_types.pd...).
It checks for type inconsistencies, violations, etc just like a compiler in a traditional statically typed language. But it doesn't emit optimized code based on it. So say it deduced that a variable can only be an integer between 1 and 10 it doesn't have a way to emit some optimized assembly code based on that information. Besides the types it automatically infers, additional typing constraints can be specified by the user.
So for the guard in question, if say the second clause of right_age(_) -> wasn't there and it saw calls to the function with X=105, then Dialyzer would come back with an error with something like:
your_module:105: Clause guard cannot succeed.