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.