Coalesce (a.k.a. nvl) function

1,124 views
Skip to first unread message

Gleb Arshinov

unread,
Jan 20, 2016, 4:46:58 PM1/20/16
to elixir-l...@googlegroups.com
Wanted to propose a function like SQL's coalesce (a.k.a. nvl):

http://www.postgresql.org/docs/9.2/static/functions-conditional.html#FUNCTIONS-COALESCE-NVL-IFNULL:
"The COALESCE function returns the first of its arguments that is not null."

It would help simplify a typical Ecto.Repo.insert_or_update invocation.

E.g. from Ecto.Repo help:

case MyRepo.get(Post, id) do
nil -> %Post{id: id}
post -> post
end
|> Post.changeset(changes)
|> MyRepo.insert_or_update

More realistically you have a good-size pipeline before the case
setting up the query before running it. So now it looks like this:

from(p in Post)
|> MyRepo.one
|> case do
nil -> %Post{id: id}
post -> post
end
|> Post.changeset(changes)
|> MyRepo.insert_or_update

With coalesce this would become:

from(p in Post)
|> MyRepo.one
|> coalesce(%Post{id: id})
|> Post.changeset(changes)
|> MyRepo.insert_or_update

Thoughts?

Best regards,

Gleb

José Valim

unread,
Jan 20, 2016, 4:57:17 PM1/20/16
to elixir-l...@googlegroups.com
That's the or operator: ||

(MyRepo.get(Post, id) || %Post{}) |> ...

or:

|> Kernel.||(%Post{})



José Valim
Skype: jv.ptec
Founder and Director of R&D


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CACZNi58qT5NUsKaHGbS1fBSjm4SSTrVoC%2BSJhzEqx0Y%2BWpQd1g%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Gleb Arshinov

unread,
Jan 20, 2016, 5:14:29 PM1/20/16
to elixir-l...@googlegroups.com
That's what I thought of first and used it in our code. Liked the
improvement but saw two problems with Kernel.||
* The syntax to invoke it complicated. I asked for help on Slack to
figure out it out:
https://elixir-lang.slack.com/archives/general/p1453320936023973

The syntax is also complicated enough to hide intent and just hard to read:

from(p in Post)
|> MyRepo.one
|> Kernel.||(%Post{id: id})
|> Post.changeset(changes)
|> MyRepo.insert_or_update

* the semantics is not right if you get false as argument. Not a big
deal in most cases, but still not quite right.

Best regards,

Gleb
> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JrMDjCgZaPmfodUZLSGBKHhnOYUd5cvU9qiCFKPjcDLw%40mail.gmail.com.

Peter Hamilton

unread,
Jan 20, 2016, 5:24:28 PM1/20/16
to elixir-l...@googlegroups.com
Is there potential for a CAS operation?

|> compare_and_swap(nil, %Post{id: id})

That would solve the falsey values issue.

Using CAS as the name might cause confusion since while it's technically atomic it's not really solving concurrency issues.

Gleb Arshinov

unread,
Jan 20, 2016, 5:38:18 PM1/20/16
to elixir-l...@googlegroups.com
More info on other languages:

https://en.wikipedia.org/wiki/Null_coalescing_operator

IMO, additional constraint in Elixir is that this needs to be pipe-friendly.

Gleb

jisaa...@gmail.com

unread,
Jan 21, 2016, 1:17:28 PM1/21/16
to elixir-lang-core
I am against `coalesce`. Elixir does not have any NULL and it is better for it. `nil` is just an atom. It is already used more than it should. For example `MyReop.get` ought to return `:notfound` or something more meaningful.

Anyway I think the pattern is common enough that a function for the general case might belong in the stdlib. like CAS or default_if

def default_if(x, x, _), do: x
def default_if(_, _, dft), do: dft

MyRepo.get(Post, id) |> default_if(:nil, %Post{id: id})

Gleb Arshinov

unread,
Jan 21, 2016, 7:28:47 PM1/21/16
to elixir-l...@googlegroups.com
I agree that a more general function like that is a good idea. nils
are less special in Elixir than in SQL, so an extra parameter seems
sensible.

Gleb
> https://groups.google.com/d/msgid/elixir-lang-core/0b4ff4dd-b52e-43d0-a71e-a3c0cb602eda%40googlegroups.com.

Booker Bense

unread,
Jan 27, 2016, 9:50:53 AM1/27/16
to elixir-lang-core
I have written a library that provides a general purpose protocol for these kinds of data
conversions. 


It's likely overkill in this case, but it does have the advantage of being pipe friendly and 
in theory you can write any data transformation as a pipe of 

PhStTransform.transform(data, potion)
calls. In this case you would use this potion. 

nil_post_potion = %{ Atom =>
  fn(atom, depth) -> if(List.first(depth) == Atom && str == nil , do: %Post{id: id} end}
It's kind of massive overkill for this small case, but I think the idea has a lot of potential. 

- Booker C. Bense

Booker Bense

unread,
Jan 27, 2016, 9:52:29 AM1/27/16
to elixir-lang-core
Doh! typo 


On Wednesday, January 27, 2016 at 6:50:53 AM UTC-8, Booker Bense wrote:
I have written a library that provides a general purpose protocol for these kinds of data
conversions. 


It's likely overkill in this case, but it does have the advantage of being pipe friendly and 
in theory you can write any data transformation as a pipe of 

PhStTransform.transform(data, potion)
calls. In this case you would use this potion. 

nil_post_potion = %{ Atom =>
  fn(atom, depth) -> if(List.first(depth) == Atom && atom == nil , do: %Post{id: id} end}
Reply all
Reply to author
Forward
0 new messages