elixir 入门笔记

来源:转载

安装

MAC 平台用 brew 安装

brew update

brew install elixir

如果没有 erlang 环境,上面的命令会自定安装 erlang 的环境。

基本数据类型

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

数学运算

iex> 1 / 2

1 / 2

0.5

/ 总是返回浮点数,如果需要整数运算,使用 div 和 rem 函数

iex> div(1, 2)

div(1, 2)

iex> rem(1, 2)

rem(1, 2)

1

二进制,八进制,十六进制表示方式

iex> 0b10000

0b10000

16

iex> 0o20

0o20

16

iex> 0x10

0x10

16

原子

原子是一种常量,变量名就是它的值。有2种写法:

  1. :原子名
  2. :"原子名"

列表

  1. 列表中可以包含任意数据类型

    iex> [1, 1.2, true, "hello"]

    [1, 1.2, true, "hello"]

    [1, 1.2, true, "hello"]

  2. 列表可以通过 ++/– 来拼接

    iex> [1, 2, true] ++ [1, 3, false]

    [1, 2, true] ++ [1, 3, false]

    [1, 2, true, 1, 3, false]

    iex> [1, 2, true, 2, false] -- [1, 3, false, 2]

    [1, 2, true, 2, false] -- [1, 3, false, 2]

    [true, 2]

  3. 可以通过 hd/1 tl/1 函数来获取头部和头部以外的部分

    iex> hd([1, 2, true])

    hd([1, 2, true])

    1

    iex> tl([1, 2, true])

    tl([1, 2, true])

    [2, true]

  4. 对一个空列表执行 hd/1 和 tl/1 会报错

    iex> hd([])

    hd([])

    ** (ArgumentError) argument error

    :erlang.hd([])

    iex> tl([])

    tl([])

    ** (ArgumentError) argument error

    :erlang.tl([])

列表和元组区别

  1. 列表是以链表形式在内存存储的,元组是在内存中连续存储的。
  2. 列表前置拼接操作很快,后置拼接操作慢

    后置拼接时修改了原列表的最后一个元素,所以会重建整个列表

  3. 函数的返回值一般用元组来保存

字符串和字符列表

  1. 双引号包裹的是字符串: 字符串中存储的是 byte
  2. 单引号包裹的是字符列表: 字符列表中存储的是每个字符的 codepoint

基本运算符

  1. 算术运算 + - * / div/2 rem/2
  2. 列表拼接 ++ –
  3. 字符串拼接 <>

    iex> "foo" <> "bar"

    "foo" <> "bar"

    "foobar"

  4. 布尔运算符

    • or and not 这3个运算符只接受布尔值作为第一个参数

      iex> true or 1

      true or 1

      true

      iex> 1 or true

      1 or true

      ** (ArgumentError) argument error: 1

    • || && ! 这3个运算符可以接受非布尔值作为第一个参数

      iex> 1 || true

      1 || true

      1

      iex> true || 1

      true || 1

      true

  5. 比较运算符

    = ! = !== <= >= < >

    • = != !== 相比,后者的检查更加严格

      iex> 1 == 1.0

      1 == 1.0

      true

      iex> 1 === 1.0

      1 === 1.0

      false

      iex> 1 != 1.0

      1 != 1.0

      false

      iex> 1 !== 1.0

      1 !== 1.0

      true

    • 不同数据类型之间也可以比较大小

      iex> 1 < "hello"

      1 < "hello"

      true

      iex> 1 > "hello"

      1 > "hello"

      false

      iex> 1 < [1, 2]

      1 < [1, 2]

      true

      iex> "hello" < [1, 2]

      "hello" < [1, 2]

      false

      不同数据类型之间的默认的顺序如下:

      number < atom < reference < functions < port < pid < tuple < maps < list < bitstring

模式匹配

  1. elixir 中 = 是模式匹配运算符
  2. 可以给list 的 head 和 tail 赋值

    iex> [h|t]=[1,2,3]

    [1, 2, 3]

    iex> h

    1

    iex> t

    [2, 3]

控制语句

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

case的条件中可以加入判断的表达式,比如下面的 (when x > 0)

iex> case {1, 2, 3} do

...> {1, x, 3} when x > 0 ->

...> "Will match"

...> _ ->

...> "Won't match"

...> end

cond

iex> cond do

...> 2 + 2 == 5 ->

...> "This will not be true"

...> 2 * 2 == 3 ->

...> "Nor this"

...> 1 + 1 == 2 ->

...> "But this will"

...> 3 + 3 == 6 ->

...> "But this will too"

...> end

"But this will"

只会执行第一个匹配上的分支

if/unless

iex> if nil do

...> "This won't be seen"

...> else

...> "This will"

...> end

"This will"

unless 和 if 相反,条件为false时才执行

iex> unless true do

...> "This won't be seen"

...> else

...> "This will"

...> end

"This will"

do

do 语句快有2种写法:

iex> if true do

...> "this is true"

...> else

...> "this is false"

...> end

OR

iex> if true, do: ("this is true"), else: ("this is false")

键值列表-图-字典

键值列表

iex> l = [{:a, 1},{:b, 2}]

[a: 1, b: 2]

iex> l[:a]

1

iex> l[:b]

2

键值列表还有另一种定义方式:(注意 a: 和 1 之间必须有个空格)

iex> l = [a: 1, b: 2]

[a: 1, b: 2]

键值列表2个特点:

  1. 有序
  2. key 可以重复,重复时,优先取排在前面的key

iex> l = [a: 3] ++ l;

[a: 3, a: 1, b: 2]

iex> l

[a: 3, a: 1, b: 2]

iex> l[:a]

3

图的2个特点:

  1. 图中的key是无序的
  2. 图的key可以是任意类型

iex> map = %{:a => 1, 2 => :b}

%{2 => :b, :a => 1}

图匹配时,只要 = 右边包含左边的值就能匹配上

iex> %{} = %{:a => 1, 2 => :b}

%{2 => :b, :a => 1}

iex> %{:a => 1, 2 => :b} = %{}

** (MatchError) no match of right hand side value: %{}

iex> %{:a => 1} = %{:a => 1, 2 => :b}

%{2 => :b, :a => 1}

iex> %{:a => 1, :c => 2} = %{:a => 1, 2 => :b}

** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}

修改图中的值可以用以下的方式:

iex> %{map | 2 => :c}

%{2 => :c, :a => 1}

字典

以上的 键值列表 和 图 都是 字典 ,它们都实现了 Dict 接口。

此模块现在已经 deprecated

模块和函数定义

模块和函数定义方式

defmodule Math do

def sum(a, b) do

do_sum(a, b)

end

defp do_sum(a, b) do

a + b

end

end

Math.sum(1, 2) #=> 3

Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)

函数中的卫兵表达式

defmodule Math do

def zero?(0) do

true

end

def zero?(x) when is_number(x) do

false

end

end

Math.zero?(0) #=> true

Math.zero?(1) #=> false

Math.zero?([1,2,3])

#=> ** (FunctionClauseError)

默认参数

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

枚举类型和流

枚举类型

枚举类型提供了大量函数来对列表进行操作

iex> Enum.sum([1,2,3])

6

iex> Enum.map(1..3, fn x -> x * 2 end)

[2, 4, 6]

iex> Enum.reduce(1..3, 0, &+/2)

6

iex> Enum.filter(1..3, &(rem(&1, 2) != 0))

[1, 3]

枚举操作都是积极的,比如如下的操作:

iex> odd? = &(rem(&1, 2) != 0)

#Function<6.54118792/1 in :erl_eval.expr/5>

iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum

7500000000

以上每步的操作(Enum.map, Enum.filter)都会产生一个新的列表,这就是 积极 的意思。

和上面的枚举类型对应,流的处理是 懒惰 的,比如:

iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum

7500000000

表面上看,和枚举类型的处理一样,而实际上,流先创建了一系列的计算操作。然后仅当我们把它传递给Enum模块,它才会被调用。

iex> stream = 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?)

#Stream<[enum: 1..100000,

funs: [#Function<23.27730995/1 in Stream.map/2>,

#Function<8.27730995/1 in Stream.filter/2>]]>

iex> Enum.sum(stream) <== 这里才开始执行

7500000000

进程

elixir中进程都是轻量级的,所以使用时不用太在意进程的数目。

  1. 派生的进程执行完自动结束自己

    iex> pid = spawn fn -> 1 + 2 end

    #PID<0.62.0>

    iex> Process.alive?(pid)

    false

  2. 发送和接收消息

    下面示例中是给自己发送了一条消息,可以通过 flush/1 函数刷新消息,刷新一次之后就

    iex> send self(), {:hello, "world"}

    {:hello, "world"}

    iex> flush

    {:hello, "world"}

    :ok

    iex> flush

    :ok

    也可以通过 receive/1 函数来接收

    iex> send self(), {:hello, "world"}

    {:hello, "world"}

    iex> receive do

    ...> {:hi, msg} -> msg

    ...> {:hello, msg} -> msg

    ...> end

    "world"

    receive/1 函数收不到消息会阻塞,可以给它设置一个超时时间

    iex> receive do

    ...> {:hello, msg} -> msg

    ...> after

    ...> 3000 -> "timeout"

    ...> end

    "timeout"

  3. 进程间的连接

    进程B连接进程A之后,进程A出现异常,进程B就能捕获,这样进程B就能处理进程A的异常

    进程连接的方式很简单,就是 spawn_link/1 函数

    iex> spawn fn -> raise "oops" end

    #PID<0.76.0>

    15: 18:22.804 [error] Process #PID<0.76.0> raised an exception

    ** (RuntimeError) oops

    :erlang.apply/2

    iex> spawn_link fn -> raise "oops" end

    ** (EXIT from #PID<0.73.0>) an exception was raised:

    ** (RuntimeError) oops

    :erlang.apply/2

    15: 18:31.533 [error] Process #PID<0.78.0> raised an exception

    ** (RuntimeError) oops

    :erlang.apply/2

    关注 Process模块 模块,里面提供了进程操作的函数

  4. 进程中保存状态的方法:

    defmodule KV do

    def start do

    {:ok, spawn_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

    iex> send pid, {:put, :hello, :world}

    #PID<0.62.0>

    iex> send pid, {:get, :hello, self()}

    {:get, :hello, #PID<0.41.0>}

    iex> flush

    :world

    实际使用时,可以用 Agent 模块 来简化上面的操作。

模块属性

elixir中模块的属性主要有3个作用:

  1. 作为一个模块的注释,通常附加上用户或虚拟机用到的信息
  2. 作为常量
  3. 在编译时作为一个临时的模块存储

注释

注释时,一些常用的模块属性如下:

@moduledoc为当前模块提供文档@doc为该属性后面的函数或宏提供文档@behaviour(注意这个单词是英式拼法)用来注明一个OTP或用户自定义行为@before\_compile提供一个每当模块被编译之前执行的钩子。这使得我们可以在模块被编译之前往里面注入函数。
名称含义

常量

作为常量:

defmodule MyServer do

@my_data 14

def first_data, do: @my_data

@my_data 13

def second_data, do: @my_data

end

测试方法:

iex> MyServer.first_data

14

iex> MyServer.second_data

13

临时存储

模块中的变量只在编译时存在,所以用做临时存储,存储一些只在编译时使用的变量。

示例:

defmodule MyServer do

@my_data 14

def first_data, do: @my_data

@my_data 13

def second_data, do: @my_data

end

iex> MyServer.first_data #=> 14

iex> MyServer.second_data #=> 13

结构体

  1. 定义

    defmodule User do

    defstruct name: "harry", age: 32

    end

  2. 使用方式

    iex> j = %User{}

    %User{age: 32, name: "harry"}

    iex> j.name

    "harry"

    iex> j[:name]

    ** (UndefinedFunctionError) undefined function User.fetch/2

    User.fetch(%User{age: 32, name: "harry"}, :name)

    (elixir) lib/access.ex:77: Access.get/3

    iex> j.__struct__

    User

协议

协议类似于其他语言中的接口,谁实现了协议,谁就可以使用协议,

比如下面的例子,Integer 和 User 结构体实现了协议,就可以使用协议中的方法。

defmodule User do

defstruct name: "harry", age: 32

end

defprotocol Enough do

def enough?(data)

end

defimpl Enough, for: Integer do

def enough?(data) do

if data > 0 do

true

else

false

end

end

end

defimpl Enough, for: User do

def enough?(data) do

if data.age > 18 do

true

else

false

end

end

end

使用示例:

iex> Enough.enough?(11)

true

iex> Enough.enough?(0)

false

iex> u = %User{}

%User{age: 32, name: "harry"}

iex> Enough.enough?(u)

true

iex> u = %{u|age: 10}

%User{age: 10, name: "harry"}

iex> Enough.enough?(u)

false

iex> Enough.enough?("string")

** (Protocol.UndefinedError) protocol Enough not implemented for "string"

iex:3: Enough.impl_for!/1

iex:4: Enough.enough?/1

上面的 string 类型没有实现协议,所以不能使用。

我们在实际使用中也不会对没种类型都实现协议,为了避免出现异常,可以设置协议对所有类型的默认实现

defprotocol Enough do

@fallback_to_any true

def enough?(data)

end

defimpl Enough, for: Any do

def enough?(_), do: false

end

这样以后,如下使用就不会报错了

iex> Enough.enough?("string")

false

异常处理

自定义异常

自定义异常使用 defexception/1 函数,

iex> h(defexception)

The most common way to raise an exception is via raise/2:

┃ defmodule MyAppError do

┃ defexception [:message]

┃ end

┃ value = [:hello]

┃ raise MyAppError,

┃ message: "did not get what was expected, got: #{inspect value}"

In many cases it is more convenient to pass the expected value to raise/2 and

generate the message in the exception/1 callback:

┃ defmodule MyAppError do

┃ defexception [:message]

┃ def exception(value) do

┃ msg = "did not get what was expected, got: #{inspect value}"

┃ %MyAppError{message: msg}

┃ end

┃ end

┃ raise MyAppError, value

The example above shows the preferred strategy for customizing exception

messages.

异常的使用

elixir 虽然提供了 try/catch/rescue/after 的结构,但是尽量不要使用这种结构,使用这种异常处理方式,会影响现有程序的处理流程。

elixir 的很多函数都会返回错误信号,通过信号来处理错误是推荐的方式(类似golang的错误处理),比如如下示例:

iex> case File.read "hello" do

...> {:ok, body} -> IO.puts "got ok"

...> {:error, body} -> IO.puts "got error"

...> end

列表速构

速构的意思也就是从一个列表方便的生成另一个列表。

  1. 生成器

    iex> l = for n <- [1, 2, 4], do: n*n

    [1, 4, 16]

    iex> l

    [1, 4, 16]

  2. 过滤器

    iex> require Integer

    iex> for n <- 1..4, Integer.is_odd(n), do: n*n

    [1, 9]

  3. 二进制转化为元组

    iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>

    <<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}]

  4. into

    • 删除空格

      iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>

      "helloworld"

sigils(魔法印)

sigils 也就是对已有的变量或者常量做一些标记,使之变为其他的东西。

sigils 的目的就是提高 elixir 语言的扩展性。

  1. 正则表达式中的使用

    iex> regex = ~r/foo|bar/

    ~r/foo|bar/

    iex> "foo" =~ regex

    true

    iex> "bat" =~ regex

    false

  2. 表示字符串,字符以及列表的示例

    iex> ~s(this is a string with "quotes")

    "this is a string with \"quotes\""

    iex> ~c(this is a string with "quotes")

    'this is a string with "quotes"'

    iex> ~w(foo bar bat)

    ["foo", "bar", "bat"]

    ~w 还可以加入其他的修饰符(比如:c, s, a 分别代表字符列表,字符串,原子)

    iex> ~w(foo bar bat)a

    [:foo, :bar, :bat]

  3. 自定义 sigils

    iex> defmodule MySigils do

    ...> def sigil_i(string, []), do: string <> "add_sigil"

    ...> end

    iex> import MySigils

    iex> ~i("123")

分享给朋友:
您可能感兴趣的文章:
随机阅读: