Как проверить тип переменной в Эликсире


141

В Elixir как проверить тип, например, в Python:

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

Я читал в Elixir, что есть средства проверки типов, такие как is_bitstring, is_float, is_list, is_map и т. Д., Но что, если вы понятия не имеете, каким может быть тип?

Ответы:


107

Нет прямого способа получить тип переменной в Elixir / Erlang.

Обычно вы хотите знать тип переменной, чтобы действовать соответствующим образом; вы можете использовать is_*функции, чтобы действовать в зависимости от типа переменной.

В «Learn You Some Erlang» есть хорошая глава о наборе текста на Erlang (и, следовательно, в Elixir).

Самым идиоматическим способом использования is_*семейства функций, вероятно, было бы использование их в сопоставлении с образцом:

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 нет хранимой информации о типах? Мне действительно нужно создать совершенно новую оболочку для существующих типов, чтобы язык можно было использовать? Oo
Дмитрий

2
@ Дмитрий, что ты имеешь в виду под словом "пригодный"? Могу я увидеть конкретный пример, в котором вы бы использовали результат чего-то вроде typeof(variable)?
whatyouhide

1
Когда программа покидает время компиляции и входит в среду выполнения, вся информация о том, какой объект был утерян. Когда я хочу проверить информацию о запущенной программе, единственный способ узнать, что происходит, - это проверить то, что отображается через сеть карт. если информация о типе недоступна, и я хочу проверить тип, анализ объекта для получения его типа стоит гораздо дороже, чем если бы тип уже был открыт. typeof позволяет нам анализировать работающую систему и расширять ее во время выполнения таким образом, чтобы допускать проверку типов и полиморфизм.
Дмитрий

2
Чтобы быть более конкретным; Наиболее полезное использование typeof - это возможность напрямую отображать хеш-таблицу [строка типа, функция] в список неизвестных. Например; IO.puts не могут быть сопоставлены foo = [1, "hello", [1, 2, 3]]с кодом, Enum.map(foo, fn(x) -> IO.puts x end)потому что [1,2, 3] будут читаться как символы (почему erlang !!?), И покажет вам кучу улыбающихся лиц (попробуйте!). поэтому мы вынуждены использовать inspect, хотя inspect нужен только в том случае, если это список, в противном случае в большинстве случаев он нам не нужен. typeof позволяет нам превратить операторы if (O (n)) в поиск по словарю (O (1)).
Дмитрий

1
@Dmitry для такого типа использования были бы полезны протоколы Elixir. elixir-lang.org/getting-started/protocols.html Вы можете реализовать свой собственный Printableпротокол, который обертывает и изменяет поведение печати, например списки целых чисел. Просто убедитесь, что вы не используете его с кодом Erlang, иначе вы будете ломать голову, задаваясь вопросом, почему вместо сообщений вы видите списки целых чисел.
Мэтт Ядчак

175

Начиная с elixir 1.2, iв iex есть команда, которая перечисляет тип и другие параметры любой переменной 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

Если вы посмотрите код iкоманды, вы увидите, что это реализовано через протокол.

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

Если вы хотите реализовать функцию для любого типа данных в Elixir, способ сделать это - определить протокол и реализацию протокола для всех типов данных, с которыми должна работать функция. К сожалению, вы не можете использовать функцию протокола в охранниках. Однако простой «типовой» протокол было бы очень просто реализовать.


1
в 2019 году это возвращает ошибку undefined function i/1- то же самое для info / 1
krivar

1
Это все еще работает в Elixir 1.8.1. У вас должна быть установлена ​​очень старая версия elixir.
Фред Волшебный чудо-пес

2
@krivar @ fred-the-magic-wonder-dog, вы оба правы :). &i/1это функция на IEx.Helpers. Если вы добавите его &IEx.Helpers.i/1в свой ванильный эликсир, вы создадите файл, CompileErrorесли только вы не включили его в :iexкачестве приложения в свой mix.exs.
popedotninja

44

Также для целей отладки, если вы не в iex, вы можете вызвать его напрямую:

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

3
Если вы хотите видеть это в своем журнале, добавьте IO.inspect (IEx.Info.info (5))
Гийом

24

Другой подход - использовать сопоставление с образцом. Допустим, вы используете Timex, который использует %DateTime{}структуру, и хотите проверить, является ли элемент одним из них. Вы можете найти совпадение, используя сопоставление с образцом в методе.

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

def is_a_datetime?(_) do
  false
end

1
или, как отмечается в принятом ответе, но не подчеркивается: «Обычно вы хотите знать тип переменной, чтобы действовать соответствующим образом». в Elixir вы действуете соответственно путем сопоставления с образцом, а не с помощью switch/ case.
mariotomo

19

Я просто оставлю это здесь, чтобы кто-нибудь, надеюсь, придумал действительно разумную версию. На данный момент в 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

Для полноты картины тестовые примеры:

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

Вот решение с протоколами; Я не уверен, быстрее ли они (я надеюсь, что они не делают цикл для всех типов), но это довольно уродливо (и хрупко; если они добавят или удалят базовый тип или переименуют, это сломает его).

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

Если вам действительно нужен «типограф», вы легко создадите его, используя инструменты организации «Философский камень». github.com/philosophers-stone . Phenetic все еще находится на раннем этапе развития, но он может делать это и многое другое.
Fred the Magic Wonder Dog

легко привязать себя к внешней зависимости? как это улучшит мою способность делиться кодом с друзьями? Это путь к двум проблемам.
Дмитрий

Спасибо за редактирование @aks; Теперь я могу вернуться к 4 пробелам ^ _ ^
Дмитрий

16

Я просто вставляю код с 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

Умное использование цитаты! Чем больше я вижу код Elixir, тем больше он напоминает мне Perl; эта конструкция ~ w очень похожа на qw //. Интересно, есть ли в Perl какой-нибудь умный механизм для имитации цитат в стиле Lisplike.
Дмитрий

Интересно, как работает цитата; может ли он быть эмулирован с помощью препроцессора регулярных выражений, или он требует, чтобы синтаксический анализатор прошел через весь код, чтобы выполнить расширение макроса.
Дмитрий

1

Я столкнулся с ситуацией, когда нужно проверить параметр, который должен быть определенного типа. Может быть, активный способ получше.

Как это:

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

Применение:

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

1

Просто потому, что об этом никто не упомянул

IO.inspect/1

Выводит на консоль объект ... его почти эквивалент JSON.stringify

Очень полезно, когда вы всю жизнь не можете понять, как выглядит объект в тесте.


4
Не ответ на вопрос, даже близко
LowFieldTheory
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.