Elixir 开发小技巧 持续更新 (Updated at: 2016-04-18)

Map 的嵌套匹配

添加时间: 2016-04-30

def background_check(%{manager: employee} = company) do
  %{name: full_name} = employee
  from(c in Criminals, where: c.full_name == ^full_name, select: c)
  |> Repo.one
  |> case do
    nil -> congratulate(employee)
    criminal -> notify(company)
  end
end
  • 对参数 company 进行匹配, 析构出其中的 manager 字段

  • manager 再次析构出其中的 name 字段

  • 然后查询 Criminals 表进行对比

在单个表达式中一次匹配

def check_company(%{manager: %{name: full_name} = employee} = company) do
  from(c in Criminals, where: c.full_name == ^full_name, select: c)
  |> Repo.one()
  |> case do
    nil -> congratulate(employee)
    criminal -> notify(company)
  end
end

Elixir单元测试超时

添加时间: 2016-04-18

** (ExUnit.TimeoutError) test timed out after 60000ms. You can change the timeout:
     
       1. per test by setting "@tag timeout: x"
       2. per case by setting "@moduletag timeout: x"
       3. globally via "ExUnit.start(timeout: x)" configuration
       4. or set it to infinity per run by calling "mix test --trace"
          (useful when using IEx.pry)
     
     Timeouts are given as integers in milliseconds.

上面是一个超时的错误提示, 通过上面的描述, 我们可以很清楚的了解如何设置单元测试的超时, 对于需要长时间运行的测试, 请把timeout设置的更大一点.

上面的说明标号分别为

  • 单独设置一个测试@tag timeout: x

  • 整个测试用例@moduletag timeout: x

  • 全局设置ExUnit.start(timeout: x)

  • 第四个不常见, 有空再研究一下.

获取和替换进程状态

添加时间: 2016-04-18

进程状态可以在任何时候获取和替换, 一般用于热更的时候.

# 创建一个GenServer模块
iex(1)> defmodule Test do
...(1)>   use GenServer
...(1)> end
{:module, Test,
 <<70, 79, 82, 49, 0, 0, 10, 40, 66, 69, 65, 77, 69,
120, 68, 99, 0, 0, 2, 60, 131, 104, 2, 100, 0, 14, 
101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>,
:ok} 

# 启动进程 
iex(2)> GenServer.start_link Test, %{name: "developerworks"}, name: :server
{:ok, #PID<0.72.0>}

# 获取进程状态   
iex(3)> :sys.get_state :server
%{name: "developerworks"}

# 替换状态
iex(4)> :sys.replace_state :server, fn(state)-> %{name: "new_state"} end      
%{name: "new_state"}

# 新状态
iex(5)> :sys.get_state :server
%{name: "new_state"}

http://www.hoterran.info/erlang-otp-sys-sourcecode
http://stackoverflow.com/questions/1840717/achieving-code-swapping-in-erlangs-gen-server

布尔运算需要注意的地方

||,&&! 两边支持任何数据类型
and, or, not 第一个参数必须为布尔类型

进制数的表示

添加时间: 2016-04-06

二进制: 前缀0b, 例如:

0b100000000 # 256

八进制: 前缀0o, 例如:

0o400 # 256

十六进制: 前缀0x, 例如:

0xFF # 255

关键字列表转换为Map

添加时间: 2016-04-05

keywords = [a: 1, b: 2]
Enum.into(keywords, %{})
iex(1)> keywords = [a: 1, b: 2] 
[a: 1, b: 2]
iex(2)> Enum.into(keywords, %{})
%{a: 1, b: 2}
iex(3)> map = Enum.into(keywords, %{})
%{a: 1, b: 2}
iex(4)> map[:a]
1
iex(5)> map.a  
1

# 动态访问
iex(6)> map[:c]
nil
# 严格语法
iex(7)> map.c  
** (KeyError) key :c not found in: %{a: 1, b: 2}

如果Key不存在, 严格语法抛出KeyError异常, 动态访问方式返回nil, 可以参考Jose Valim的这篇文章Writing assertive code with Elixir

.ex用于编译, .exs用于解释.

添加时间: 2016-04-05

对于ExUnit测试, 用.exs文件, 因此不需要在每次修改的时候重新编译. 如果编写脚本或测试, 使用.exs文件, 否则使用.ex后缀.

解释代码需要消耗更多的时间来执行(解析, tokenize等), 但不要求编译. 如果灵活性比执行速度更重要, 使用.exs后缀, 否则使用.ex.

IEx的历史记录

添加时间: 2016-04-05

Elixir 的IEx默认是不带历史命令支持的, 退出Erlang VM后,之前的命令全部么有了, 要跨多个IEx Session保存命令历史, 可以使用下面这个开源的工具:

git clone https://github.com/ferd/erlang-history.git
cd erlang-history
make install

安装完成后启动IEx就可以使用了.

这里还有一篇文章 Erlang 和 Elixir shell 历史记录设置

通过结构来创建新的类型

添加时间: 2016-04-02

创建一个工具模块包括常用的辅助函数

defmodule Util do
  @mdoc """
  工具模块
  """

  @doc """
  用于获取UNIX时间戳
  """
  def get_unixtime() do
    {megas, secs, _micros} = :os.timestamp
    megas * 1000 * 1000 + secs
  end
end

定义一个User类型模块

defmodule User do
  @mdoc """
  用户类型
  """

  defstruct username: "", tel: "", email: "", created_at: Util.get_unixtime
  @type t :: %__MODULE__{
    username: String.t,
    tel: String.t,
    email: String.t,
    created_at: Integer.t
  }
end

测试代码

# 测试
require Logger
defmodule UserTest do
  def main() do
    # 创建一个新的User结构类型
    user = %User{}
    # 打印
    Logger.info "#{inspect user}"
  end
end

# 调用
UserTest.main

编译选项

打开 warnings_as_errors 编译器选项, 让你的代码没有警告, 在config.exs中设置编译选项

Code.compiler_options([
  debug_info: true,
  docs: true,
  ignore_module_conflict: false,
  warnings_as_errors: true
])

小心使用Enum.map/2

在集合过大的时候不要直接使用该函数, 请使用 Stream 模块, 要获取结果的时候再用 Enum 模块中的函数计算最终结果.

枚举类型和流

二进制速构

http://elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#for/1

pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
rgb = for <> do
  {r, g, b}
end
IO.inspect rgb

字符过滤

for <>, c != ?\s, into: "", do: <>
"helloworld"

函数的动态定义

通过函数名称列表, 动态的创建函数的定义.

这种技巧通常用于批量的定义类似的函数一般参数数量相同, 函数名称有规律的重复, 多用于开发API模块

defmodule Reader do
  [:doc, :voice, :video] |> Enum.each(fn(name)->
    def unquote(:"read_#{name}")(id) do
      IO.inspect("Call function #{unquote(name)}(#{id})")
    end
  end)
end

Reader.read_doc 1     # Call function doc(1)
Reader.read_video 2   # Call function doc(2)
Reader.read_voice 3   # Call function doc(3)

函数的动态调用

使用apply/3动态调用模块

defmodule RiakPoolEx.Handlers.MediaHandler do
  @mdoc """
  You can call functions in the module liks this:

  Exmaples:

    alias RiakPoolEx.QueryHandler.MediaHandler
    MediaHandler.read_doc(1)
    MediaHandler.read_voice(2)
    MediaHandler.read_video(3)
    MediaHandler.read_picture(4)
  """
  alias RiakPoolEx.QueryHandler
  [:doc, :voice, :video, :picture] |> Enum.each(fn(name)->
    def unquote(:"read_#{name}")(id) do
      apply(RiakPoolEx.QueryHandler, unquote(:"read_#{name}", [id]))
    end
  end)
end

你可能感兴趣的:(elixir)