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

PragTob
Hello everyone, I know we had quite some threads (read through lots of them) about background job processing but it remains a hotly deba...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
thojanssens1
It would be nice to be able to define a redirect from one route to another from the router.ex file. E.g.: redirect "/", UserController, ...
New
arcanemachine
https://nitter.net/josevalim/status/1744395345872683471 https://twitter.com/josevalim/status/1744395345872683471
New
AstonJ
Are there any Elixir or Erlang libraries that help with this? I’ve been thinking how streaming services like twitch have exploded recentl...
New
sashaafm
Piggy backing a bit on @dvcrn topic BEAM optimization for functions with static return type?, I’ve been trying to understand in a deeper ...
New
tmbb
This is a post to discuss the new Phoenix LiveView functionality. From Chris’s talk, it appears that they generate all HTML on the serve...
342 18146 126
New
praveenperera
How We Replaced React with Phoenix By: Thought Bot
New
shishini
I think this twitter post and youtube video didn’t get as much attention as I hoped I am still new to Elixir, so can’t really judge ...
New
CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30877 112
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement