Pattern matching on strings vs lists. concerns over iolists

I was wondering if it is a bad idea to have one function with multiple heads that behave differently for lists as strings.

The example is as follows. I am trying to create a succinct API for creating HTTP responses. For each status there is a function that takes body and headers. The function heads that exist so far are as follows.

def ok(%{body: body, headers: headers}) do
def ok(body, headers \\ []) when is_binary(body) do

Usage is as follows

# create simple text responses
Response.ok("Hello, World!", [{"my-header", "data"}])

# use a helper that returns a map of body and headers,
in this case the json_content helper ecodes the data and also sets the header required.
Response.ok(json_content(%{name: "Nelli"}))

What I would like to do is add another function head that is used in the cases when a body is not required. for example redirections do not need any response body. In such a case the first argument should be treated as the headers. This can be done by adding the following clause

def ok(body, headers \\ []) when is_list(body) do
  headers = body ++ headers
  body = ""
  ok(body, headers)
end

My concern is adding this clause prevents me from using bodies that are iolists. An iolist is a list and yet it can be treated as a binary.

In the general case is it always worth considering that binaries can be represented as lists and so consider that case?

I’ve tagged this as an erlang discussion because the same concern would be true in erlang as in Elixir.

1 Like

If you want to support iolists just check if, say, body is_list or is_binary, then just be sure to convert it via iolist_to_binary or whatever method you want if you want to use it as a binary (or just pass it around as-is). :slight_smile:

If body is optional though then I think you’d want:

def ok(%{body: body, headers: headers}) do
  ...
end
def ok(headers_or_body) do
  case headers_or_body do
    # Do whatever you need to do to distinguish if it is a body or header here
  end
end
def ok(body, headers) when is_binary(body) or is_list(body) do
  ...
end

I think I might have not explained my goals very well. The line with
# Do whatever you need to do to distinguish if it is a body or header here
is the bit I am interested in.

What I was proposing was using the fact an argument was a binary to decide it must be a body and if it is a list then it must be headers.

Ah sorry, it depends on how you distinguish your headers from your body, like if your headers are always 2-tuple-lists and body is always an iolist (binary, charlist, iolist), then you could put this there:

case headers_or_body do
  [] -> ok("", []) ok("", []) # Huh, its empty regardless
  [(_, _) | _] = headers -> ok("", headers) # Process headers, empty body
  body -> ok(body, []) # Process body, empty headers
end

That’s definitely what I was looking for. The headers should always be tuples (and tuples cannot be part of an iolist).

Precisely, you just have to match out enough to prove what it is one way or another. :slight_smile:

1 Like

Used this technique. I think it makes quite a nice API for creating requests. Don’t even need to handle the empty list as a special case because it can just be considered a body that consists of an empty iolist

1 Like