Functional Domain Programming for Elixir?

I read the book “Domain Modelllng Made Functional” several times. And it was absolute clear, that I want to have some types sich as

“Unchecked Order” – read → “Verified Order” – send → “Dispatched Order”

to be sure, that I always deal with the right type of order, for what reason soever. An unread order cannot be send to database for example.

But as Elixir has not really a type system (yet) - is this the right way to go, or how would I deal with this? When I get an order, I need to verify waiter rights. I have to check if I have to ask for length, price, weight, article text, constrainted articles… would I reflect these rules by types? Or would I simply trust on the pipe chain?

Perhaps you could use structs?

@spec read(UncheckedOrder.t()) :: VerifiedOrder.t()
def read(%UncheckedOrder{} = order) do
  # ...
end
# ...

@spec send(VerifiedOrder.t()) :: DispatchedOrder.t()
def send(%VerifiedOrder{} = order) do
  # ...
end
# ...

?

That is a bit of my problem with many resources about functional programming - they cover static typing instead, which makes it not particularly well transferable to Elixir (or Clojure, or some others). In this case use different structs, as @RudManusachi mentioned, but in my experience this will quickly start to feel weird and overengineered (at least that’s my experience, but maybe I did that wrong and someone could share a success story of this approach in Elixir).

IMO Elixir needs to find its own way to do functional modeling, DDD etc, because copying from Scala or F# won’t work. I wrote something about this few years ago, but this is just scratching the surface (and not necessarily the only direction).

2 Likes

Maybe like this?

  defmodule Order do
    @type t( status) ::
      %__MODULE__{
        id: Ecto.UUID.t(),
        status: status
      }

    defstruct [ :id, :status]
  end

  @spec send_order( Order.t( :verified)) :: :ok | { :error, :unverified_order}
  def send_order( order)

  def send_order( %{ status: :verified}) do
    :ok
  end

  def send_order( %{ status: _}) do
    { :error, :unverified_order}
  end

2 Likes

What is the problem with using just data with a specific structure and pattern matching? It achieves exactly the same thing and I would say for basic cases it’s easier to read.

The functions can be exactly as @DaAnalyst mentioned, but it doesn’t mean that you necessarily need a struct.

That’s exactly what many of us do but the sentiment expressed in many such discussions is that sometimes there occurs a “struct bloat” or “too many different types of maps we’re matching on in function heads”-bloat. Which I’ve also seen happening. There are ways around it, of course.

One problem I find with this approach is refactoring. Something like “find all occurences of this map shape” and rename a key there. Or make sure certain key is only of type integer etc.

5 Likes

Yeah, that’s why I prefer to just add more structs. It gets annoying but it’s more future-proof.

1 Like