Justin

Justin

Getting things done with Elixir - tips?

I finished working thru “Programming Elixir 1.3”. It was theoretically interesting, but I’m left wondering how to use it to accomplish real world tasks.

I’ve been a developer for a long time. My background is in imperative procedural languages, focusing on back end apps that are highly DB, and moderately computationally, intensive.

Am I just overlooking the obvious?

Most Liked

AstonJ

AstonJ

Programming Elixir teaches you the language - how you apply it is either down to you (based on your experience, perhaps specifically, in designing concurrent/parallel systems) or, based on what you learn next. Luckily, there’s a lot of material on the latter!

I have currently read/done:

And loved every single one of them (check out my reviews in their respective threads). Dave’s course shows you how to apply Elixir - and it is one of the best programming courses I have ever done!

There are other books and courses that show you how to ‘think’ in or get the most out of Elixir too, have a look through our Learning Resources > Books and Learning Resources > Courses sections :023:

bbense

bbense

Insert semi made up quote about “Every sufficiently concurrent program includes a buggy half-implemented version of the BEAM”.

aseigo

aseigo

That is the essence of functional programming in the rough, yes. You have functions which take data as input and return some value as a result. So you could describe any / all functional programs in the way you have.

Where it gets more interesting in this case is in the details of the “how” with Elixir. For instance, querying a handful of data sources: this is trivial to do in parallel with Elixir by putting each query into its own process. You may choose to do this with one-off usage of Tasks, or you might create GenServers that are re-used between queries. You may even create a worker pool to limit the number of queries being made in parallel. (The latter is what Ecto does behind the scenes.)

Those processes would then preferably be monitored by a Supervisor (or by just manually linking, via spawn_link e.g.), so that if something goes wrong in one of them they can be restarted / retried, or even abort the parent process that is doing the top-level task.

Depending on the shape of the data being processed, the “arbitrary logic” you mention can be written elegantly using Elixir’s facilities for pattern matching, pipelining using the |> operator, etc.

You can then very easily turn that into a distributed application where a bunch of computers (high end servers or clusters of little RPi’s even) work on that task together.

So … while you could describe it as a “ton of functions”, and you could literally write it that way, there is a lot more in the toolbox. Personally, that toolbox added on top of FP, great tooling, etc. is what makes Elixir so exciting.

peerreynders

peerreynders

Pretty much - for example:

defmodule Summary  do
  defstruct order_id: 0, items: [], customer_name: "", date: Date.utc_today()

  defp cons_description(order_id),
    do:
      fn
        (%Item{order_id: id, item: description}, items) when id === order_id ->
          [description | items]
        (_, items) ->
          items
      end

  defp gather_descriptions(items, order_id),
    do: List.foldl(items, [], cons_description(order_id))

  defp find_customer(customers, customer_id),
    do: Enum.find(customers, fn(%Customer{id: id}) -> id === customer_id end)

  defp lookup_name(customers, id) do
    with %Customer{name: name} <- find_customer(customers, id) do
      name
    else
      _ -> "N.A."
    end
  end

  def summarize_order(items, customers) do
    fn(%Order{id: order_id, customer_id: customer_id, date: date}) ->
      %Summary{
        order_id: order_id,
        items: gather_descriptions(items, order_id),
        customer_name: lookup_name(customers, customer_id),
        date: date
      }
    end
end

  def from(orders, items, customers) do
    orders
    |> Enum.map(summarize_order(items, customers))
  end
end
$ elixir demo.exs
Customers:
[%Customer{id: 1, name: "Samson Bowman"},
 %Customer{id: 2, name: "Zelda Graves"}, 
 %Customer{id: 3, name: "Noah Hensley"},
 %Customer{id: 4, name: "Noelle Haynes"},
 %Customer{id: 5, name: "Paloma Deleon"}]
Orders:
[%Order{customer_id: 3, date: ~D[2014-03-20], id: 1},
 %Order{customer_id: 4, date: ~D[2014-04-25], id: 2},
 %Order{customer_id: 5, date: ~D[2014-07-17], id: 3},
 %Order{customer_id: 2, date: ~D[2014-01-05], id: 4},
 %Order{customer_id: 5, date: ~D[2014-06-09], id: 5}]
Items:
[%Item{item: "gum", order_id: 2}, %Item{item: "sandals", order_id: 4},
 %Item{item: "pen", order_id: 3}, %Item{item: "gum", order_id: 1},
 %Item{item: "pen", order_id: 2}, %Item{item: "chips", order_id: 3},
 %Item{item: "pop", order_id: 1}, %Item{item: "chips", order_id: 5}]
Summaries:
[%Summary{customer_name: "Noah Hensley", order_id: 1, date: "2014-03-20", items:
 ["pop","gum"]},
 %Summary{customer_name: "Noelle Haynes", order_id: 2, date: "2014-04-25", items:
 ["pen","gum"]},
 %Summary{customer_name: "Paloma Deleon", order_id: 3, date: "2014-07-17", items:
 ["chips","pen"]},
 %Summary{customer_name: "Zelda Graves", order_id: 4, date: "2014-01-05", items:
 ["sandals"]},
 %Summary{customer_name: "Paloma Deleon", order_id: 5, date: "2014-06-09", items:
 ["chips"]}]
$

Full code (with alternate Summary module):

# https://stackoverflow.com/questions/25438893/haskell-how-to-implement-sql-like-operations#answer-25438952

defmodule Customer do
  defstruct id: 0, name: "First Last"

  def new(id, name),
    do:
      %Customer{
        id: id,
        name: name
      }
end

defmodule Order do
  defstruct id: 0, customer_id: 0, date: Date.utc_today()

  def new(id, customer_id, date),
    do:
      %Order{
        id: id,
        customer_id: customer_id,
        date: date
      }
end

defmodule Item do
  defstruct order_id: 0, item: "Description"

  def new(order_id, item),
    do:
      %Item{
        order_id: order_id,
        item: item
      }
end

defmodule Summary  do
  defstruct order_id: 0, items: [], customer_name: "", date: Date.utc_today()

  defp cons_description(%Item{order_id: id, item: description}, m),
    do: Map.update(m, id, [description], &([description|&1]))

  defp make_descriptions_map(items),
    do: List.foldl(items, Map.new(), &cons_description/2)

  defp put_name(%Customer{id: id, name: name}, m),
    do: Map.put(m, id, name)

  defp make_names_map(names),
    do: List.foldl(names, Map.new(), &put_name/2)

  def summarize_order(items, customers) do
    descriptions = make_descriptions_map(items)
    names = make_names_map(customers)

    fn(%Order{id: order_id, customer_id: customer_id, date: date}) ->
      %Summary{
        order_id: order_id,
        items: Map.get(descriptions, order_id, []),
        customer_name: Map.get(names, customer_id, "N.A."),
        date: date
      }
    end
  end

  def from(orders, items, customers),
    do: Enum.map(orders, summarize_order(items, customers))

end

defimpl Inspect, for: Summary do
  import Inspect.Algebra

  def inspect(summary, opts) do
    concat [
      "%Summary{customer_name: ",
      to_doc(summary.customer_name, opts),
      ", order_id: ",
      to_doc(summary.order_id, opts),
      ", date: ",
      to_doc(Date.to_iso8601(summary.date), opts),
      ", items: ",
      to_doc(summary.items, opts),
      "}"
    ]
  end
end

defmodule Demo do

  defp make_customers,
    do: [
      Customer.new(1, "Samson Bowman"),
      Customer.new(2, "Zelda Graves"),
      Customer.new(3, "Noah Hensley"),
      Customer.new(4, "Noelle Haynes"),
      Customer.new(5, "Paloma Deleon")
    ]

  def to_date(year,month,day) do
    with {:ok, date} <- Date.new(year, month, day) do
      date
    else
      _ -> Date.utc_today()
    end
  end

  def make_order(id, customer_id, year, month, day),
    do: Order.new(id, customer_id, to_date(year, month, day))

  defp make_orders,
    do: [
      make_order(1, 3, 2014, 3, 20),
      make_order(2, 4, 2014, 4, 25),
      make_order(3, 5, 2014, 7, 17),
      make_order(4, 2, 2014, 1, 5),
      make_order(5, 5, 2014, 6, 9)
    ]

  defp make_items,
    do: [
      Item.new(2, "gum"),
      Item.new(4, "sandals"),
      Item.new(3, "pen"),
      Item.new(1, "gum"),
      Item.new(2, "pen"),
      Item.new(3, "chips"),
      Item.new(1, "pop"),
      Item.new(5, "chips")
    ]

  def run() do
    customers = make_customers()
    orders = make_orders()
    items = make_items()

    IO.puts("Customers:")
    IO.inspect(customers)
    IO.puts("Orders:")
    IO.inspect(orders)
    IO.puts("Items:")
    IO.inspect(items)

    IO.puts("Summaries:")
    IO.inspect(Summary.from(orders, items, customers))
  end

end

Demo.run()

One thing to keep in mind is that functional programming is “value-oriented programming” while imperative programming is largely PLace-Oriented Programming (PLOP) due to the fact that it heavily relies on mutable state (locations). See Rich Hickey’s Value of Values talk.

Ultimately value based computations compose better (again Rich Hickey Simple made Easy).

The preceding demonstration code is entirely sequential. As already mentioned with BEAM languages exploiting concurrency relentlessly is always an option. For example for the lifetime of the script:

  • one process could steward the customer list and be responsible for looking up the customer name by customer_id.
  • another process could steward the items list and be responsible for serving the list of item descriptions for a particular order_id.
  • each order record could be serviced by it’s own process, returning the completed summary upon termination.
Justin

Justin

Google is great when you just want to get something done. Books have their use too. They can help to answer questions you didn’t even know to ask.

Where Next?

Popular in Questions Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New

We're in Beta

About us Mission Statement