Scripting with Elixir
underjord.io
underjord.io
That said I think the underlying lack of data mutability really increases the potential for code reuse and scripting.
Even scripts require some structure and code quality. I organize my bash scripts, otherwise they quickly become unmanagable.
If you dislike typing long module names, and for some unknown reasons won't use short alias (alias My.Long.Module.Name, as: M) or import. Then the best workaround is to use "M" as a module name:
#/usr/bin/env elixir
...
defmodule M do
...
def f, do: ...
...
end
...
M.f()
Anyway, I think there was Joe Armstrong's blog post, where he said that modules are just containers for functions. The way we decide which module to put a function is completely arbitrary. Sometimes it's very hard to decide, or maybe the same function could equally belong to several modules. I designing my toy programming language where functions do not nelong to modules or namespaces, but you can query them based on metadata like tags/types/docs/etc.One can still reuse code with "Code.require_file", which is nice to have.
sum.(2, 3)
Maybe a but clunky but no module required.
In the Python space, though not the same, the ergonomics of which kinda get you there, there's this: pipe https://pypi.org/project/pipe/
They are actually even nicer under Livebook: https://livebook.dev/
It can let you drag-drop reorder, enable/disable, steps in a pipeline: https://github.com/livebook-dev/livebook/blob/main/lib/liveb...
It is really wild.
Granted, it's not as much of a learning curve as personal Lisp macros.
I would rewrite the example from the post like this (NOTE: I didn't run it, so it might be slightly incorrect):
#/usr/bin/env elixir
"file.json"
|> File.read!()
|> Jason.decode!()
|> Enum.flat_map(& &1["children"])
|> Enum.map(&{&1["id"], &1})
|> Enum.into(%{})
---1. although map is much easier to parallelize/vectorize (e.g. in Array PLs)
#/usr/bin/env elixir
"file.json"
|> File.read!()
|> Jason.decode!()
|> Enum.flat_map(fn x -> x["children"] end)
|> Enum.map(fn x -> {x["id"], x} end)
|> Enum.into(%{})
[1] The `&a_function_with_odd_arguments(&2, &1)` type notation can still be very useful and is (subjectively) a bit less symbol-salad.The capture operator syntax in Elixir isn't a very ergonomic design.
It uses the same symbol for both fun and arguments, and also for MFAs (&M.f/a).
It doesn't allow nesting, so in these cases there is no choice but to use the full notation.
In case the intent isn't clear from the code, I'll also use the full notation.
Also, I think it's much more acceptable to use shorthand/sugar in one-off scripts rather than in "real" code.
NOTE: in your example you use "x" for argument, so it's not much better than "&1". Since you already using the full notation, it's better to meaningfull argument names, such as "item" and "subitem".
If I see only only this: (x,y) -> ... I can guess quite well what x and y are used for, and I suspect I am not the only developer who recognizes this as a 'parameter list' partly because the names happen to use 'x' and 'y' which are otherwise non-descriptive.
Nevertheless, your point still stands.
fn x -> ... x ... end
and fn item -> ... item ... end
is not that far, but it makes a huge different for other team members and even for the future self.Beside Elixir/Erlang, I also a kdb+/q (an APL family language) developer, and there the 1st,2nd,3rd arguments of the function are called x, y, z correspondingly by default. E.g.
instead of
f: {[n] n*n}
f 5
=> 25
you can write: f: {x*x}
f 5
=> 25
or instead of: add: {[a;b] a+b}
add[10;20]
=> 30
you can write: add: {x+y}
add[10;20]
=> 30
There is a wide range of syntactic sugar and shorthands in PLs. If your PL supports multiple options, you need to choose the best one depending on the context or situation.Having said that, I believe that modern PLs plagued with the flexibility syndrome, and I would prefer it if the PL designers selected a single approach and sticked to it.
"file.json"
|> File.read!()
|> Jason.decode!()
|> Stream.flat_map(& &1["children"])
|> Map.new(fn child -> {child["id"], child} end)edit: it's been out since 2015 it looks like. Not sure why but I didn't know about this before. It would have been useful.
I'm glad that my 2 favorite languages have that feature.
I wish more developers knew about single-file scripts with dependencies. People seem to only discover them accidentally through a language or runtime that has the support built in. It means Elixir will contribute to the awareness, but Elixir itself is niche. Maybe Deno and Bun, as JavaScript runtimes, will popularize them. Having a common term not specific to any language may help, as there doesn't seem to be one. It hinders search. "(Single-file) scripts with dependencies" is what I have settled on.
Edit: Added the Crystal example and the second paragraph.
Plus, seeing code like https://github.com/colinrymer/leftpad.ex/blob/8d2230bf094eed... is just fun:
defmodule Leftpad do
@moduledoc """
Remembering `String.rjust/3` can be difficult, so Leftpad provides you
another way to easily left pad/right justify your UTF-8 encoded binaries.
"""
@doc ~S"""
Provides basically the same functionality as [`String.rjust/3`](http://elixir-lang.org/docs/stable/elixir/String.html#rjust/3)
## Examples
iex> Leftpad.pad("foo", 5)
" foo"
iex> Leftpad.pad("foobar", 6)
"foobar"
iex> Leftpad.pad("1", 2, ?0)
"01"
"""
@spec pad(string :: String.t, count :: non_neg_integer, char :: char) :: String.t
def pad(string, count, char \\ 32), do: String.rjust(string, count, char)
end
Look at that one line doing all the work, and even it delegates it to another function. defdelegate pad(string, count, char \\ 32), to: String, as: :rjust %pip install foo~=1.5
%pip install bar~=1.7
import foo
import barI believe this made me use Elixir instead of Ruby (my natural scripting language) more and more.
I use it on a regular basis for my work on https://transport.data.gouv.fr/?locale=en (which is Elixir-based and open-source), as one can see at https://github.com/etalab/transport-site/tree/master/scripts.
What I like the most is that it helps me start ideas, experiments & data analysis with their own set of dependencies (without much care of "will this impact the main application?"), store those experiments in the same repo at the main application, and maybe later promote some of those experiments to the main application source code.
Concrete examples include:
- data wrangling https://github.com/etalab/transport-site/blob/master/scripts..., https://github.com/etalab/transport-site/blob/master/scripts...
- exploring a new tool https://github.com/etalab/transport-site/blob/master/scripts...
- gradually iterating to create scripts to generate XML queries https://github.com/etalab/transport-site/tree/master/scripts... (later incorporated in the application)
- comparing GTFS (transportation data) files via scripting https://github.com/etalab/transport-site/blob/master/scripts...
- learning how to compute the checksum of the internal content of a zip file https://github.com/etalab/transport-site/blob/master/scripts...
As mentioned in the article, I can definitely recommend to check out https://github.com/wojtekmach/mix_install_examples which has a long list of examples.
One last tip is that you can also use "mix run script.exs" on a file not using "Mix.install", in order to rely on the same dependencies & configuration as the main application (e.g. to run a Ecto query https://github.com/etalab/transport-site/blob/master/scripts...).
Seems like a no-brainer to go with python for the long term. Why would anyone take the time to use this?
Because the author likes Elixir and its trivial concurrency story. But they also wrote the above: Use what you want, for them it's Elixir. Why complain about other people's preferences when they aren't actually foisting it on you?
No one should be learning Elixir solely for the purpose of writing short, one-off scripts, though.
Which isn’t to say the change isn’t welcome or not needed. Just that the limitation was overblown in most cases.
Another case, if your code base is in Elixir, picking an unrelated different language (python) would (IMHO) be as silly a decision as picking elixir for scripts when you have a python app, and I would strongly question that person's judgment and fitness for making sustainable and maintainable technical decisions.
1. you prefer functional style languages, such as elixir 2. you want to avoid `pip install` ing dependencies