Comment vérifier le type de variable dans Elixir

138

Dans Elixir, comment vérifier le type comme en Python:

>>> a = "test"
>>> type(a)
<type 'str'>
>>> b =10
>>> type(b)
<type 'int'>

J'ai lu dans Elixir qu'il y avait des vérificateurs de type tels que 'is_bitstring', 'is_float', 'is_list', 'is_map' etc., mais que faire si vous n'avez aucune idée de ce que pourrait être le type?

Faible Kian Seong
la source

Réponses:

104

Il n'y a pas de moyen direct d'obtenir le type d'une variable dans Elixir / Erlang.

Vous souhaitez généralement connaître le type d'une variable pour agir en conséquence; vous pouvez utiliser les is_*fonctions pour agir en fonction du type d'une variable.

Learn You Some Erlang a un joli chapitre sur la saisie dans Erlang (et donc dans Elixir).

La manière la plus idiomatique d'utiliser la is_*famille de fonctions serait probablement de les utiliser dans des correspondances de motifs:

def my_fun(arg) when is_map(arg), do: ...
def my_fun(arg) when is_list(arg), do: ...
def my_fun(arg) when is_integer(arg), do: ...
# ...and so on
que cachez-vous
la source
3
Erlang / Elixir n'a-t-il vraiment aucune information de type stockée? Ai-je vraiment besoin de créer un tout nouveau wrapper sur les types existants pour que le langage soit utilisable? Oo
Dmitry
2
@Dmitry qu'entendez-vous par utilisable? Puis-je voir un exemple concret où vous utiliseriez le résultat de quelque chose comme typeof(variable)?
whatyouhide
1
Lorsqu'un programme quitte le moment de la compilation et entre au moment de l'exécution, toutes les informations sur l'objet de certains sont perdues. Lorsque je veux inspecter les informations d'un programme en cours d'exécution, la seule façon de savoir ce qui se passe est d'inspecter les choses qui sont exposées via un réseau de cartes. si les informations de type ne sont pas disponibles et que je veux inspecter le type, il en coûte beaucoup plus d'analyser l'objet pour obtenir son type que si le type était déjà exposé. typeof nous permet d'analyser le système en cours d'exécution et de l'étendre au moment de l'exécution d'une manière qui permet la vérification de type et le polymorphisme.
Dmitry
2
Pour être plus précis; l'utilisation la plus utile de typeof est la possibilité de mapper directement une table de hachage de [type string, function] sur une liste d'inconnues. Par exemple; IO.puts ne peut pas être mappé foo = [1, "hello", [1, 2, 3]], avec du code Enum.map(foo, fn(x) -> IO.puts x end)car [1,2, 3] sera lu comme des caractères (pourquoi erlang !!?), Et vous montrera un tas de visages souriants (essayez-le!). nous sommes donc obligés d'utiliser inspect même si inspect n'est nécessaire que s'il s'agit d'une liste, sinon la plupart du temps nous n'en avons pas besoin. typeof nous permet de transformer les instructions if (O (n)) en recherches dans le dictionnaire (O (1)).
Dmitry
1
@Dmitry pour ce type d'utilisation Les protocoles Elixir seraient utiles. elixir-lang.org/getting-started/protocols.html Vous pouvez implémenter votre propre Printableprotocole qui encapsule et change le comportement de l'impression, par exemple des listes d'entiers. Assurez-vous simplement de ne pas l'utiliser avec le code Erlang - ou vous vous gratterez la tête en vous demandant pourquoi au lieu de messages, vous voyez des listes d'entiers.
Matt Jadczak
168

À partir de elixir 1.2, il y a une icommande dans iex qui listera le type et plus de n'importe quelle variable Elixir.

iex> foo = "a string" 
iex> i foo 
Term
 "a string"
Data type
 BitString
Byte size
 8
Description
 This is a string: a UTF-8 encoded binary. It's printed surrounded by
 "double quotes" because all UTF-8 encoded codepoints in it are        printable.
Raw representation
  <<97, 32, 115, 116, 114, 105, 110, 103>>
Reference modules
  String, :binary

Si vous regardez dans le code de la icommande, vous verrez que cela est implémenté via un protocole.

https://github.com/elixir-lang/elixir/blob/master/lib/iex/lib/iex/info.ex

Si vous souhaitez implémenter une fonction pour n'importe quel type de données dans Elixir, la façon de le faire est de définir un protocole et l'implémentation du protocole pour tous les types de données sur lesquels vous souhaitez que la fonction fonctionne. Malheureusement, vous ne pouvez pas utiliser une fonction de protocole dans les gardes. Cependant, un protocole de "type" simple serait très simple à mettre en œuvre.

Fred le chien magique
la source
1
en 2019, cela renvoie une erreur undefined function i/1- idem pour info / 1
krivar
1
Cela fonctionne toujours dans Elixir 1.8.1. Vous devez avoir une très ancienne version d'elixir installée.
Fred the Magic Wonder Dog
2
@krivar @ fred-the-magic-wonder-dog vous avez tous les deux raison :). &i/1est une fonction sur IEx.Helpers. Si vous le mettez &IEx.Helpers.i/1dans votre élixir vanille, vous en générerez un CompileErrorsauf si vous l'avez inclus :iexcomme application dans votre fichier mix.exs.
popedotninja
39

Aussi à des fins de débogage, si vous n'êtes pas dans iex, vous pouvez l'appeler directement:

IEx.Info.info(5)
=> ["Data type": "Integer", "Reference modules": "Integer"]
Atomkirk
la source
1
Si vous voulez le voir dans votre journal, ajoutez IO.inspect (IEx.Info.info (5))
Guillaume
24

Une autre approche consiste à utiliser la correspondance de modèles. Supposons que vous utilisez Timex, qui utilise une %DateTime{}structure, et que vous voulez voir si un élément en est un. Vous pouvez trouver une correspondance en utilisant la correspondance de modèle dans la méthode.

def is_a_datetime?(%DateTime{}) do
  true
end

def is_a_datetime?(_) do
  false
end
popedotninja
la source
1
ou, comme la réponse acceptée le remarquait mais ne le soulignait pas: «Vous voulez généralement connaître le type d'une variable pour agir en conséquence». dans Elixir, vous agissez en conséquence par correspondance de motifs, pas par switch/ case.
mariotomo
18

Je vais juste laisser ceci ici pour le bien de quelqu'un qui, espérons-le, trouvera une version réellement saine d'esprit. Pour le moment, il n'y a pas de bonnes réponses à cette question à venir sur Google ...

defmodule Util do
    def typeof(self) do
        cond do
            is_float(self)    -> "float"
            is_number(self)   -> "number"
            is_atom(self)     -> "atom"
            is_boolean(self)  -> "boolean"
            is_binary(self)   -> "binary"
            is_function(self) -> "function"
            is_list(self)     -> "list"
            is_tuple(self)    -> "tuple"
            true              -> "idunno"
        end    
    end
end

Par souci d'exhaustivité, les cas de test:

cases = [
    1.337, 
    1337, 
    :'1337', 
    true, 
    <<1, 3, 3, 7>>, 
    (fn(x) -> x end), 
    {1, 3, 3, 7}
]

Enum.each cases, fn(case) -> 
    IO.puts (inspect case) <> " is a " <> (Util.typeof case)
end

Voici une solution avec des protocoles; Je ne sais pas s'ils sont plus rapides (j'espère bien qu'ils ne font pas une boucle sur tous les types), mais c'est assez moche (et fragile; s'ils ajoutent ou suppriment un type de base ou le renomment, cela le cassera).

defprotocol Typeable, do: def typeof(self)
defimpl Typeable, for: Atom, do: def typeof(_), do: "Atom"
defimpl Typeable, for: BitString, do: def typeof(_), do: "BitString"
defimpl Typeable, for: Float, do: def typeof(_), do: "Float"
defimpl Typeable, for: Function, do: def typeof(_), do: "Function"
defimpl Typeable, for: Integer, do: def typeof(_), do: "Integer"
defimpl Typeable, for: List, do: def typeof(_), do: "List"
defimpl Typeable, for: Map, do: def typeof(_), do: "Map"
defimpl Typeable, for: PID, do: def typeof(_), do: "PID"
defimpl Typeable, for: Port, do: def typeof(_), do: "Port"
defimpl Typeable, for: Reference, do: def typeof(_), do: "Reference"
defimpl Typeable, for: Tuple, do: def typeof(_), do: "Tuple"

IO.puts Typeable.typeof "Hi"
IO.puts Typeable.typeof :ok
Dmitry
la source
Si vous voulez vraiment un vérificateur de "type", vous en construisez facilement un en utilisant les outils de l'organisation de la pierre philosophale. github.com/philosophers-stone . Phenetic en est encore à ses débuts, mais il peut le faire et bien plus encore.
Fred the Magic Wonder Dog
me lier facilement à une dépendance externe? comment cela va-t-il améliorer ma capacité à partager du code avec des amis? C'est une route vers 2 problèmes.
Dmitry
Merci pour la modification @aks; Je peux en fait revenir à 4 espaces maintenant ^ _ ^
Dmitry
15

Je viens de coller le code de https://elixirforum.com/t/just-created-a-typeof-module/2583/5 :)

defmodule Util do
  types = ~w[function nil integer binary bitstring list map float atom tuple pid port reference]
  for type <- types do
    def typeof(x) when unquote(:"is_#{type}")(x), do: unquote(type)
  end
end
user3197999
la source
Utilisation intelligente du devis! Plus je vois du code Elixir, plus il me rappelle Perl; cette construction ~ w ressemble beaucoup à qw //. Je me demande si Perl a un mécanisme intelligent pour simuler une citation Lisplike.
Dmitry
Je me demande comment fonctionne la citation; peut-il être émulé à l'aide d'un préprocesseur d'expression régulière, ou nécessite-t-il un analyseur parcourant tout le code pour faire l'expansion des macros.
Dmitry
1

Je suis tombé sur une situation besoin de vérifier le paramètre doit être d'un certain type. Peut-être peut-être actif d'une meilleure façon.

Comme ça:

@required [{"body", "binary"},{"fee", "integer"}, ...]
defp match_desire?({value, type}) do
  apply(Kernel, :"is_#{type}", [value])
end

Usage:

Enum.map(@required, &(match_desire?/1))
Bingoabs
la source
1

Juste parce que personne ne l'a mentionné

IO.inspect/1

Sorties pour consoler l'objet ... c'est presque équivalent à JSON.stringify

Très utile lorsque vous ne pouvez tout simplement pas pour la vie de vous comprendre à quoi ressemble un objet dans un test.

John Nicholas
la source
4
Pas une réponse à la question, même pas proche
LowFieldTheory