`IO.puts "Hello world!"
接触elixir时, 学会的第一行代码是 IO.puts "Hello world!"
. 出于好奇, 我观察了一下 IO.puts
函数的实现.
IO
我们先到elixir源文件中的IO模块里看看.
IO 模块中 IO.puts
的定义
@doc """
Writes `item` to the given `device`, similar to `write/2`,
but adds a newline at the end.
"""
@spec puts(device, chardata | String.Chars.t) :: :ok
def puts(device \\ :stdio, item) do
:io.put_chars map_dev(device), [to_chardata(item), ?\n]
end
首先看它的类型, 这里我们需要先了解一点, 在erlang和elixir中, 字符串的类型是不同的.
比如 erlang 中 [97]
等同于 "a"
, 而 elixir 中 <<97>>
等同于 "a"
.
类型 device
, chardata
的定义
@type device :: atom | pid
@type chardata() :: :unicode.chardata()
定义内联函数 map_dev/1
, to_chardata/1
.
内联函数的作用是在编译时, 调用内联函数的地方会被替换成该函数的函数体.
@compile {:inline, map_dev: 1, to_chardata: 1}
# Map the Elixir names for standard IO and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
Kernel
Kernel 模块中 to_string/1
函数的定义
@doc """
Converts the argument to a string according to the
`String.Chars` protocol.
This is the function invoked when there is string interpolation.
## Examples
iex> to_string(:foo)
"foo"
"""
defmacro to_string(arg) do
quote do: String.Chars.to_string(unquote(arg))
end
String.Chars
protocol 对不同类型的参数的实现
import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc ~S"""
The `String.Chars` protocol is responsible for
converting a structure to a binary (only if applicable).
The only function required to be implemented is
`to_string` which does the conversion.
The `to_string/1` function automatically imported
by `Kernel` invokes this protocol. String
interpolation also invokes `to_string` in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_string(bar)`.
"""
def to_string(term)
end
defimpl String.Chars, for: Atom do
def to_string(nil) do
""
end
def to_string(atom) do
Atom.to_string(atom)
end
end
defimpl String.Chars, for: BitString do
def to_string(term) when is_binary(term) do
term
end
def to_string(term) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: term,
description: "cannot convert a bitstring to a string"
end
end
defimpl String.Chars, for: List do
def to_string(charlist), do: List.to_string(charlist)
end
defimpl String.Chars, for: Integer do
def to_string(term) do
Integer.to_string(term)
end
end
defimpl String.Chars, for: Float do
def to_string(term) do
IO.iodata_to_binary(:io_lib_format.fwrite_g(term))
end
end
注意这里使用了一个神奇的 erlang 函数 -- :io_lib_format.fwrite_g
用来转换 Float, 我看了一下 erlang 的源代码, 发现了一段注释
%% Writes the shortest, correctly rounded string that converts
%% to Float when read back with list_to_float/1.
%%
%% See also "Printing Floating-Point Numbers Quickly and Accurately"
%% in Proceedings of the SIGPLAN '96 Conference on Programming
%% Language Design and Implementation.
具体的实现挺复杂的, 有兴趣的朋友可以去看看 https://github.com/erlang/otp...
IO.iodata_to_binary/1
的定义
@doc """
Converts iodata (a list of integers representing bytes, lists
and binaries) into a binary.
The operation is Unicode unsafe.
Notice that this function treats lists of integers as raw bytes
and does not perform any kind of encoding conversion. If you want
to convert from a charlist to a string (UTF-8 encoded), please
use `chardata_to_string/1` instead.
If this function receives a binary, the same binary is returned.
Inlined by the compiler.
## Examples
iex> bin1 = <<1, 2, 3>>
iex> bin2 = <<4, 5>>
iex> bin3 = <<6>>
iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4 | bin3])
<<1, 2, 3, 1, 2, 3, 4, 5, 4, 6>>
iex> bin = <<1, 2, 3>>
iex> IO.iodata_to_binary(bin)
<<1, 2, 3>>
"""
@spec iodata_to_binary(iodata) :: binary
def iodata_to_binary(item) do
:erlang.iolist_to_binary(item)
end
总结
内联函数的作用是在编译时, 调用内联函数的地方会被替换成该函数的函数体, 适用于短小的函数. 定义的内联函数的方法是使用模块属性
@compile {:inline, fun_a: 1, fun_b: 1}
.使用
defprotocal
来定义协议. 使用defimpl .., for: ..
来为某种数据类型实现协议.quote do:
中间没有逗号