Elixir 開發小技巧 持續更新 (Updated at: 2016-04-18)

Map 的嵌套匹配

添加時間: 2016-04-30html

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 字段git

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

  • 而後查詢 Criminals 表進行對比shell

在單個表達式中一次匹配app

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-18ide

** (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 <<r::8, g::8, b::8 <- pixels >> do
  {r, g, b}
end
IO.inspect rgb

字符過濾

for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"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
相關文章
相關標籤/搜索