Oliver

Oliver

Allowing an ignored member in list construction?

One common problem we face in constructing lists is that there is (AFAIK) no support for conditionally inserting members into list declarations.

You can’t write:

allow_even? = false
[
1,
if allow_even? do 2 end,
3
]

because the result would be [1, nil, 3], though syntactically a lot of code would be easier and more readable for us if you could do that.

What if returning a very specific value, like :ignore_list_member would lead to a list being constructed like this:

[1, :ignore_list_member, 3] ==> [1, 3]

Functions could return specifically this value if there’s nothing to insert.

def foo(x), do: if rem(x, 2) != 0 do x else :ignore_list_member end
[
foo(1),
foo(2),
foo(3)
]
===> [1, 3]

Right now we typically do something like piping a whole list like this:

allow_even? = false
[
1,
if allow_even? do 2 end,
3
]
|> Enum.reject(&is_nil/1)

which is fine, but I wonder if this could be solved more conveniently and generic without the need to walk the list a second time after constructing it - just once when it’s “constructed” with the declarative syntax.

If it’s not possible with small effort, I still thank you for reading this. :slightly_smiling_face:

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Yes, but this happens rather a lot and is very well optimized. Eg an Enum.map is a 2x walk because you have to build up the result backwards and then reverse it (or use a body recursive stack, but that’s still functionally a 2x walk since you have to pop the stack).

If you have literally just one optional item btw you can:

if optional_thing do
  [a, optional_thing, b]
else
  [a, b]
end

If you are trying to construct a list that has many different items that may optional interspersed amongst items that are not, I don’t see how the compiler is supposed to do constant optimization. Definitionally you aren’t constructing a constant, you’re constructing a value who’s length varies by a bunch of different values.

Overall I think this discussion of costs is probably misplaced. Are we talking about lists that are literally a handful of items long? If so we’re in hyper optimization land and the code that performs best will rely heavily on exactly what you’re trying to do.

al2o3cr

al2o3cr

At a minimum, I think this would need some alternative syntax because this is already valid Elixir:

[
  :foo,
  if x.something? do :bar end,
  :baz
]

This is distinctly different from Dart, where the regular if is a statement with no value so putting it inside an expression would otherwise be meaningless.

That syntax would also cause headaches for referential transparency, since this should be “the same” code as the above:

extra_var = if x.something? do :bar end

[
  :foo,
  extra_var,
  :baz
]

IMO the better approach is to use a domain-appropriate “empty” element and clean it up if needed. Cleanup may not even be strictly necessary - for instance, if you’re producing a big iolist value with optional parts then [] is a perfectly fine “skip this and go to the next element” marker.

dimitarvp

dimitarvp

On the very rare occasion I’ve done this I just used a list of lists (where the conditional adding of a thing added a non-empty list, and if the condition was false then it simply added an empty list) and then just do List.flatten at the end.

I understand the appeal of having such a language construct but it’s IMO too niche.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

If the value is conditionally there then there is no avoiding paying for concatenation. Lists are linked lists, they’re built from back to front. If part of the back is different depending on a runtime value then you have to pay a runtime cost to deal with it.

gregvaughn

gregvaughn

You can use the filter of a for comprehension

iex(9)> require Integer
Integer
iex(10)> allow_even? = false
false
iex(11)> for x <- 1..3, !Integer.is_even(x) || allow_even?, do: x
[1, 3]
iex(12)> allow_even? = true
true
iex(13)> for x <- 1..3, !Integer.is_even(x) || allow_even?, do: x
[1, 2, 3]

or you could use Enum.flat_map but then you’re creating intermediate lists

iex(14)> Enum.flat_map(1..3, fn
...(14)>   x when not Integer.is_even(x) or allow_even? -> [x]
...(14)>   _ -> []
...(14)> end)
[1, 2, 3]
iex(15)> allow_even? = false
false
iex(16)> Enum.flat_map(1..3, fn
...(16)>   x when not Integer.is_even(x) or allow_even? -> [x]
...(16)>   _ -> []
...(16)> end)
[1, 3]

Where Next?

Popular in Proposals: Ideas Top

martosaur
TL;DR Logger.Translator acts as a global filter and swallows structure of some OTP reports, which some logger handlers could benefit from...
New
manhvu
In a large repo, working with module need to add alias too much is quite annoyed and not good for organizing code. I think better add su...
New
kip
Sumary of proposal DateTime.from_iso8601/3 adjusted to: set all numeric fields to the values as parsed (not shifted to UTC), preservi...
New
tristan
This is a cross post from the Erlang Forums. ETS table `select_take` - Proposals: Ideas - Erlang Programming Language Forum - Erlang Foru...
New
cevado
Being able to build nested relations in a schemaless changeset would be helpful to deal with complex forms on Phoenix without the need to...
New
markevans
Hi! I’m excited about everything that’s going on re. gradual typing and am really pleased to see that Jose and the team seem to be think...
New
sodapopcan
So after complaining about this for the third or fourth time on this forum, I figured I should make a proposal. TL;DR with can be hard t...
New
RobinBoers
Hi. We had a few issues in our project regarding mix tasks, where we expected the items in the @requirements module tag to have been exec...
New
7rans
I implemented Access behavior for a struct today. Pseudo-code… defmodule MyStruct do defstruct data: %{} @behaviour Access # ... ...
New
dkuku
This is a proposal to make the map key mismatch errors a bit better: Every time I have a typo It’s very challenging for me even when I u...
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

We're in Beta

About us Mission Statement