kccarter

kccarter

Best way to pass many arguments to a function?

Looking for some advice on how a seasoned Elixir developer might go about implementing the creation of a map/struct that is dependent on several other maps.

Let’s say we’re modelling something like a Freight Shipping Order, aka “Bill of Lading”. Generally speaking, these have a lot of fields. In our case, we need to make several API calls to just get the right information to assemble one.

Consider this pseudo code, typos are to be ignored.

def fetch_freight_order(id) do 
  # These all just return a Map, constructed from a JSON response.
  shipping_customer = call_api(...)
  receiving_customer = call_api(...)
  line_items = call_api(...)
  freight_company = call_api(...)
  
 # ... and maybe 4-5 more calls to an api we don't control ...

  # To assemble a freight_order, we need all of the parts above.
  freight_order = new_freight_order(
    shipping_customer,
    receiving_customer,
    line_items,
    freight_company,
   # ... 4-5 more arguments
  )
end

Option 1
Would Elixir developers pass each argument or would they be more included to pass a keyword list, map, or use some other pattern?

# Easy to get confused at the call site which order these are all in.
def new_freight_order(
  shipping_customer, 
  receiving_customer,
  line_items,
  freight_company,
   # ... 4-5 more arguments
)

%{
  # Populate shipping_customer fields
  shipping_name: shipping_customer["Name"],
  shipping_street: shipping_customer["Street"],

  # Populate receiving_customer fields
  receiving_name: receiving_customer["Name"],
  receiving_street: receiving_customer["Street"],

  # ... maybe 50+ more fields ... 
}

end

Option 2
If we pass a single map, then we can pattern match in the function’s signature but it gets rather unwieldy pretty quickly and the call site also gets a lot busier:

def new_freight_order(%{
  shipping_customer: shipping_customer,
  receiving_customer: receiving_customer,
  line_items: line_items,
  # a bunch more matches...
}) do 

# Merge into a single freight_order
end

Option 3
Or perhaps use single pattern matches:

def new_freight_order(freight_order, %{shipping_customer: shipping_customer}) do 
  # Merge in just the shipping fields
end

def new_freight_order(freight_order, %{receiving_customer: receiving_customer}) do 
  # Merge in just the receiving fields
end

Option 4
Alternatively, would anyone create separate functions for each section?

def populate_shipping_customer(freight_order, shipping_customer) do 
end

def populate_receiving_customer(freight_order, receiving_customer) do 
end

I appreciate that style is subjective, but as new Elixir devs, we’re curious what patterns might be preferred over others. This use-case comes up quite a bit for us. A lot of the data we’re working with within our Elixir app (and showing to the user), requires a considerable number of discrete API calls just to gather the required data before we can assemble it into some sort of local representation.

Thanks in advance.

Most Liked

wojtekmach

wojtekmach

Hex Core Team

I agree that around 4 or more function arguments, things start to become dicey.

I also agree that depending on situation, Ecto changesets might feel like an overkill.

Here’s a couple options below.

Keyword.validate! is a built-in way to ensure proper key names and you can even set defaults.

For doing a bit more validations, a good old recursive function that parses the keyword list key/value pairs is often enough. Or do it inline and even convert to a map right away (to later use the assertive map.field syntax):

Map.new(options, fn
  {:host, host} when is_binary(host) -> {:host, host}
  {:port, port} when is_integer(port) -> {:port, port}
end)

Beyond that, NimbleOptions — NimbleOptions v1.1.1 is a great option as it’s super easy to define basic validations, defaults, and it can ever create docs for you.

rvirding

rvirding

Creator of Erlang

A struct has one definite advantage over using plain maps is that you define what should be in that structure and what should not be there. It gives you much better control over the data. And as structs are implemented using maps it is just as efficient as using maps directly.

kccarter

kccarter

We’re able to use structs in some places where we have deterministic fields for a given response. An address is a good example.

But we find ourselves “having” to use normal maps with string keys more often than not because of the amount of JSON data we work with that isn’t easily defined ahead of time. (This is equally problematic in something like TypeScript.)

An example might be running a report that aggregates a bunch of values for every SKU in a system, with the results being returned in the format:

{
 "sku_001": { "title": "value", "color", "value", ... },
 "sku_002": { "title": "value", "price", "value", ... },
 "sku_003": { "width": "value", "height", "value", ... },
}

We also do a layout of layout in HEEX templates where we don’t know the “keys” ahead of time, and structs don’t allow fetching a field based on a key-string.

Where Next?

Popular in Questions Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
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
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New

Other popular topics Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
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
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement