Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement function/process polymorphism in Elixir

I want to 'extend' a built-in IO/File functions of Elixir in several (independent) ways.

I came up with the following code pattern:

defmodule FileExtension1 do
  # arguments are different for different extensions,
  # but I guess I could just stick with single map/list
  # argument for uniformity 
  def open!(filename, some_param \\ true) do
    filename
      |> File.open!
      |> attach(some_param)
  end

  def close(io) do
    io |> File.close
  end

  def attach(io, some_param \\ false) do
    spawn_link fn ->
      file_manager(io, some_param)
    end
  end

  def detach(io) do
    io |> send {:detach, self}
    receive do
      {:will_detach, ^io} -> :ok
    end
  end

  defp file_manager(io, some_param, state \\ <<>>) do
    if Process.alive?(io) do
      receive do
        {:detach, sender} ->
          sender |> send {:will_detach, self}
        {:custom_request, sender, reference, count} ->
          # {result, new_state} = do_job(...)
          sender |> send {:custom_reply, reference, result} 
          file_manager(io, some_param, new_state)
        {:io_request, sender, reference, {:some_pattern}} ->
          # {result, new_state} = do_job(...)
          sender |> send {:io_reply, reference, result}
          file_manager(io, some_param, new_state)
        x ->
          io |> proxy(x)
          file_manager(io, some_param, state)
      end
    end
  end

  defp proxy(io, data) do
    {request_type, original_from, original_reference, command} = data
    reference = make_ref
    io |> send {request_type, self, reference, command}
    receive do
      {response_type, ^reference, result} -> original_from |> send {response_type, original_reference, result}
    end
  end
end

Basically, it does the following:

  • Handles custom IO requests in accordance with custom protocol
  • Modifies processing of some of standard IO requests
  • Proxies everything else to underlying File

Now I can transparently stack these things on top of each other (i.e. attach first one to the File, then second one to the first one, etc.).

The problem is: now I have three modules which follows the same pattern I described above. I want to somehow remove code duplication.

What should I look into?

  • Just create another module with shared functions? Like IO contains shared functions for several devices, or Enum contains shared functions for several types. Could you give me a small example how that would work in my case?
  • Protocols? I don't quite understand how protocols could be used here, because what I'm trying to achieve doesn't fall into 'use some function for different (built-in) types' pattern.
  • Behaviours? Looks like I could benefit from creating some variation of GenServer, tuned to my needs. Then again, if I should use that, small example will help here.

Bonus question: How do I test that shared functionality using ExUnit?

like image 856
EugZol Avatar asked Nov 27 '25 06:11

EugZol


1 Answers

A very simple example of passing a function to another function:

#PassInFunction
defmodule PIF do
  def hello(name), do: IO.puts("Hello #{name}!")
  def execf(name, f), do: f.(name)
end

PIF.execf("Onorio",&PIF.hello/1)

In your particular case I'd use the file type (or extension as the case may be) to determine which function to pass in.

like image 143
Onorio Catenacci Avatar answered Nov 29 '25 09:11

Onorio Catenacci



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!