Elixir には演算子を再定義できる機能が備わっています。
defmodule Foo do def lhs + rhs do String.to_integer(Integer.to_string(lhs) <> Integer.to_string(rhs)) end end
普通の関数と同じように利用することができます。
Foo.+(123, 456)
モジュール名で修飾することなく、一般的な演算子として利用するには import
する必要があります。
import Foo, only: [+: 2]
しかしこれだけでは正しく機能しません。
123 + 456 #=> error: function +/2 imported from both Foo and Kernel, call is ambiguous
Kernel.+/2
も同時に定義されているために、どちらの演算子を利用したらよいか判別がつかないからです。
再定義する演算子の引数の型を限定すれば、型を元に判別ができるのではと思うのですが、
defmodule Foo do defstruct [:value] def new(value \\ 0) do %__MODULE__{value: value} end def lhs + rhs when is_struct(lhs, Foo) and is_struct(rhs, Foo) do new(Kernel.+(lhs.value, rhs.value)) end end
残念ながら結果は同じです。
import Foo, only: [+: 2] Foo.new(123) + Foo.new(456) #=> error: function +/2 imported from both Foo and Kernel, call is ambiguous
このようなばあい、衝突している関数の import
を解除することで解消することができます。
import Kernel, except: [+: 2] import Foo, only: [+: 2] Foo.new(123) + Foo.new(456) #=> %Foo{value: 579}
ただし、当然ですが、解除した関数を利用することができなくなります。
123 + 456 #=> ** (FunctionClauseError) no function clause matching in Foo.+/2
これを解消する方法として、再定義した演算子を適用しない「その他のケース」のばあいに元の関数を呼び出せばよさそうです。
defmodule Foo do defstruct [:value] def new(value \\ 0) do %__MODULE__{value: value} end def lhs + rhs when is_struct(lhs, Foo) and is_struct(rhs, Foo) do new(Kernel.+(lhs.value, rhs.value)) end def lhs + rhs when is_struct(lhs, Foo) and is_number(rhs) do new(Kernel.+(lhs.value, rhs)) end def lhs + rhs when is_number(lhs) and is_struct(rhs, Foo) do new(Kernel.+(lhs, rhs.value)) end # その他のケース def lhs + rhs do Kernel.+(lhs, rhs) end end
import Kernel, except: [+: 2] import Foo, only: [+: 2] Foo.new(123) + Foo.new(456) #=> %Foo{value: 579} Foo.new(123) + 456 #=> %Foo{value: 579} 123 + Foo.new(456) #=> %Foo{value: 579} 123 + 456 #=> 579
最後に。
import Kernel
と import Foo
はいつも組で利用するので、一つの記述で済ませられるようにマクロを定義すると便利です。
defmodule Foo do defstruct [:value] defmacro __using__(_) do quote do import Kernel, except: [+: 2] import Foo, only: [+: 2] end end # 略 end
これで use
するだけで利用できるようになりました。
use Foo Foo.new(123) + Foo.new(456) #=> %Foo{value: 579} 123 + 456 #=> 579