Techiniquement il est possible de définir des opérateurs dans un module Elixir :
Mais le test
Les deux premiers résultats montrent qu'on peut appeler l'opérateur préfixé de son module, jusque là tout va bien.
Les deux tests suivants montrent que quand utilisés normalement (infixes, sans module), les opérateurs renvoient des booléens. C'est dû au fait qu'ils sont automatiquement importés du module
Cependant, il existe des opérateurs disponibles qui ne sont pas définis par défaut, et qu'on peut donc utiliser librement :
Mais ils sont définis comme des fonctions locales. Le dernier résultat de
Dans le test
Par contre, l'opérateur
Il serait possible d'importer une sélection :
Allez j'arrête d'étaler ma science ! Mais en conclusion je dirais qu'il vaut mieux utiliser
Edit: Le fait que
defmodule BuildingCost do
defstruct items: %{}
end
defmodule Resources do
defstruct items: %{}
def resources < cost do
case __MODULE__.>=(resources, cost) do
:yep -> :nope
:nope -> :yep
end
end
def resources >= cost do
contains(resources, cost)
end
def resources ~> cost do
contains(resources, cost)
end
def contains(%Resources{} = resources, %BuildingCost{} = cost) do
:yep
end
def inside_module(resources, cost) do
resources ~> cost
end
end
defmodule Test do
def run() do
[
# :yep
Resources.>=(%Resources{}, %BuildingCost{}),
# :nope
Resources.<(%Resources{}, %BuildingCost{}),
# false
%Resources{} < %BuildingCost{},
# true
%Resources{} >= %BuildingCost{},
# :yep
Resources.inside_module(%Resources{}, %BuildingCost{})
# Compile error: undefined function ~>/2
# %Resources{} ~> %BuildingCost{}
]
|> IO.inspect(pretty: true)
end
def run2() do
import Resources
[
# :yep
%Resources{} ~> %BuildingCost{},
# :yep
contains(%Resources{}, %BuildingCost{})
# Compile error: function >=/2 imported from both Resources and Kernel, call is ambiguous
# %Resources{} >= %BuildingCost{}
]
|> IO.inspect(pretty: true)
end
end
Test.run()
Test.run2()
Mais le test
run()
ci-dessus renvoie la liste suivante : [:yep, :nope, false, true, :yep]
. Les deux premiers résultats montrent qu'on peut appeler l'opérateur préfixé de son module, jusque là tout va bien.
Les deux tests suivants montrent que quand utilisés normalement (infixes, sans module), les opérateurs renvoient des booléens. C'est dû au fait qu'ils sont automatiquement importés du module
Kernel
et le langage ne vérifie pas que l'opérateur soit redéfini dans un module qui définit la struct (ici %Resources{}
). Car les strucs sont en fait de simples objets avec une clé __struct__
.Cependant, il existe des opérateurs disponibles qui ne sont pas définis par défaut, et qu'on peut donc utiliser librement :
\\, <-, |, ~>>, <<~, ~>, <~, <~>, <|>, <<<, >>>, |||, &&& et ^^^
. (fuck le smiley, l'opérateur est trois `^`.Mais ils sont définis comme des fonctions locales. Le dernier résultat de
run()
montre qu'appeler Resources.inside_module
permet bien d'utiliser l'opérateur ~>
au sein du module Resources
. Par contre la ligne du dessous dans le test renvoie une erreur de compilation parce que la fonction ~>
n'est pas définie.Dans le test
run2()
, j'ai importé les fonctions du module Resources
(au lieu de l'importer de façon classique au niveau du module Test
, ceci afin de ne pas avoir a créer deux modules de tests, et puis ça montre au passage qu'on peut importer des contextes où on veut, puissant mais à utiliser avec parcimonie) ; on peut donc appeler ~>
et contains
directement.Par contre, l'opérateur
>=
déclenche une erreur de compilation : "function >=/2 imported from both Resources and Kernel, call is ambiguous". Kernel
est toujours importé automatiquement, mais n'a pas de précédence sur un autre module.Il serait possible d'importer une sélection :
import Resources, only: [{:~>, 2}, {:contains, 2}]
, mais dans ce cas %Resources{} >= %BuildingCost{}
renverrait un booléen et pas :yep
.Allez j'arrête d'étaler ma science ! Mais en conclusion je dirais qu'il vaut mieux utiliser
contains
dans tous les cas.Edit: Le fait que
%Resources{} < %BuildingCost{}
renvoie false
est simplement dû à l'ordre alphabétique