raytracer

raytracer

Post conditions in Elixir

What do you think about pre and post condition checks in Elixir?

I’ve just started learning Elixir and can see how guards are similar to pre-condition checks. Is there anything that could be used for post-condition checks?

Last night I realised the pipe operator could be used (abused?) to help implement post-condition checks and wrote this marcro:

defmacro ensure input, pattern1, pattern2 do
  quote do
    case unquote(input) do
      unquote(pattern1) -> unquote(input)
      unquote(pattern2) -> unquote(input)
      _ -> raise PostConditionError
    end
  end
end

It would be used like:

def is_food(input) do
  case input do
    "Cheese" -> true
    "Beer" -> {:error, "Item is not food"}
    "Phone" -> false
  end

  |> ensure(true, {:error, _reason})
end

A is_food("Phone") call would result in a PostConditionError being raised.

Thoughts?

Most Liked

LostKobrakai

LostKobrakai

You might be able to create a macro def/3, which allows for a syntax like

def is_foot(input) when is_binary(food) do
  case input do
    "Cheese" -> true
    "Beer" -> {:error, "Item is not food"}
    "Phone" -> false
  end
after
  result == true or match?({:error, _reason}, result)
end
christhekeele

christhekeele

Worth noting this is already valid syntax with very different implications: it creates an implicit try block around the function and ensures your code runs after it whether or not it raised; and does not allow accessing the result of the function (since there may be none since it could have raised).

Playing around with a couple of observations:

  • What you propose is still actually def/2: the macro receives a call and a keyword list of blocks, so in our example the macro would receive:

    defmacro def(head, [do: body, after: postcheck])
    
  • Semantically what we want is actually closer to the else block in a try that matches on the result iff no error was raised, allowing for post-checks and result transformation:

    def is_foot(input) when is_binary(food) do
      case input do
        "Cheese" -> true
        "Beer" -> {:error, "Item is not food"}
        "Phone" -> false
      end
    else
      true -> true
      {:error, reason} -> {:error, {:not_foot, reason}}
      _ -> raise PostConditionError # possibly implicit, though traditionally not
    end
    
  • You can’t make up your own block keywords, the parser only allows usage of the existing secondary-block terms after a do: catch, rescue, after and else

  • The existing implementation of def with an implicit try only uses catch, rescue, and after

Conclusion: We could make a custom def/2 macro that employs an else block and transforms the provided implementation into something traditionally allowed in Kernel.def/2, and else is already the only syntactically valid unused block keyword in Kernel.def/2.

Cons: def(head, do: body, else: postcheck) does not indicate intent nearly as well as def(head, do: body, after: postcheck). But such a macro would definitely be possible!

Alternatively:

  • Since block arguments must come last, the only form of def/3 the parser would recognize would be:

    defmacro def(head, postcheck, [do: body])
    
  • This isn’t that great though:

    def is_foot(input) when is_binary(food), true or {:error, _reason} do
      case input do
        "Cheese" -> true
        "Beer" -> {:error, "Item is not food"}
        "Phone" -> false
      end
    end
    
  • Interestingly we could employ a fictional ensure/1 function that the parser doesn’t choke on, to make the intent more legible:

    def is_foot(input) when is_binary(food), ensure true or {:error, _reason} do
      case input do
        "Cheese" -> true
        "Beer" -> {:error, "Item is not food"}
        "Phone" -> false
      end
    end
    

Cons: certain complicated expressions could make the parser decide it no longer likes this construct, it’s arguably even hackier though it reads well..

Where Next?

Popular in Discussions Top

ricklove
I was just introduced to Elixir and Phoenix. I was told about the 2 million websocket test that was done 2 years ago. From my research, t...
New
AlexMcConnell
The reason that Rails is as popular as it is is because it’s very easy for relatively inexperienced developers to get a lot of work done....
588 20142 166
New
Crowdhailer
I’ve been hearing much about the new formatter and it’s something I have been keen to try. I find examples buy far the most illuminating...
248 19814 150
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
marciol
Please, let me know if this kind of discussion already took place in another topic . Hi all, how do you consider if is better to build ...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
crispinb
On reading dhh’s latest The One Person Framework it strikes me that Phoenix with LiveView is already pretty much this. However, never hav...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement