Inside a pipe chain, how to execute one pipe line / exclude it based on say is_list(variable)
? i.e. execute pipe line 5 only if variable
is a list. Is that possible?
Do you have an example of the code? I guess that with the code is a little bit better to understand what you mean.
I have discovered a possible solution… Here is the working pipeline with conditionals:
page =
Policy
|> join(:left, [p], c in Client, p.client_id == c.id)
|> where([p, c], c.user_id == ^conn.assigns.current_user.id)
# filter by expires
if is_nil(params["policy"]["expires"]) do
expires_params = "01-01-1970" |> Timex.parse!("%d-%m-%Y", :strftime) |> Ecto.DateTime.cast!
page = page |> where([p, c], p.expires >= ^expires_params)
else
page = page |> where([p, c], p.expires >= ^params["policy"]["expires"])
end
# filter by client_id
if is_list(params["policy"]["client_id"]) do
page = page |> where([p, c], p.client_id in ^params["policy"]["client_id"])
end
# final processing
page = page |> preload([p, c], [client: c]) |> Repo.paginate(params)
Works, but maybe there is a better / nicer way to code it
Actually, you should never assign variables inside conditionals like if
, case
and so on.
Instead of writing:
if is_list(params["policy"]["client_id"]) do
page = page |> where([p, c], p.client_id in ^params["policy"]["client_id"])
end
You should write:
page =
if is_list(params["policy"]["client_id"]) do
page |> where([p, c], p.client_id in ^params["policy"]["client_id"])
else
page
end
Other than that I think it’s a good solution, but you could also take a look at Ecto’s 2.1 new dynamic expressions: https://github.com/elixir-ecto/ecto/blob/v2.1/CHANGELOG.md
It might be possible to extract a function where you can pattern match and compose the query. Here’s what it could look like for the expires policy (actual code may depend on where your functions and usage are located):
# Policy module
def with_expiry(page, nil) do
expires_params = "01-01-1970" |> Timex.parse!("%d-%m-%Y", :strftime) |> Ecto.DateTime.cast!
page |> where([p, c], p.expires >= ^expires_params)
end
def with_expiry(page, expires_params) do
page |> where([p, c], p.expires >= ^expires_params)
end
#...
page =
Policy
|> join(:left, [p], c in Client, p.client_id == c.id)
|> where([p, c], c.user_id == ^conn.assigns.current_user.id)
|> Policy.with_expiry(params["policy"]["expires"])
# ...
@rossta, @vidalraphael, @kelvinst
Thank you very much… I have learned new things here today… I am happy that I have found my way back to this forum after some bad bi yearly banning experiences at holy stackoverflow (latest only because I asked a duplicate question)…
Yep! @rossta thank you! That’s the way to go!
A post was split to a new topic: StackOverflow culture
Hi there!
If I have to pass in a limit clause based on a function argument. Is function pattern matching the best way to do this? In the solution above, there is 2 functions that returns where clause. Is it possible to do
def with_limit(page, nil) do
page
end
def with_limit(page, limit_size) do
page |> limit: size
end
query |> with_limit limit_size
i was going to do a
if param do
page = query |> limit: size
end
but someone mentioned earlier that i shouldnt be assigning in if/else clause.
I’m often lazy and just put a case between things… >.>
value
|> something(42)
|> something_else()
|> case do
page where limit_size == nil -> page
page -> page |> limit(size)
end
|> some_more_things()
...
Also you really need to use code tags instead of quotes for bodies of code, you do it like:
```elixir
Code goes here like 2+2
```
Makes:
Code goes here like 2+2
heheh thanks! you saved me again man!
will try it out!
IO.puts "Thanks!"
Hehe, do note the ‘proper’ way to do it is different function heads and pipe to the function, but when lazy… ^.^;
Woah. Did not realize you can do this. So much more readable than anon functions. Time to go clean up some code
Bringing the discussion up, as it’s been a while I felt like I was missing something, and I may finally have nailed it.
Context
As far as I understand, the philosophy of a functional programming language is about data transformation.
Hence the pipe operator is amazing in Elixir as it is exactly what it does:
initial_data
|> first_transformation()
|> second_transformation()
|> third_transformation_with_params(my_param)
|> another_one()
# final_data
But there are cases where the transformation should be applied conditionally, which is what is discussed here.
Of course, one can write another sub-method like:
def maybe_apply_second_transformation(data, true), do: second_transformation(data)
def maybe_apply_second_transformation(data, false), do: data
initial_data
|> first_transformation()
|> maybe_apply_second_transformation(should_apply)
|> third_transformation_with_params(my_param)
|> another_one()
But that’s way too much extra code for a simple condition.
So I like @OvermindDL1 quite a lot:
initial_data
|> first_transformation()
|> case do
data when should_apply -> second_transformation(data)
data -> data
end
But this syntax is quite heavy for a simple if
, the data -> data
line doesn’t bring any valuable information, not perfect IMHO.
Question
Shouldn’t this be valid:
initial_data
|> first_transformation()
|> (if should_apply, do: second_transformation())
|> third_transformation_with_params(my_param)
|> another_one()
I know it doesn’t now, I guess, the data
is given as the first parameter of if
, instead of should_apply
.
But when writing a if
in a pipeline, it is more useful to get the data
as the first parameter of the do
block than of the if
condition.
# Not useful
boolean
|> if do: data
# More useful Useful
data
|> if boolean, do: fn d -> d end
# data
If the if
operator shouldn’t be used for backward compatibility reasons, shouldn’t we imagine another one, like if_map
? Or even a syntax like:
initial_data
|> first_transformation()
|>? should_apply, do: second_transformation()
|> third_transformation_with_params(my_param)
|> another_one()
Thanks for reading and feedback
I feel that statement is the trap here. Instead of naming it maybe_apply_second_transformation
I’d suggest something like prepare_for_third_transformation
(maybe inline second_transformation(data)
) and then the code is totally fine. Most importantly that code will age the best. Say at some later point you also need to do some different transformation for the false
case as well. Just replace data
with whatever that other case needs to be applied to.
All your proposed short inline forms will likely no longer be short and sweet once you need to deal with 2 cases and once you’re no longer at 1 or 2 cases you’ll need to switch to the function (or case) version anyways.
As a possible alternative to writing a full fledged maybe_*
function, you can also consider to use a general purpose maybe_if/3
function. I’ve done that in the past when I needed to chain multiple optional steps.
initial_data
|> first_transformation()
|> maybe_if(should_apply_step2, &second_transformation/1)
|> maybe_if(should_apply_step3, &third_transformation_with_params(&1, my_param))
|> another_one()
where maybe_if
is defined like this:
def maybe_if(data, true, action)
when is_function(action, 1),
do: apply(action, data)
def maybe_if(data, false, _action), do: data
Thanks for feedback that’s very interesting.
@wolf4earth I like your solution very much, I shall go for it.
All your proposed short inline forms will likely no longer be short and sweet once you need to deal with 2 cases and once you’re no longer at 1 or 2 cases you’ll need to switch to the function (or case) version anyways.
@LostKobrakai I don’t really agree with your point.
data
|> first_transformation()
|> if should_apply,
do: second_transformation(),
else: alternate_transformation()
|> third_transformation()
reads much clearer IMHO than
data
|> first_transformation()
|> case do
data when should_apply -> second_transformation(data)
data -> alternate_transformation(data)
end
|> third_transformation()
My thinking made me understand that in order to get my ideal version to work, one should write the if/4
macro (from if source code
). Apologies I suck at macros…
defp build_if(nil, condition, do: do_clauses, else: else_clauses) do
build_if(condition, do: do_clauses, else: else_clauses)
end
defp build_if(entry, condition, do: do_clauses, else: else_clauses) do
quote do
if(condition, entry |> unquote(do_clauses), entry |> unquote(else_clauses))
end
end
Though my code may not be correct, couldn’t it work?
The cool thing is that from my understanding, it could be backward compatible, as it is easy to detect that entry
is nil
, so the very same operator could be used (though it may be confusing at first).
I’d say so as well, but imo non of that needs to be handled as part of the pipeline. The pipeline should only be concerned with the steps needing to happen, but not with what happens as part of those steps – and doing nothing is a valid option for the latter.
For your examples second_transformation
and alternate_transformation
are both versions of some_transformation_needed_before_the_third_transformation
, which should be what’s piped into.
I guess I’m not yet comfortable with the expressiveness of idiomatic Elixir code.
Take this SO question.
It seems like the solution in Elixir is to always create more functions, whereas I’m a one-liner fan, as context always varies, and on-the-fly definitions seem more appropriate to me than more code lines.
But this would be a long topic to discuss!