defmodule X
def f(x) when is_integer(x) do
# ... something
end
def g(x) when is_integer(x) do
# ... something
end
end
Now, I want to also provide clauses that apply when is_binary(x) .
I have a wrapper function like
def wrapper(f) when is_function(f) do
fn b when is_binary(b) -> b
|> # convert to integer representation (in a custom way, not string.parse_int)
|> f.() # i need the .() right?
|> Enum.map(&to_string/1)
|> Enum.join("")
end
I want to express this kind of concept
f(x) when is_binary(x) = wrapper(f)
g(x) when is_binary(x) = wrapper(g)
# ideally would not need to repeat the is_binary(x) clause
Not sure how to do this in elixir in the simplest way. Here’s what I have right now:
def f(x)
when is_binary(x) do
string_version(&f/1).(x)
end
def g(x)
when is_binary(x) do
string_version(&f/1).(x)
end
Feels rather clunky/repetitive though. How do I get an extra clause by transforming the initial clause idiomatically? One thought I had is with macros:
add_clause wrapper, f
adding a clause with a macro seems like … oof. and I’m not sure how I would copy the guards from the result of the wrapper to the output of the macro.
If I’m understanding your question correctly, and I’m not sure that I am, one option would be to break out “wrapper” logic into private functions and call them in other clauses of your functions.
def f(x) when is_integer(x) do
# ... something
end
def f(x) when is_binary(x) do
x
|> convert_to_integer()
|> f()
|> convert_to_binary()
end
def g(x) when is_integer(x) do
# ... something
end
def g(x) when is_binary(x) do
x
|> convert_to_integer()
|> g()
|> convert_to_binary()
end
defp convert_to_integer(binary) do
# ...
end
defp convert_to_binary(integer) do
# ...
end
To me it does seem a little strange, though, to use the same function for integer -> integer and binary -> binary operations.
If it helps, consider the int -> int and string -> int cases and ignore transforming back to a string. The same question still applies.
In some langauges like Scala there is a concept that you can just get natural transformations for free, if they exist.
This is called implicits there, and it’s called other things in other languages
I am in a situation where i want the same function to apply no matter what form the concept represented by its argument is expressed in. It’s admittedly not that much of a change and one could imagine just dropping the transforms wherever I need them in the calling code. It is a nice convenience that’s more “ergonomic” for the data type i’m working with.
As for why int -> int and string -> string it’s common that if I get the type in one format I want it back in the same format.
imagine units - if someone speaks in meters you do the calculation and return to them a value in meters. Someone else can use yards and they would get yards, that kind of thing . So there is a real world analogy to what i’m doing.
Yeah, I thought about it and realized that macros are indeed the right answer - they are code generation tools and even things like decorators in other languages are also macros, just a special kind of limited macro with special syntax.
So I guess some kind of macro it is
add_clause to_f, from_f for
add_clause transform for
def somefunc(x) do x end
or similar i suppose is what I’m looking for? Not sure how i’ll get the stacking property to work on the definition. Maybe this is safer:
add_clause somefunc, to_f, from_f,
add_clause somefunc, transform
def somefunc(x) do x end
and i just use convention to keep them close by so i’m not lost where the extra clauses are coming from.