30分钟学习Elixir,给Erlang程序员看到Elixir语法学习
首先是我为毛要学习Elixir?
Elixir是Rails核心成员开发的Erlang前端语言,Elixir的语法很多和Ruby很像,同样也继承了Ruby的魔性:“让你喜爱上编程!” ,另外Elixir植根于于Erlang 这样的老牌稳定的并发虚拟机,让诟病Erlang语法的各位程序员又有了一个新的选择。
- 类似Ruby的优雅语法给。
- 稳定的Erlang虚拟机。
- 并发
- lisp/宏的复活?
Elixir社区贡献了很多库比如ORM来填补Erlang的各种落后,社区复活了
- Awesome Elixir: 列出了很多好用的Elixir库
- 官网
- 标准库
- 本文Follow的是官网的Getting Started
Mac OS X
Homebrew
Update your homebrew to latest: brew update
Run: brew install elixir
Macports
Run: sudo port install elixir
交互模式
iex> 40 + 2
42
iex> "hello" <> " world"
"hello world"
运行脚本
IO.puts "Hello world from Elixir"
elixir simple.exs
Hello world from Elixir
iex> 1 # integer
iex> 0x1F # integer
iex> 1.0 # float
iex> true # boolean
iex> :atom # atom / symbol
iex> "elixir" # string
iex> [1, 2, 3] # list
iex> {1, 2, 3} # tuple
# Basic arithmetic
iex> 1 + 2
3
div(10, 2)
5
rem 10, 3
1
iex> round(3.58)
4
iex> trunc(3.58)
3
# Booleans
iex> true
true
iex> true == false
false
iex> is_boolean(true)
true
iex> is_boolean(1)
false
# Atoms
iex> :hello
:hello
iex> :hello == :world
false
# Strings
iex> "hellö"
"hellö"
iex> "hellö #{:world}"
"hellö world"
iex> is_binary("hellö")
true
iex> byte_size("hellö")
6
iex> String.length("hellö")
5
# Anonymous functions
iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> is_function(add)
true
iex> is_function(add, 2)
true
iex> is_function(add, 1)
false
iex> add.(1, 2)
3
# (Linked) Lists
iex> list = [1, 2, 3]
iex> hd(list)
1
iex> tl(list)
[2, 3]
# Tuples
iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2
iex> elem(tuple, 1)
"hello"
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> put_elem(tuple, 1, "world")
{:ok, "world"}
iex> tuple
{:ok, "hello"}
# 列表连接
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, 2, 3] -- [2]
[1, 3]
# 字符串连接
iex> "foo" <> "bar"
"foobar"
# 逻辑操作符
iex> true and true
true
iex> false or is_atom(:example)
true
类型排序
number < atom < reference < functions < port < pid < tuple < maps < list < bitstring
对于Elixir来说,可以用=对变量进行多次赋值。
iex(5)> x=1
1
iex(6)> x=2
2
但是=实际上是匹配运算符。
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1
更复杂一点的匹配
# 普通匹配
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
# 元组数量不对,匹配失败
iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}
# 类型不同,匹配失败
iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value: [:hello, "world", 42]
# 使用pin操作符^来对变量进行匹配而不是赋值
iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {y, ^x} = {2, 1}
{2, 1}
iex> y
2
iex> {y, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}
case
iex> case {1, 2, 3} do
...> {4, 5, 6} ->
...> "This clause won't match"
...> {1, x, 3} ->
...> "This clause will match and bind x to 2 in this clause"
...> _ ->
...> "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"
iex> x = 1
1
iex> case 10 do
...> ^x -> "Won't match"
...> _ -> "Will match"
...> end
"Will match"
iex> case {1, 2, 3} do
...> {1, x, 3} when x > 0 ->
...> "Will match"
...> _ ->
...> "Would match, if guard condition were not satisfied"
...> end
"Will match"
cond
iex> cond do
...> 2 + 2 == 5 ->
...> "This will not be true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> 1 + 1 == 2 ->
...> "But this will"
...> end
"But this will"
if and unless
iex> if true do
...> "This works!"
...> end
"This works!"
iex> unless true do
...> "This will never be seen"
...> end
nil
do end 代码块
iex> is_number(if true do
...> 1 + 2
...> end)
true
类型断言
comparison operators (==, !=, ===, !==, >, >=, <, <=)
boolean operators (and, or, not)
arithmetic operations (+, -, *, /)
arithmetic unary operators (+, -)
the binary concatenation operator <>
the in operator as long as the right side is a range or a list
all the following type check functions:
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_function/2
is_integer/1
is_list/1
is_map/1
is_nil/1
is_number/1
is_pid/1
is_port/1
is_reference/1
is_tuple/1
plus these functions:
abs(number)
binary_part(binary, start, length)
bit_size(bitstring)
byte_size(bitstring)
div(integer, integer)
elem(tuple, n)
hd(list)
length(list)
map_size(map)
node()
node(pid | ref | port)
rem(integer, integer)
round(number)
self()
tl(list)
trunc(number)
tuple_size(tuple)
字符串-string:a UTF-8 encoded binary
字节数和字符数是不同的。
iex> string = "hełło"
"hełło"
iex> byte_size(string)
7
iex> String.length(string)
5
获取代码点
iex> ?a
97
iex> ?ł
322
iex> String.codepoints("hełło")
["h", "e", "ł", "ł", "o"]
二进制-binary-bitstring
iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size(<<0, 1, 2, 3>>)
4
1 bit操作
iex> <<1 :: size(1)>>
<<1::size(1)>>
iex> <<2 :: size(1)>> # truncated
<<0::size(1)>>
iex> is_binary(<< 1 :: size(1)>>)
false
iex> is_bitstring(<< 1 :: size(1)>>)
true
iex> bit_size(<< 1 :: size(1)>>)
1
模式匹配
iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x>> = <<0, 1, 2, 3>>
** (MatchError) no match of right hand side value: <<0, 1, 2, 3>>
模式抽取
# 抽取了x
iex> <<0, 1, x :: binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> x
<<2, 3>>
字符列表:Char Lists: a list of characters:
iex> 'hełło'
[104, 101, 322, 322, 111]
iex> is_list 'hełło'
true
iex> 'hello'
'hello'
和字符转之间的转化
iex> to_char_list "hełło"
[104, 101, 322, 322, 111]
iex> to_string 'hełło'
"hełło"
iex> to_string :hello
"hello"
iex> to_string 1
"1"
关键字列表
iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true
iex> list[:a]
1
新奇语法
iex> list ++ [c: 3]
[a: 1, b: 2, c: 3]
iex> [a: 0] ++ list
[a: 0, a: 1, b: 2]
Keyword lists are important because they have three special characteristics:
- Keys 必须是原子。
- Keys 是有序的,顺序是开发者自己指定的。。
- Keys 可以重复。
神器the Ecto library 通过这样的代码来指定查询语句。
query = from w in Weather,
where: w.prcp > 0,
where: w.temp < 20,
select: w
map:关联数组
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil
map对比关键字列表有以下区别:
- Maps 允许任何类型作为key.
- Maps 的Keys是无序的,因为是关联数组嘛。
iex> %{} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
Map模块提供了一些方便的操作函数
iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
可以直接通过"点号"获取属性
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map.a
1
iex> map.c
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
iex> %{map | :a => 2}
%{2 => :b, :a => 2}
iex> %{map | :c => 3}
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
嵌套数据操作
iex> users = [
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
[john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]
iex> users[:john].age
27
iex> users = put_in users[:john].age, 31
[john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]
iex> users = update_in users[:mary].languages, &List.delete(&1, "Clojure")
[john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}]
定义模块
iex> defmodule Math do
...> def sum(a, b) do
...> a + b
...> end
...> end
iex> Math.sum(1, 2)
3
编译模块
defmodule Math do
def sum(a, b) do
a + b
end
end
# elixirc math.ex
iex> Math.sum(1, 2)
3
使用defp定义的是私有函数,不能被外部调用
defmodule Math do
def sum(a, b) do
do_sum(a, b)
end
defp do_sum(a, b) do
a + b
end
end
IO.puts Math.sum(1, 2) #=> 3
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)
erlang高级货
defmodule Math do
def zero?(0), do: true
def zero?(x) when is_integer(x), do: false
end
函数捕获/函数引用
iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function(fun)
true
iex> fun.(0)
true
# 本地函数或者被导入的函数,也可以被捕获。
iex> &is_function/1
&:erlang.is_function/1
iex> (&is_function/1).(fun)
true
捕获语法也可以用来创建简单函数
iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2
&1代表了传入参数的第一个函数。上面代码等同于 fn x -> x+1 end/
默认函数参数
defmodule Concat do
def join(a, b, sep \\ " ") do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
默认参数可以是表达式
defmodule DefaultTest do
def dowork(x \\ IO.puts "hello") do
x
end
end
iex> DefaultTest.dowork
hello
:ok
iex> DefaultTest.dowork 123
123
iex> DefaultTest.dowork
hello
:ok
多函数体的函数需要实现一个函数声明来指定默认值
defmodule Concat do
def join(a, b \\ nil, sep \\ " ")
def join(a, b, _sep) when is_nil(b) do
a
end
def join(a, b, sep) do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello")
一不小心就会导致函数覆盖
defmodule Concat do
def join(a, b) do
IO.puts "***First join"
a <> b
end
def join(a, b, sep \\ " ") do
IO.puts "***Second join"
a <> sep <> b
end
end
编译时报错:
concat.ex:7: warning: this clause cannot match because a previous clause at line 2 always matches
- 当两个参数时第一个函数永远匹配
- 当三个参数是第二个函数被调用。
和Erlang相比只是换了个语法。
defmodule Recursion do
def print_multiple_times(msg, n) when n <= 1 do
IO.puts msg
end
def print_multiple_times(msg, n) do
IO.puts msg
print_multiple_times(msg, n - 1)
end
end
Recursion.print_multiple_times("Hello!", 3)
# Hello!
# Hello!
# Hello!
另一个简单循环demo
defmodule Math do
def double_each([head | tail]) do
[head * 2 | double_each(tail)]
end
def double_each([]) do
[]
end
end
Enum 的Map Reduce
iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end)
6
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
可以换成捕获语法
iex> Enum.reduce([1, 2, 3], 0, &+/2)
6
iex> Enum.map([1, 2, 3], &(&1 * 2))
[2, 4, 6]
可枚举的
Elixir 提供了enumerables的概念和the Enum module
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
iex> map = %{a: 1, b: 2}
iex> Enum.map(map, fn {k, v} -> {k, v * 2} end)
[a: 2, b: 4]
Range
iex> Enum.map(1..3, fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.reduce(1..3, 0, &+/2)
6
Eager vs Lazy: 立即求值和惰性求值
Enum中的函数都是立即求值。
iex> odd? = &(rem(&1, 2) != 0)
#Function<6.80484245/1 in :erl_eval.expr/5>
iex> Enum.filter(1..3, odd?)
[1, 3]
神奇的小玩意。
1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
如果不使用管道,上面的代码会变成这样:
# 是不是难以阅读多了。
Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
Stream
相对于Enum模块是立即求值,Stream模块是惰性求值。
1..100_000 |> Stream.map(&(&1 * 3))
#Stream<[enum: 1..100000, funs: [#Function<34.16982430/1 in Stream.map/2>]]>
注意上面返回的是一个Stream而不是一个值,这个stream被执行后才返回值
。
同理
iex> stream = Stream.cycle([1, 2, 3])
#Function<15.16982430/2 in Stream.cycle/1>
iex> Enum.take(stream, 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
iex> stream = Stream.unfold("hełło", &String.next_codepoint/1)
#Function<39.75994740/2 in Stream.unfold/2>
iex> Enum.take(stream, 3)
["h", "e", "ł"]
所以Stream是惰性求值。
spawn
iex> spawn fn -> 1 + 2 end
#PID<0.43.0>
iex> Process.alive?(pid)
false
iex> self()
#PID<0.41.0>
iex> Process.alive?(self())
true
send and receive
ex> send self(), {:hello, "world"}
{:hello, "world"}
iex> receive do
...> {:hello, msg} -> msg
...> {:world, msg} -> "won't match"
...> end
"world"
超时
iex> receive do
...> {:hello, msg} -> msg
...> after
...> 1_000 -> "nothing after 1s"
...> end
"nothing after 1s"
Links
spawn_link/1
iex> spawn fn -> raise "oops" end
#PID<0.58.0>
[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
:erlang.apply/2
spawn_link/1之后
iex> spawn_link fn -> raise "oops" end
#PID<0.41.0>
** (EXIT from #PID<0.41.0>) an exception was raised:
** (RuntimeError) oops
:erlang.apply/2
因为shell会捕获所有异常并将其良好的显示,所以你需要在一个文件中做这个实验。
# spawn.exs
spawn_link fn -> raise "oops" end
receive do
:hello -> "let's wait until the process fails"
end
$ elixir spawn.exs
** (EXIT from #PID<0.47.0>) an exception was raised:
** (RuntimeError) oops
spawn.exs:1: anonymous fn/0 in :elixir_compiler_0.__FILE__/1
这一次进程失败并且把父进程连累的一起退出了,因为他们link了。
Tasks
Tasks是一个更上层的模块提供了更好的错误隔离和交互
iex(1)> Task.start fn -> raise "oops" end
{:ok, #PID<0.55.0>}
15:22:33.046 [error] Task #PID<0.55.0> started from #PID<0.53.0> terminating
** (RuntimeError) oops
(elixir) lib/task/supervised.ex:74: Task.Supervised.do_apply/2
(stdlib) proc_lib.erl:239: :proc_lib.init_p_do_apply/3
Function: #Function<20.90072148/0 in :erl_eval.expr/5>
Args: []
Instead of spawn/1 and spawn_link/1, we use Task.start/1 and Task.start_link/1 to return {:ok, pid} rather than just the PID. This is what enables Tasks to be used in supervision trees. Furthermore, Task provides convenience functions, like Task.async/1 and Task.await/1, and functionality to ease distribution.
State
Erlang状态循环。
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
end
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
The IO module
iex> IO.puts "hello world"
hello world
:ok
iex> IO.gets "yes or no? "
yes or no? yes
"yes\n"
The File module
iex> {:ok, file} = File.open "hello", [:write]
{:ok, #PID<0.47.0>}
iex> IO.binwrite file, "world"
:ok
iex> File.close file
:ok
iex> File.read "hello"
{:ok, "world"}
case File.read(file) do
{:ok, body} -> # do something with the `body`
{:error, reason} -> # handle the error caused by `reason`
end
{:ok, body} = File.read(file)
Path
iex> Path.join("foo", "bar")
"foo/bar"
iex> Path.expand("~/hello")
"/Users/jose/hello"
Processes and group leaders
实际上File.open返回的是一个进程。
iex> {:ok, file} = File.open "hello", [:write]
{:ok, #PID<0.47.0>}
iodata
可以直接写列表哟。
iex> IO.puts 'hello world'
hello world
:ok
iex> IO.puts ['hello', ?\s, "world"]
hello world
:ok
alias
别名只是换了个名字
defmodule Math do
alias Math.List, as: List
end
as 可以忽略
一次别名多个
alias MyApp.{Foo, Bar, Baz}
require
require和元编程相关。
宏是一些在编译时展开的代码块。想要使用宏必须require
iex> Integer.is_odd(3)
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.is_odd/1
iex> require Integer
Integer
iex> Integer.is_odd(3)
true
Integer.is_odd/1 is defined as a macro
- 模块不需要require。
- 如果要使用模块中定义的宏则需要require。
import
和以前的Erlang的导入一样,但是似乎没人用。
iex> import List, only: [duplicate: 2]
List
iex> duplicate :ok, 3
[:ok, :ok, :ok]
也可以按类型来导入
import Integer, only: :macros
import Integer, only: :functions
import是词法域的。
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
use
use和require的区别:
use requires the given module and then calls the using/1 callback on it allowing the module to inject some code into the current context. Generally speaking, the following module:
defmodule Example do
use Feature, option: :value
end
is compiled into
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
动态调用
模块放到变量中
iex> mod = :lists
:lists
iex> mod.flatten([1, [2], 3])
[1, 2, 3]
模块是可以嵌套的
defmodule Foo do
defmodule Bar do
end
end
# 等价
defmodule Elixir.Foo do
defmodule Elixir.Foo.Bar do
end
alias Elixir.Foo.Bar, as: Bar
end
模块属性主要有3个作用:
- 可以给模块添加更多的说明。
- 可以当成常量使用
- 编译期间的模块数据临时存储。
作为记号
- @moduledoc - provides documentation for the current module.
- @doc - provides documentation for the function or macro that follows the attribute.
- @behaviour - (notice the British spelling) used for specifying an OTP or user-defined behaviour.
- @before_compile - provides a hook that will be invoked before the module is compiled. This makes it possible to inject functions inside the module exactly before compilation.
defmodule Math do
@moduledoc """
Provides math-related functions.
## Examples
iex> Math.sum(1, 2)
3
"""
@doc """
Calculates the sum of two numbers.
"""
def sum(a, b), do: a + b
end
编译后可以通过h查看
$ elixirc math.ex
$ iex
iex> h Math # Access the docs for the module Math
...
iex> h Math.sum # Access the docs for the sum function
...
作为常量
defmodule MyServer do
@initial_state %{host: "147.0.0.1", port: 3456}
IO.inspect @initial_state
end
使用没有被初始化的常量会造成编译时警告
defmodule MyServer do
@unknown
end
warning: undefined module attribute @unknown, please remove access to @unknown or explicitly set it before access
作为临时数据存储
Plugin允许开发者自定义自己的模块。
defmodule MyPlug do
use Plug.Builder
plug :set_header
plug :send_ok
def set_header(conn, _opts) do
put_resp_header(conn, "x-header", "set")
end
def send_ok(conn, _opts) do
send(conn, 200, "ok")
end
end
IO.puts "Running MyPlug with Cowboy on http://localhost:4000"
Plug.Adapters.Cowboy.http MyPlug, []
上文中,plug是一个宏,它把每一个请求需要的插件都记录到一个模块变量里,但这需要更多的元编程知识才能看的懂,这里先忽略。
structs是在map的基础上建立起来的一层抽象。
定义struct
iex> defmodule User do
...> defstruct name: "John", age: 27
...> end
使用
iex> %User{}
%User{age: 27, name: "John"}
iex> %User{name: "Meg"}
%User{age: 27, name: "Meg"}
访问和更新结构体
iex> john = %User{}
%User{age: 27, name: "John"}
iex> john.name
"John"
iex> meg = %{john | name: "Meg"}
%User{age: 27, name: "Meg"}
iex> %{meg | oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "Meg"}
字段抽取
iex> %User{name: name} = john
%User{age: 27, name: "John"}
iex> name
"John"
struct 差不多就是map
iex> is_map(john)
true
iex> john.__struct__
User
但是为maps实现的协议并没有为struct实现
ex> john = %User{}
%User{age: 27, name: "John"}
iex> john[:name]
** (UndefinedFunctionError) undefined function: User.fetch/2
iex> Enum.each john, fn({field, value}) -> IO.puts(value) end
** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"}
但是因为struct就是map,所以它可以和Map模块很好的一起工作
iex> kurt = Map.put(%User{}, :name, "Kurt")
%User{age: 27, name: "Kurt"}
iex> Map.merge(kurt, %User{name: "Takashi"})
%User{age: 27, name: "Takashi"}
iex> Map.keys(john)
[:__struct__, :age, :name]
protocals基础
Protocals是elixr实现多态的一种方法,类比Erlang的behavour,golang的interface。
定义一个protocal
defprotocol Blank do
@doc "Returns true if data is considered blank/empty"
def blank?(data)
end
实现:
# Integers are never blank
defimpl Blank, for: Integer do
def blank?(_), do: false
end
# Just empty list is blank
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
# Just empty map is blank
defimpl Blank, for: Map do
# Keep in mind we could not pattern match on %{} because
# it matches on all maps. We can however check if the size
# is zero (and size is a fast operation).
def blank?(map), do: map_size(map) == 0
end
# Just the atoms false and nil are blank
defimpl Blank, for: Atom do
def blank?(false), do: true
def blank?(nil), do: true
def blank?(_), do: false
end
使用:
iex> Blank.blank?(0)
false
iex> Blank.blank?([])
true
iex> Blank.blank?([1, 2, 3])
false
为struct实现protocal
defmodule User do
defstruct name: "John", age: 27
end
defimpl Blank, for: User do
def blank?(_), do: false
end
那么就可以了
iex> Blank.blank?(%{})
true
iex> Blank.blank?(%User{})
实现any
对所有类型实现protocal可以很快变成一段乱麻,但是庆幸有any这个东西。
defimpl Blank, for: Any do
def blank?(_), do: false
end
Any也就是所有类型的意思。
集成Deriving
defmodule DeriveUser do
@derive Blank
defstruct name: "john", age: 27
end
但是这样并没有什么用,还需要fallback to any.
fallback to any,接口默认值实现
为protocal定义fall_back_to_any为true
defprotocol Blank do
@fallback_to_any true
def blank?(data)
end
这样就可以了。
内建协议
Enumerable就是一个协议
iex> Enum.map [1, 2, 3], fn(x) -> x * 2 end
[2, 4, 6]
iex> Enum.reduce 1..3, 0, fn(x, acc) -> x + acc end
6
String.Chars protocol
iex> tuple = {1, 2, 3}
{1, 2, 3}
iex> "tuple: #{tuple}"
** (Protocol.UndefinedError) protocol String.Chars not implemented for {1, 2, 3}
Inspect protocals:这个协议将所有类型转成可读的字符串。
iex> {1, 2, 3}
{1, 2, 3}
iex> %User{}
%User{name: "john", age: 27}
协议合并:consolidation
在后面的mix中你可以看到如下输出
Consolidated String.Chars
Consolidated Collectable
Consolidated List.Chars
Consolidated IEx.Info
Consolidated Enumerable
Consolidated Inspect
具体不详。
erlang的老东西,简单易懂
map
iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]
匹配filter:
iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]
函数filter
iex> multiple_of_3? = fn(n) -> rem(n, 3) == 0 end
iex> for n <- 0..5, multiple_of_3?.(n), do: n * n
[0, 9]
多列表推导
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
2进制生成
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
into魔法
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"
into 对map也可以用哟
iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
%{"a" => 1, "b" => 4}
记号以(~)开始并跟上一个字符。
正则表达式 ~r
# A regular expression that matches strings which contain "foo" or "bar":
iex> regex = ~r/foo|bar/
~r/foo|bar/
iex> "foo" =~ regex
true
iex> "bat" =~ regex
false
Strings ~s
iex> ~s(this is a string with "double" quotes, not 'single' ones)
"this is a string with \"double\" quotes, not 'single' ones"
Char lists ~c
iex> ~c(this is a char list containing 'single quotes')
'this is a char list containing \'single quotes\''
Word lists ~w
iex> ~w(foo bar bat)
["foo", "bar", "bat"]
# 给了a作为后缀代表atom.
iex> ~w(foo bar bat)a
[:foo, :bar, :bat]
插入符和转义符 大s和小s
iex> ~s(String with escape codes \x26 #{"inter" <> "polation"})
"String with escape codes & interpolation"
iex> ~S(String without escape codes \x26 without #{interpolation})
"String without escape codes \\x26 without \#{interpolation}"
用~S编写here doc中的转义字符非常方便
@doc ~S"""
Converts double-quotes to single-quotes.
## Examples
iex> convert("\"foo\"")
"'foo'"
"""
def convert(...)
自定义记号
下面两种写法相等
iex>
sigil_r(<<"foo">>, 'i')
~r"foo"i
h sigil_r
定义自己的
iex> defmodule MySigils do
...> def sigil_i(string, []), do: String.to_integer(string)
...> def sigil_i(string, [?n]), do: -String.to_integer(string)
...> end
iex> import MySigils
iex> ~i(13)
13
iex> ~i(42)n
-42
Errors
错误的计算会造成异常,比如原子和数字相加:
iex> :foo + 1
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(:foo, 1)
通过raise可以直接抛出异常:
iex> raise "oops"
** (RuntimeError) oops
iex> raise ArgumentError, message: "invalid argument foo"
** (ArgumentError) invalid argument foo
你也可以用defexception来定义自己的异常。
iex> defmodule MyError do
iex> defexception message: "default message"
iex> end
iex> raise MyError
** (MyError) default message
iex> raise MyError, message: "custom message"
** (MyError) custom message
Errors can be rescued using the try/rescue construct:
try do
raise "oops"
rescue
e in RuntimeError -> e
end
%RuntimeError{message: "oops"}
实际上,Elixir的开发者很少使用try/rescue结构,Elixr一般提供两个版本,一个返回错误,一个抛出异常,异常版本的函数以!结束。比如
File.read "hello"
File.read! "hello"
这也是ruby来的。
Throws
Throw和Cache一般用于无法在中间给出返回的控制流程,比如:
try do
Enum.each -50..50, fn(x) ->
if rem(x, 13) == 0, do: throw(x)
end
"Got nothing"
catch
x -> "Got #{x}"
end
"Got -39"
Exit
当一个进程退出时,它会发送一个exit
消息。
iex> spawn_link fn -> exit(1) end
#PID<0.56.0>
** (EXIT from #PID<0.56.0>) 1
exit也可以被try catch捕获
try do
exit "I am exiting"
catch
:exit, _ -> "not really"
end
"not really"
使用try/catch的情况本来就比较少见,用来捕获exit就更少见了。
After
After用来在异常发生后清理资源,比如关闭文件。
iex> {:ok, file} = File.open "sample", [:utf8, :write]
iex> try do
...> IO.write file, "olá"
...> raise "oops, something went wrong"
...> after
...> File.close(file)
...> end
** (RuntimeError) oops, something went wrong
有时候你可以少写一个try
iex> defmodule RunAfter do
...> def without_even_trying do
...> raise "oops"
...> after
...> IO.puts "cleaning up!"
...> end
...> end
iex> RunAfter.without_even_trying
cleaning up!
** (RuntimeError) oops
Elixir会自动加一个try,当你使用after, rescue, catch的时候。
Elixir是一门动态类型语言,所有的类型都是在运行时被推导的,尽管这样,Elixir提供了typespecs,主要用来:
- 定义用户自定义类型。
- 指明函数参数了类型,函数原型。
round/1’s typed signature is written as:
round(number) :: integer
::指的时候左边的表达式返回右边的类型,函数原型用@spec来编写。
@spec round(number) :: integer
def round(number), do: # implementation...
更多内容仔细看文档
例子1
defmodule LousyCalculator do
@spec add(number, number) :: {number, String.t}
def add(x, y), do: {x + y, "You need a calculator to do that?!"}
@spec multiply(number, number) :: {number, String.t}
def multiply(x, y), do: {x * y, "Jeez, come on!"}
end
We can use the @type directive in order to declare our own custom type.
defmodule LousyCalculator do
@typedoc """
Just a number followed by a string.
"""
@type number_with_remark :: {number, String.t}
@spec add(number, number) :: number_with_remark
def add(x, y), do: {x + y, "You need a calculator to do that?"}
@spec multiply(number, number) :: number_with_remark
def multiply(x, y), do: {x * y, "It is like addition on steroids."}
end
Custom types defined through @type are exported and available outside the module they’re defined in:
defmodule QuietCalculator do
@spec add(number, number) :: number
def add(x, y), do: make_quiet(LousyCalculator.add(x, y))
@spec make_quiet(LousyCalculator.number_with_remark) :: number
defp make_quiet({num, _remark}), do: num
end
Static code analysis-静态代码分析
Behaviours
用来定义模块API
defmodule Parser do
@callback parse(String.t) :: any
@callback extensions() :: [String.t]
end
defmodule JSONParser do
@behaviour Parser
def parse(str), do: # ... parse JSON
def extensions, do: ["json"]
end
defmodule YAMLParser do
@behaviour Parser
def parse(str), do: # ... parse YAML
def extensions, do: ["yml"]
end
have fun !.