zoukankan      html  css  js  c++  java
  • 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)
    0
    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")
      
  • 相关阅读:
    SERU最佳需求分析方法
    需求规格说明书(Volere版)
    开发设计模式之设计六大原则
    清晰、高效、一致、美观 – 关于设计原则的优先级排序
    一个案例,三个角色,简单说下B端产品的权限设计
    如何从0到1打造一个完美的业务系统?
    MarkdownPad2.5 注册码
    WEBSTORM快捷键
    jQuery
    BOM与DOM
  • 原文地址:https://www.cnblogs.com/wang_yb/p/5333019.html
Copyright © 2011-2022 走看看