如何在Elixir中检查变量的类型

176
在Elixir中如何检查类型,就像在Python中一样:
>>> a = "test"
>>> type(a)
<type 'str'>
>>> b =10
>>> type(b)
<type 'int'>

我在 Elixir 中读到有诸如 'is_bitstring'、'is_float'、'is_list'、'is_map' 等类型检查器,但如果你不知道类型是什么怎么办?

12个回答

215

从Elixir 1.2开始,在iex中有一个i命令,可以列出任何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中为任何数据类型实现函数,那么要做的方法就是定义一个协议,并针对您希望该函数适用于的所有数据类型实现该协议。不幸的是,您无法在Guard中使用Protocol函数。然而,一个简单的“类型”协议非常容易实现。


1
在2019年,这会返回一个错误“未定义函数i/1” - 对于info/1也是如此。 - krivar
1
这在 Elixir 1.8.1 中仍然有效。你必须安装了非常旧的版本的 Elixir。 - Fred the Magic Wonder Dog
3
你们两个都是对的:). &i/1IEx.Helpers 中的一个函数。如果你在原始的 Elixir 中输入 &IEx.Helpers.i/1,除非你已经将 :iex 应用程序包含在你的 mix.exs 文件中,否则会生成一个 CompileError - popedotninja

122
在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

6
在Erlang/Elixir中,是否真的没有存储类型信息?我是否真的需要为了让语言可用而创建全新的包装器来处理现有类型?O.o - Dmytro
2
@Dmitry,您所说的“可用”是什么意思?您能给我一个具体的例子,展示一下何时会使用 typeof(variable) 的结果吗? - whatyouhide
2
当程序离开编译时进入运行时,关于某个对象的所有信息都会丢失。当我想要检查正在运行的程序的信息时,唯一的方法是检查通过映射网络公开的事物。如果类型信息不可用,并且我想要检查类型,则分析对象以获取其类型的成本比已经公开类型要高得多。typeof允许我们分析运行系统并在运行时扩展它,以允许类型检查和多态性。 - Dmytro
3
更具体来说,typeof 最有用的用途是能够直接将 [type string, function] 的哈希表映射到未知类型的列表。例如,使用代码 Enum.map(foo, fn(x) -> IO.puts x end) 无法映射到 foo = [1, "hello", [1, 2, 3]],因为 [1,2,3] 会被解释为字符(为什么Erlang!!?),并显示一堆笑脸(试试看!)。所以,即使不需要 inspect,我们也被迫使用它,除非它是一个列表,否则大多数情况下我们不需要它。typeof 让我们将 O(n) 的 if 语句转换为 O(1) 的字典查找。 - Dmytro
2
@Dmitry,对于这种情况,Elixir协议会很有用。http://elixir-lang.org/getting-started/protocols.html 您可以实现自己的“Printable”协议,它可以包装和更改打印行为,例如整数列表。只要确保不要与Erlang代码一起使用,否则您将会想了解为什么看到的是整数列表而不是消息。 - Matt Jadczak
显示剩余2条评论

65

另外,为了调试目的,如果您不在iex中,可以直接调用它:

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

4
如果您想在日志中查看它,请添加IO.inspect(IEx.Info.info(5))。 - Guillaume
如果你在IEx中或不在IEx中,都可以直接调用它。实际上,这与调用i/1不同,因为i/1仅打印信息,但是这个函数让你可以捕获结果。 - zenw0lf

36

我只是为了希望有人能够找到一个实际理智的版本,就把这个留在这里。目前在谷歌上还没有好的答案...

defmodule Util do
    def typeof(a) do
        cond do
            is_float(a)    -> "float"
            is_number(a)   -> "number"
            is_atom(a)     -> "atom"
            is_boolean(a)  -> "boolean"
            is_binary(a)   -> "binary"
            is_function(a) -> "function"
            is_list(a)     -> "list"
            is_tuple(a)    -> "tuple"
            true           -> "idunno"
        end    
    end
end
为了完整性起见,测试用例:

For the sake of completeness, test cases:

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(a)
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

如果你真的想要一个“类型”检查器,你可以轻松地使用哲学家之石组织中的工具构建一个。https://github.com/philosophers-stone。Phenetic仍处于早期阶段,但它可以做到这一点,还有更多功能。 - Fred the Magic Wonder Dog
1
如何轻松地绑定外部依赖项?这怎样能够提高我与朋友共享代码的能力呢?这是通往两个问题的路。 - Dmytro
感谢 @aks 的编辑;我现在可以回到4个空格了 ^_^ - Dmytro
在 Elixir 中,self 是当前进程的 PID,这可能会让人感到困惑。 - mrroot5
1
据我所知,在Elixir中self不是一个关键字,您指的是允许被本地变量/参数命名为selfself/1,而且没有任何合法问题可以被遮蔽。话虽如此,我将self更改为a,我想在最终版本中用_替换a,但我不记得Elixir足够了解是否正确(尽管它似乎可以工作)。 - Dmytro

29

另一种方法是使用模式匹配。比如你正在使用Timex,它使用一个%DateTime{}结构体,并且你想查看一个元素是否是其中之一。你可以在该方法中使用模式匹配来查找匹配项。

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

def datetime?(_) do
  false
end

4
正如接受的答案所指出但没有强调的那样:“通常你想要知道一个变量的类型是为了相应地采取行动”。在Elixir中,你通过模式匹配来采取相应的行动,而不是通过switch/case语句。 - mariotomo

20

聪明地使用引号!我看到越多的Elixir代码,它越让我想起Perl;那个~w结构看起来非常类似于qw//。我想知道Perl是否有一些聪明的机制来模拟Lisp风格的引用。 - Dmytro
我想知道引用是如何工作的;它是否可以使用正则表达式预处理器来模拟,还是需要解析器遍历整个代码来进行宏展开。 - Dmytro

3

在iex和您的代码中,您可以使用IEx.Info.info。结果是一个元组列表,因此包含为字符串的实际类型信息需要进行一些解包:

IEx示例

iex(1)> "abc" |> IEx.Info.info |> hd |> elem(1)
"BitString"

在你的代码中

def datatype(myvar) do
  myvar |> IEx.Info.info |> hd |> elem(1)
end

var1 = {1,2,3}
IO.puts(datatype(var1)) # will print the string "Tuple"

3

Useful.typeof/1

受这个主题的启发,我们将 typeof/1 添加到了我们的Useful 函数库中。它正是你所期望的。

mix.exs文件中将其添加到你的deps中:

def deps do
  [
    {:useful, "~> 0.4.0"}
  ]
end

用法:

iex> pi = 3.14159
iex> Useful.typeof(pi)
"float"

iex> fun = fn (a, b) -> a + b end
iex> Useful.typeof(fun)
"function"

iex> Useful.typeof(&Useful.typeof/1)
"function"

iex> int = 42
iex> Useful.typeof(int)
"integer"

iex> list = [1,2,3,4]
iex> Useful.typeof(list)
"list"

文档: https://hexdocs.pm/useful/Useful.html#typeof/1
一如既往,欢迎反馈、贡献和改进。


2

根据这里的实现 (链接),您可以对 IEx.Info.info/1 返回的元组进行模式匹配:

defmodule Type do
  def of(x) do
    [{_, type} | _] = IEx.Info.info(x)

    type
  end
end

Type.of(1) # "Integer"

1
我发现有一种情况需要检查参数的类型是否正确。也许可以采用更好的方法。
就像这样:
@required [{"body", "binary"},{"fee", "integer"}, ...]
defp match_desire?({value, type}) do
  apply(Kernel, :"is_#{type}", [value])
end

使用方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接