¿Cómo se verifica el tipo de variable en Elixir?


138

En Elixir, ¿cómo verifica el tipo, como en Python:

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

Leí en Elixir que hay verificadores de tipo como 'is_bitstring', 'is_float', 'is_list', 'is_map', etc., pero ¿qué pasa si no tiene idea de cuál podría ser el tipo?

Respuestas:


104

No hay forma directa de obtener el tipo de variable en Elixir / Erlang.

Por lo general, desea saber el tipo de una variable para actuar en consecuencia; puede usar las is_*funciones para actuar según el tipo de una variable.

Learn You Some Erlang tiene un buen capítulo sobre cómo escribir en Erlang (y, por lo tanto, en Elixir).

La forma más idiomática de usar la is_*familia de funciones probablemente sería usarlas en coincidencias de patrones:

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

3
¿Erlang / Elixir realmente no tiene información de tipo almacenada? ¿Realmente necesito crear un envoltorio completamente nuevo sobre los tipos existentes para que el lenguaje sea utilizable? Oo
Dmitry

2
@Dmitry, ¿qué quieres decir con usable? ¿Puedo ver un ejemplo concreto en el que usarías el resultado de algo así typeof(variable)?
whatyouhide

1
Cuando un programa deja el tiempo de compilación y entra en tiempo de ejecución, se pierde toda la información sobre qué objeto se pierde. Cuando quiero inspeccionar la información de un programa en ejecución, la única forma de saber qué está pasando es inspeccionando las cosas que están expuestas a través de una red de mapas. Si la información de tipo no está disponible, y quiero inspeccionar el tipo, cuesta mucho más analizar el objeto para obtener su tipo que si el tipo ya estuviera expuesto. typeof nos permite analizar el sistema en ejecución y extenderlo en tiempo de ejecución de una manera que permita la verificación de tipos y el polimorfismo.
Dmitry

2
Para ser más especifico; El uso más útil de typeof es la capacidad de mapear directamente una tabla hash de [type string, function] en la lista de incógnitas. Por ejemplo; IO.puts no se puede asignar foo = [1, "hello", [1, 2, 3]], con código Enum.map(foo, fn(x) -> IO.puts x end)porque [1,2, 3] se leerá como caracteres (¿por qué erlang!?), Y le mostrará un montón de caras sonrientes (¡pruébelo!). así que nos vemos obligados a usar la inspección aunque la inspección solo es necesaria si es una lista, de lo contrario la mayoría de las veces no la necesitamos. typeof nos permite convertir las declaraciones if (O (n)) en búsquedas de diccionario (O (1)).
Dmitry

1
@Dmitry para ese tipo de uso Los protocolos de Elixir serían útiles. elixir-lang.org/getting-started/protocols.html Puede implementar su propio Printableprotocolo que envuelve y cambia el comportamiento de la impresión, por ejemplo, listas de enteros. Solo asegúrese de no usarlo con el código Erlang, o se rascará la cabeza preguntándose por qué en lugar de mensajes está viendo listas de enteros.
Matt Jadczak

168

Comenzando en elixir 1.2 hay un icomando en iex que enumerará el tipo y más de cualquier variable de 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 busca el icomando en el código , verá que se implementa mediante un protocolo.

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

Si desea implementar una función para cualquier tipo de Datos en Elixir, la forma de hacerlo es definir un Protocolo e implementar el Protocolo para todos los tipos de datos en los que desea que funcione la función. Desafortunadamente, no puedes usar una función de Protocolo en guardias. Sin embargo, un simple protocolo de "tipo" sería muy sencillo de implementar.


1
en 2019 esta devuelve un error undefined function i/1- lo mismo para el Info / 1
krivar

1
Esto todavía funciona en Elixir 1.8.1. Debe tener instalada una versión muy antigua de elixir.
Fred the Magic Wonder Dog

2
@krivar @ fred-the-magic-wonder-dog ambos tienen razón :). &i/1es una función de IEx.Helpers. Si lo pones &IEx.Helpers.i/1en tu vainilla Elixir, generarás un a CompileErrormenos que lo hayas incluido :iexcomo aplicación en tu mix.exs.
popedotninja

39

También para fines de depuración, si no está en iex, puede llamarlo directamente:

IEx.Info.info(5)
=> ["Data type": "Integer", "Reference modules": "Integer"]

1
Si desea verlo en su registro, agregue IO.inspect (IEx.Info.info (5))
Guillaume

24

Otro enfoque es utilizar la coincidencia de patrones. Digamos que estás usando Timex, que usa una %DateTime{}estructura, y quieres ver si un elemento es uno. Puede encontrar una coincidencia utilizando la coincidencia de patrones en el método.

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

def is_a_datetime?(_) do
  false
end

1
o, como la respuesta aceptada remarcó pero no enfatizó: "Usualmente quieres saber el tipo de una variable para actuar en consecuencia". en Elixir, actúas de acuerdo con el patrón, no con switch/ case.
mariotomo

18

Solo dejaré esto aquí por el bien de alguien que, con suerte, descubra una versión realmente sensata. Por el momento no hay buenas respuestas para esto en 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

En aras de la integridad, los casos de prueba:

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

Aquí hay una solución con protocolos; No estoy seguro de si son más rápidos (espero que no estén haciendo un bucle sobre todos los tipos), pero es bastante feo (y frágil; si agregan o eliminan un tipo básico o cambian el nombre, lo romperá).

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

Si realmente quieres un verificador de "tipo", puedes construir uno fácilmente usando las herramientas de la organización de piedra filosofal. github.com/philosophers-stone . La fenética aún está en los primeros días, pero puede hacer esto y mucho más.
Fred the Magic Wonder Dog

¿Me uniría fácilmente a una dependencia externa? ¿Cómo va a mejorar eso mi capacidad de compartir código con amigos? Este es un camino a 2 problemas.
Dmitry

Gracias por editar @aks; Ahora puedo volver a 4 espacios ^ _ ^
Dmitry

15

Acabo de pegar el código 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

Uso inteligente de la cita! Cuanto más veo el código de Elixir, más me recuerda a Perl; esa construcción ~ w se parece mucho a qw //. Me pregunto si Perl tiene algún mecanismo inteligente para simular la cita de Lisplike.
Dmitry

Me pregunto cómo funciona la cita; ¿Se puede emular usando un preprocesador de expresión regular o se requiere un recorrido analizador a través de todo el código para realizar la expansión de macros?
Dmitry

1

Me encontré con una situación que necesita verificar el parámetro debe ser cierto tipo. Quizás pueda activarse de una mejor manera.

Me gusta esto:

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

Uso:

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

1

Solo porque nadie lo ha mencionado

IO.inspect/1

Salidas para consolar el objeto ... es casi equivalente a JSON.stringify

Muy útil cuando simplemente no puede durante toda su vida descubrir cómo se ve un objeto en una prueba.


44
No es una respuesta a la pregunta, ni siquiera cerca
LowFieldTheory
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.