Last active
December 7, 2017 17:13
-
-
Save danielgracia/094a24b39b75b58c4bc69fa4128b8dfa to your computer and use it in GitHub Desktop.
Coalesce macro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Coalesce do | |
@moduledoc """ | |
Provides a "coalesce" macro. | |
This macro is partly inspired by the COALESCE function in the SQL-92 standard. | |
The original COALESCE function is a function that returns the first non-null | |
computed expression passed as one of its argument, or NULL itself if no such | |
expression is found. | |
The "coalesce" macro, by comparison, accepts a compile-time list of expressions to evaluate, | |
and returns the first one with a truthy value - which means that the macro will skip both | |
`nil` and `false`. | |
Ultimately, this is just a cleaner way to write a sequence of `||` operations. Where you would to this: | |
var = do_operation(:a) || do_operation(:b) || fallback(:a) || fallback(:b) || [...] | |
Or this: | |
var = do_operation(:a) | |
var = var || do_operation(:b) | |
var = var || fallback(:a) | |
[...] | |
You can instead do this: | |
import Coalesce | |
var = coalesce [do_operation(:a), do_operation(:b), fallback(:a), ...] | |
The coalesce macro is short-circuiting, and won't evaluate any expressions beyond the first truthy one. | |
""" | |
@doc """ | |
Selects and returns the first truthy expression in a list. | |
The argument must be a compile-time list. In other words, it needs to be a list literal, although | |
the values of the list themselves do not need to be literal values. | |
If you need to return the first truthy value from a list created at run time, | |
you can just use `Enum.find(& &1)`. | |
## Examples | |
iex> Coalesce.coalesce([false == true, 3, IO.puts("lol")]) | |
3 | |
""" | |
defmacro coalesce([_|_] = list) do | |
do_coalesce(list) | |
end | |
defmacro coalesce(any) do | |
string = Macro.to_string(any) | |
raise "coalesce/1 must receive a compile-time list of expressions, got: #{string}" | |
end | |
defp do_coalesce([expr]) do | |
quote do: unquote(expr) | |
end | |
defp do_coalesce([expr | tail]) do | |
quote do | |
value = unquote(expr) | |
if value do | |
value | |
else | |
unquote(do_coalesce(tail)) | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule CoalesceTest do | |
use ExUnit.Case | |
doctest Coalesce | |
import Coalesce | |
test "uses a long coalesce" do | |
assert coalesce([ | |
1 + 1 == 3, | |
Map.get(%{a: :b}, :c), | |
[ | |
1 + 1, | |
2 + 2, | |
3 + 3, | |
Map.get(%{a: :d}, :a) | |
] | |
]) == [2, 4, 6, :d] | |
end | |
test "short-circuiting" do | |
assert coalesce([ | |
true, | |
raise("coalesce ended") | |
]) == true | |
end | |
test "still runs through" do | |
assert_raise RuntimeError, "coalesce ended", fn -> | |
coalesce([ | |
false, | |
raise("coalesce ended") | |
]) | |
end | |
end | |
test "as captured function" do | |
list = [ | |
[1, false, false], | |
[false, 2, false], | |
[false, false, 3], | |
[false, false, false] | |
] | |
pass_along = fn fun -> | |
Enum.map(list, &apply(fun, &1)) | |
end | |
assert pass_along.(&coalesce([&1, &2, &3])) == [1, 2, 3, false] | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment