Hello,
I keep using a paradigme using a homemade function to make my life easier when piping.
I wonder if there is a better way to achieve the same result, maybe something already part of Elixir language.
Instead of writing a tailored maybe_dothat() function, I prefer a generic maybe_do() wrap function around a do_that() function.
Here is the may_be/1 wrapper.
def maybe_do(pipe_value, condition, function)
when is_boolean(condition) and is_function(function) do
if condition do
function.(pipe_value)
else
pipe_value
end
end
Let’s say I want to always call do_this/1 on a socket and do_that/1 but only if my_bool is true, else socket rest unchanged.
I write it this way:
There is not such construct in Elixir that I know of, there is tap/2 to perform a side effect but the original value is the one being pipped, there is also then/2 but here is the function return in the second argument that is being pipped instead.
So I guess your solution is fine if you find yourself repeating that pattern a lot.
As the other poster said, tap and then cover a lot of needs, and also yeah if it’s more convenient for you to use your own function then please don’t feel bad about it! It’s very normal.
People use their own functions a lot when you need to go through a series of validations, for example. It’s expected to do it like that in many teams even.
value
|> case do
x when my_condition -> f.(x)
x -> x
Alternatively you can write a macro that supports holes, lets say you call it p:
value
|> (p if my_condition, do: f.(_) else: _)
Doesn’t have to be exactly like this ofc, you can define it to be whatever you like the most. Just giving an example here. I actually have a bunch of these in my helpers libs. Edit: or, even better, you can also extend the pipe itself with this macro.
If you’re really talking about a socket and only talking about a couple of functions, I would use a live hook like on_mount or attach_hook along with an if.
That’s way better, thank you!
I still fear that my approach maybe a code smell.
I mean, I must not be the first one to need to chain (pipe) functions based on some conditions that are not directly part of the piped data.
This is typically true with GUI events. You want to reflect the actions in the socket (the piped data), but the event itself is not part of that socket.
An option would be to add the events to the piped data, piping {event,socket} rather than the plain socket.
Anyway, thank you again.
It’s very hard to tell without seeing your exact code, but if you’re feeling it’s a code smell it’s possible that you are trying to force a pipeline when it is not needed. Once you start threading tuples like {event, socket} through a pipeline where only one function cares about event, you’re hiding details. It makes it harder in the future to read the pipeline at a glance without looking at the implementation of each function. Of course if it’s a pattern that appears everywhere it’s not such a big deal, but generally pipelines are best when they operate on a single immutable data structure. YMMV, of course.
defmacro value ~> name do
quote do
value = unquote(value)
unquote(name) = if value, do: value, else: unquote(name)
end
end
defmacro name <~ value do
quote do
value = unquote(value)
unquote(name) = if value, do: value, else: unquote(name)
end
end
I cringe when I see stuff like if list ... when list should never be nil anyway. If a list might be nil (f.ex. if you can’t control it when a framework is calling your callback or such) then it’s best if you do call_function(List.wrap(list_or_nil), ...).
And I believe this is not just some random personal preference. I prefer code to communicate its intent clearly. Just doing if this_could_be_anything ... with no regard of its type is to me a code smell because it’s not clear what the intent is. Might as well write PHP or JS at that point. We should use all of Elixir’s strengths.
Furthermore, I’d prefer to strictly assert when is_list(parameter) in my function signatures as well (or use the [first | rest] pattern-matching idiom, optionally together with [] if an empty list is an acceptable parameter value).