Add a clause to a function by wrapping the original clause

I have a situation like:

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.

You might look at protocols for this.

Macros are basically code generators, what else would you use if you had the option?

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 :slight_smile:

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.

That’s an error-prone format, I’d avoid it.