0

In swift for example, you can overload the equality operator as follows:

Overloading equivalence (==) operator for custom class in Swift

class CustomClass: Equatable {
    var id = "my id"
}

func ==(left: CustomClass, right: CustomClass) -> Bool {
    return left.id == right.id
}

Is there a way to do this in elixir?

What I want to achieve is this:

defmodule IP do
  @enforce_keys [:host, :port]
  defstruct [:host, :port, failures: 0, timeouts: 0]

  @type t :: %IP{host: String.t(), port: integer, failures: integer, timeouts: integer}

  # I want the == operator compare host and port but not failures and timeouts..
  def compare(l, r) do
    l.host == r.host && l.port == r.port
  end
end
Shiyason
  • 759
  • 7
  • 16
  • No. The general way to do this is to create a `compare/2` function within the module that defines your type. – Justin Wood May 31 '19 at 20:18
  • @JustinWood Thank you. I implemented `compare/2` in my struct but calling `s1 == s2` still return false. Please see my updated question. – Shiyason May 31 '19 at 20:25
  • Implementing the function doesn't overload the operator. It just creates a function you can use to compare two structs. So instead of checking `s1 == s2`, you would check `IP.compare(s1, s2)`. But you could name your function whatever you want. A convention for functions returning boolean is to name them with a question mark: `IP.equal?(s1, s2)` – Brett Beatty May 31 '19 at 20:42
  • @BrettBeatty Thank you. I wanted structs to be compared as equals when inserted inside a `MapSet`, but this doesn't seem possible, since they use the `:erlang.===` to compare elements, and it doesn't seem overloadable. – Shiyason May 31 '19 at 20:58

2 Answers2

1
defmodule IP do
  defstruct [:host, :port, :name]

  def left_ip == right_ip do
    Kernel.==(left_ip.host, right_ip.host) &&
    Kernel.==(left_ip.port, right_ip.port)
  end

end

In iex:

~/elixir_programs$ iex ip.ex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> left_ip = %IP{host: 10, port: 3, name: "Joe"}   
%IP{host: 10, name: "Joe", port: 3}

iex(2)> right_ip = %IP{host: 10, port: 3, name: "Karen"}
%IP{host: 10, name: "Karen", port: 3}

iex(3)> left_ip == right_ip
false

iex(4)> import Kernel, except: [==: 2]
Kernel

iex(5)> left_ip == right_ip           
** (CompileError) iex:5: undefined function ==/2

iex(5)> import IP                     
IP

iex(6)> left_ip == right_ip
true

Kernel is automatically imported into iex, so you have to import Kernel again excepting the function Kernel.==().

An import statement has to do with namespacing. By importing IP, you no longer have to precede a function's name with the module name:

IP.==(left, right)

v.

left == right

I wanted structs to be compared as equals when inserted inside a MapSet

No worky with MapSets:

iex(24)> left_ip == right_ip
true

iex(27)> ms = MapSet.new()  
#MapSet<[]>                                                

iex(28)> ms |> MapSet.put("hello") |> MapSet.put(left_ip) |> MapSet.put("hello") |> MapSet.put(right_ip) |> MapSet.put(left_ip) 
#MapSet<[
  %IP{host: 10, name: "Joe", port: 3},
  %IP{host: 10, name: "Karen", port: 3},
  "hello"
]>

Mapset.put calls Map.put which calls :maps.put--which is an erlang function.

7stud
  • 46,922
  • 14
  • 101
  • 127
  • 1
    `def %IP{host: host, port: port} == %IP{host: host, port: port}, do: true; def _ == _, do: false` looks a bit more Elixirish. – Aleksei Matiushkin Jun 01 '19 at 05:16
  • @AlekseiMatiushkin, Nice suggestion. Not only does it check the argument types, it sidesteps `Kernel.==()` by using pattern matching: `=`. – 7stud Jun 02 '19 at 22:55
1

While it’s not achievable with MapSet out of the box, one might easily implement this on their own. MapSet is a no-brainer, it’s a simple map under the hood.

defmodule IPs do
  defstruct m: %{}

  def put_new(%IPs{m: m}, %IP{host: host, port: port} = ip)
    %IPs{m | Map.put_new(m, {host, port}, {host, port}, ip)}
  end

  def values(%IPs{m: m}), do: Map.values(m)
end

You can also implement Access behaviour, or delegate all the functions you need to the underlying map, or replicate the whole MapSet behaviour.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160