2018/4/16

Elixir4 StringsAndBinaries


String Literals


elixir 有兩種 string: single-quoted, double-quoted


  • strings 是使用 UTF-8 encoding
  • escape characters:


    \a BEL (0x07) \b BS (0x08) \d DEL (0x7f)
    \e ESC (0x1b) \f FF (0x0c) \n NL (0x0a)
    \r CR (0x0d) \s SP (0x20) \t TAB (0x09)
    \v VT (0x0b) \uhhh 1–6 hex digits \xhh 2 hex digits
  • 使用 #{...} 語法,處理 string interpolaton


    iex(1)> name="You"
    "You"
    iex(2)> "Hello, #{String.capitalize name}!"
    "Hello, You!"
  • 支援 heredocs """ ... """,會自動去掉每一行文字最前面的 tab/space


    iex(11)> IO.write """
    ...(11)>         my
    ...(11)>         string
    ...(11)>         """
    my
    string
    :ok
    
    iex(14)> IO.write "
    ...(14)>         my
    ...(14)>         string
    ...(14)>         "
    
            my
            string
            :ok

sigil


以下為 sigil types


~C 沒有 escaping or interpolation
~c 以 '' string 的方式 escaped and interpolated
~D 日期格式 yyyy-mm-dd
~N 原始的 DateTime 格式 yyyy-mm-dd hh:mm:ss[.ddd]
~R 沒有 escaping or interpolation 的 regular expression
~r 有 escaped and interpolated 的 regular expression
~S 沒有 escaping or interpolation 的 string
~s 以 "" string 的方式  escaped and interpolated
~T 時間格式 hh:mm:ss[.dddd]
~W whitespace-delimited words 沒有 no escaping or interpolation
~w whitespace-delimited words 有 escaping and interpolation

iex(1)> ~C[1\n2#{1+2}]
'1\\n2\#{1+2}'
iex(2)> ~c"1\n2#{1+2}"
'1\n23'
iex(3)> ~S[1\n2#{1+2}]
"1\\n2\#{1+2}"
iex(4)> ~s/1\n2#{1+2}/
"1\n23"
iex(5)> ~W[the c#{'a'}t sat on the mat]
["the", "c\#{'a'}t", "sat", "on", "the", "mat"]
iex(6)> ~w[the c#{'a'}t sat on the mat]
["the", "cat", "sat", "on", "the", "mat"]
iex(7)> ~D<1999-12-31>
~D[1999-12-31]
iex(8)> ~T[12:34:56]
~T[12:34:56]
iex(9)> ~N{1999-12-31 23:59:59}
~N[1999-12-31 23:59:59]

~W ~w 有增加 a, c, s 的選項,分別傳回 atoms, list, string of chars


iex(11)> ~w[the c#{'a'}t sat on the mat]a
[:the, :cat, :sat, :on, :the, :mat]
iex(12)> ~w[the c#{'a'}t sat on the mat]c
['the', 'cat', 'sat', 'on', 'the', 'mat']
iex(13)> ~w[the c#{'a'}t sat on the mat]s
["the", "cat", "sat", "on", "the", "mat"]

也可使用 """


iex(15)> ~w"""
...(15)> test
...(15)> 111
...(15)> """
["test", "111"]
iex(16)> ~r"""
...(16)> hello
...(16)> """i
~r/hello\n/i

single-quoted strings: lists of character codes


single-quoted strings 是 list of integer values,每個值都代表 string 的 codepoint。


'string' 如果裡面儲存的資料是可以列印的,會直接列印該文字,但其實內部是儲存為 int list


iex(1)> str = 'wombat'
'wombat'
iex(2)> is_list str
true
iex(3)> length str
6
iex(4)> Enum.reverse str
'tabmow'
iex(5)> [ 67, 65, 84 ]
'CAT'
iex(6)> :io.format "~w~n", [ str ]
[119,111,109,98,97,116]
:ok
iex(7)> List.to_tuple str
{119, 111, 109, 98, 97, 116}
iex(8)> str ++ [0]
[119, 111, 109, 98, 97, 116, 0]

iex(21)> '∂x/∂y'
[8706, 120, 47, 8706, 121]
iex(22)> 'pole' ++ 'vault'
'polevault'
iex(23)> 'pole' -- 'vault'
'poe'
iex(24)> List.zip [ 'abc', '123' ]
[{97, 49}, {98, 50}, {99, 51}]
iex(25)> [ head | tail ] = 'cat'
'cat'
iex(26)> head
99
iex(27)> tail
'at'
iex(28)> [ head | tail ]
'cat'



defmodule Parse do

  def number([ ?- | tail ]), do: _number_digits(tail, 0) * -1
  def number([ ?+ | tail ]), do: _number_digits(tail, 0)
  def number(str),           do: _number_digits(str,  0)

  defp _number_digits([], value), do: value
  defp _number_digits([ digit | tail ], value)
  when digit in '0123456789' do
    _number_digits(tail, value*10 + digit - ?0)
  end
  defp _number_digits([ non_digit | _ ], _) do
    raise "Invalid digit '#{[non_digit]}'"
  end
end

iex(1)> Parse.number('123')
123
iex(2)> Parse.number('+123')
123
iex(3)> Parse.number('-123')
-123
iex(4)> Parse.number('+a')
** (RuntimeError) Invalid digit 'a'
    parse.exs:13: Parse._number_digits/2

Binaries


<< term,… >>

iex(1)> b = << 1, 2, 3 >>
<<1, 2, 3>>
iex(2)> byte_size b
3
iex(3)> bit_size b
24

iex(5)> b = << 1::size(2), 1::size(3) >>
<<9::size(5)>>
iex(6)> byte_size b
1
iex(7)> bit_size b
5

可儲存 integers, floats, binaries


iex(13)> int = << 1 >>
<<1>>
iex(14)> float = << 2.5 :: float >>
<<64, 4, 0, 0, 0, 0, 0, 0>>
iex(15)> mix = << int :: binary, float :: binary >>
<<1, 64, 4, 0, 0, 0, 0, 0, 0>>

IEEE 754 float has a sign bit, 11 bits of exponent, and 52 bits of mantissa,可直接用 pattern matching 的方式拆解 float


iex(22)> << sign::size(1), exp::size(11), mantissa::size(52) >> = << 3.14159::float >>
<<64, 9, 33, 249, 240, 27, 134, 110>>
iex(23)> (1 + mantissa / :math.pow(2, 52)) * :math.pow(2, exp-1023)
3.14159



Double-Quoted Strings 就等同於 Binaries,但字串長度並不等於 bytes size,因為 string 是使用 UTF-8 encoding


iex(1)> dqs = "∂x/∂y"
"∂x/∂y"
iex(2)> String.length dqs
5
iex(3)> byte_size dqs
9
iex(4)> String.at(dqs, 0)
"∂"
iex(5)> String.codepoints(dqs)
["∂", "x", "/", "∂", "y"]
iex(6)> String.split(dqs, "/")
["∂x", "∂y"]

使用 string(binary) 的 elixir library


# at(str, offset)
#  在某個位置的 char

iex> String.at("∂og", 0)
"∂"
iex> String.at("∂og", -1)
"g"

# capitalize(str)
#  轉成小寫, 首字元大寫
iex> String.capitalize "école"
"École"
iex> String.capitalize "ÎÎÎÎÎ"
"Îîîîî"


# codepoints(str)
#  字串的 codepoints
iex> String.codepoints("José's ∂øg")
["J", "o", "s", "é", "'", "s", " ", "∂", "ø", "g"]

# downcase(str)
#  轉小寫
iex> String.downcase "ØRSteD"
"ørsted"

# duplicate(str, n)
#  重複 n 次
iex> String.duplicate "Ho! ", 3
"Ho! Ho! Ho! "


# ends_with?(str, suffix | [ suffixes ])
#  是否以某一個 suffixes 結束
iex> String.ends_with? "string", ["elix", "stri", "ring"]
true


# first(str)
#  第一個 char
iex> String.first "∂og"
"∂"


# graphemes(str)
#  與 codepoints 不同
iex> String.codepoints "noe\u0308l"
["n", "o", "e", "¨", "l"]
iex> String.graphemes "noe\u0308l"
["n", "o", "ë", "l"]

# jaro_distance
#  以 0~1 float 代表兩個 string 的差異
iex> String.jaro_distance("jonathan", "jonathon")
0.9166666666666666
iex> String.jaro_distance("josé", "john")
0.6666666666666666


# last(str)
#  最後一個字元
iex> String.last "∂og"
"g"

# length(str)
#  字串長度, 用字元計算
Returns the number of graphemes in str.
iex> String.length "∂x/∂y"
5

# myers_difference
#  由一個 string 轉換成另一個的過程
iex> String.myers_difference("banana", "panama")
[del: "b", ins: "p", eq: "ana", del: "n", ins: "m", eq: "a"]


# next_codepoint(str)
#  分割第一個字元跟剩下的字串

defmodule MyString do
    def each(str, func), do: _each(String.next_codepoint(str), func)
    defp _each({codepoint, rest}, func) do
        func.(codepoint)
            _each(String.next_codepoint(rest), func)
    end
    defp _each(nil, _), do: []
end
MyString.each "∂og", fn c -> IO.puts c end

# next_grapheme(str)
#  跟 next_codepoint 一樣,但 return graphemes,以 :no_grapheme 結束


# pad_leading(str, new_length, padding \\ 32)
#  以 str 結束,至少 new_length 這麼長的字串,字串前面的以 padding 補上
iex> String.pad_leading("cat", 5, ?>)
">>cat"


# pad_trailing(str, new_length, padding \\ " ")
#  同 pad_leading 但 padding 放在字串的後面
iex> String.pad_trailing("cat", 5)
"cat "


# printable?(str)
#  是否為 printable chars
iex> String.printable? "José"
true
iex> String.printable? "\x{0000} a null"
false


# replace(str, pattern, replacement, options \\ [global: true, insert_replaced: nil])
#  替換字串, :global 代表每一次都替換, :insert_replaced 代表要 insert 在後面 offset 個字元
iex> String.replace "the cat on the mat", "at", "AT"
"the cAT on the mAT"
iex> String.replace "the cat on the mat", "at", "AT", global: false
"the cAT on the mat"
iex> String.replace "the cat on the mat", "at", "AT", insert_replaced: 0
"the catAT on the matAT"
iex> String.replace "the cat on the mat", "at", "AT", insert_replaced: [0,2]
"the catATat on the matATat"


# reverse(str)
iex> String.reverse "pupils"
"slipup"
iex> String.reverse "∑ƒ÷∂"
"∂÷ƒ∑"

# slice(str, offset, len)
#  由 offset 位置開始取 len 個 chars
iex> String.slice "the cat on the mat", 4, 3
"cat"
iex> String.slice "the cat on the mat", -3, 3
"mat"

# split(str, pattern \\ nil, options \\ [global: true])
#  以 pattern 為分割點(預設為 space),切割 string
iex> String.split " the cat on the mat "
["the", "cat", "on", "the", "mat"]
iex> String.split "the cat on the mat", "t"
["", "he ca", " on ", "he ma", ""]
iex> String.split "the cat on the mat", ~r{[ae]}
["th", " c", "t on th", " m", "t"]
iex> String.split "the cat on the mat", ~r{[ae]}, parts: 2
["th", " cat on the mat"]


# starts_with?(str, prefix | [ prefixes ])
#  是否以 prefix 開頭
iex> String.starts_with? "string", ["elix", "stri", "ring"]
true



# trim(str)
#  去掉前後的 whitespaces
iex> String.trim "\t Hello \r\n"
"Hello

# trim(str, character)
#  去掉前後的 character
iex> String.trim "!!!SALE!!!", "!"
"SALE"

# trim_leading(str)
iex> String.trim_leading "\t\f Hello\t\n"
"Hello\t\n"

# trim_leading(str, character)
iex> String.trim_leading "!!!SALE!!!", "!"
"SALE!!!"

# trim_trailing(str)
iex> String.trim_trailing(" line \r\n")
" line"

# trim_trailing(str, character)
iex> String.trim_trailing "!!!SALE!!!", "!"
"!!!SALE"

# upcase(str)
iex> String.upcase "José Ørstüd"
"JOSÉ ØRSTÜD"

# valid?(str)
#  判斷是否為單一 char
iex> String.valid? "∂"
true
iex> String.valid? "∂og"
false

Binaries and Pattern Matching


可用在 Binary 的 type: binary, bits, bitstring, bytes, float, integer, utf8, utf16, and utf32


  • size(n): size in bits
  • signed/unsigned
  • endianness: big/little/native

<< length::unsigned-integer-size(12), flags::bitstring-size(4) >> = data



以 Binary 方式處理 String


defmodule Utf8 do
  def each(str, func) when is_binary(str), do: _each(str, func)

  # 用 binary pattern matching 以一個 utf8 字元為 head
  defp _each(<< head :: utf8, tail :: binary >>, func) do
    func.(head)
    _each(tail, func)
  end

  defp _each(<<>>, _func), do: []
end

Utf8.each "∂og", fn char -> IO.puts char end

Control Flow


elixir 有提供這些語法,但應該盡量使用 pattern matching 的方式處理


if, unless


iex(1)> if 1 == 1, do: "true part", else: "false part"
"true part"
iex(2)> if 2 == 1, do: "true part", else: "false part"
"false part"

iex(4)> if 1==1 do
...(4)>   "true"
...(4)> else
...(4)>   "false"
...(4)> end
"true"

iex(6)> unless 1 == 1, do: "error", else: "OK"
"OK"
iex(8)> unless 1 == 2, do: "OK", else: "error"
"OK"
iex(10)> unless 1 == 2 do
...(10)>   "OK"
...(10)> else
...(10)>   "error"
...(10)> end
"OK"

cond


FizzBuzz: 如果是 3 的倍數就印 Fizz,如果是 5 的倍數就印 Buzz,如果同時是 3 和 5 的倍數就印 FizzBuzz


defmodule FizzBuzz do

  def upto(n) when n > 0, do: _upto(1, n, [])

  defp _upto(_current, 0, result),  do: Enum.reverse result

  defp _upto(current, left, result) do
    next_answer =
      cond do
        rem(current, 3) == 0 and rem(current, 5) == 0 ->
          "FizzBuzz"
        rem(current, 3) == 0 ->
          "Fizz"
        rem(current, 5) == 0 ->
          "Buzz"
        true ->
          current
      end
    _upto(current+1, left-1, [ next_answer | result ])
  end
end

因為處理順序的關係,必須要在最後 Enum.reverse result


iex(1)> FizzBuzz.upto(10)
[1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"]



所以就反過來,從 n 開始做


defmodule FizzBuzz do

  def upto(n) when n > 0, do: _downto(n, [])

  defp _downto(0, result),  do: result

  defp _downto(current, result) do
    next_answer =
      cond do
        rem(current, 3) == 0 and rem(current, 5) == 0 ->
          "FizzBuzz"
        rem(current, 3) == 0 ->
          "Fizz"
        rem(current, 5) == 0 ->
          "Buzz"
        true ->
          current
      end
    _downto(current-1, [ next_answer | result ])
  end
end



改用 Enum.map 重複呼叫 fizzbuzz


defmodule FizzBuzz do

  def upto(n) when n > 0 do
    1..n |> Enum.map(&fizzbuzz/1)
  end

  defp fizzbuzz(n) do
    cond do
      rem(n, 3) == 0 and rem(n, 5) == 0 ->
        "FizzBuzz"
      rem(n, 3) == 0 ->
        "Fizz"
      rem(n, 5) == 0 ->
        "Buzz"
      true ->
        n
    end
  end
end



最好的方式是用 pattern matching 的方式


defmodule FizzBuzz do

  def upto(n) when n > 0 do
    1..n |> Enum.map(&fizzbuzz/1)
  end

  defp fizzbuzz(n) when rem(n, 3) == 0 and rem(n, 5) == 0, do: "FizzBuzz"
  defp fizzbuzz(n) when rem(n, 3) == 0, do: "Fizz"
  defp fizzbuzz(n) when rem(n, 5) == 0, do: "Buzz"
  defp fizzbuzz(n), do: n

end

case


File.open 會遇到兩種 return


case File.open("case.ex")  do

{ :ok, file } ->
  IO.puts "First line: #{IO.read(file, :line)}"

{ :error, reason } ->
  IO.puts "Failed to open file: #{reason}"

end

加上 guard clause


defrecord Person, name: "", age: 0

defmodule Bouncer do

  dave = Person.new name: "You", age: 27

  case dave do

    record = Person[age: age] when is_number(age) and age >= 21 ->
      IO.puts "You are cleared to enter the Foo Bar, #{record.name}"

    _ ->
      IO.puts "Sorry, no admission"

  end
end

Expcetions


iex(1)> raise "Give Up"
** (RuntimeError) Give Up

iex(1)> raise RuntimeError
** (RuntimeError) runtime error

iex(1)> raise RuntimeError, message: "override message"
** (RuntimeError) override message

錯誤時 raise Exception


case File.open("config_file") do
{:ok, file} ->
  process(file)
{:error, message} ->
  raise "Failed to open config file: #{message}"
end

或是由 pattern matching 的方式處理


{ :ok, file } = File.open("config_file")
process(file)

References


Programming Elixir

2018/4/9

Elixir 3 Lists_Maps

Lists




Heads and Tails


[] 為 empty list


[ 3 | [] ] 就等於 [3]


[ 1 | [ 2 | [ 3 | [] ] ] ] 就等於 [1, 2, 3]


iex(2)> [ head | tail ] = [ 1, 2, 3 ]
[1, 2, 3]
iex(3)> head
1
iex(4)> tail
[2, 3]

利用 Head Tail 不斷 recursive 呼叫 func,沒有使用的參數 _head 前面加上底線,就不會出現編譯的 warning。


defmodule MyList do
  def len([]), do: 0
  def len([_head|tail]), do: 1 + len(tail)
end

運算過程為


len([11,12,13,14,15])
= 1 + len([12,13,14,15])
= 1 + 1 + len([13,14,15])
= 1 + 1 + 1 + len([14,15])
= 1 + 1 + 1 + 1 + len([15])
= 1 + 1 + 1 + 1 + 1 + len([])
= 1 + 1 + 1 + 1 + 1 + 0
= 5



Map function for list


defmodule MyList do
  def map([], _func),             do: []
  def map([ head | tail ], func), do: [ func.(head) | map(tail, func) ]
end

使用 map


iex(1)> MyList.map [1,2,3,4], fn (n) -> n*n end
[1, 4, 9, 16]

iex(2)> MyList.map [1,2,3,4], fn (n) -> n+1 end
[2, 3, 4, 5]
iex(3)> MyList.map [1,2,3,4], &(&1 + 1)
[2, 3, 4, 5]



在 recursion 過程中,持續保留結果


  • sum([]) -> 0
  • sum([ head |tail ]) -> «total» + sum(tail)

defmodule MyList do
  def sum([], total),              do: total
  def sum([ head | tail ], total), do: sum(tail, head+total)
end

MyList.sum([1,2,3,4,5], 0)

剛剛的寫法,呼叫時要先傳入初始化的 total,也就是 0,改寫為以下做法,隱藏實際的 _sum 演算法,另外定義一個新的 public function,只對外開放可使用這個 function


defmodule MyList do

  def sum(list), do: _sum(list, 0)

  # private methods
  defp _sum([], total),              do: total
  defp _sum([ head | tail ], total), do: _sum(tail, head+total)
end

list 的 reduce function 定義


reduce(collection, initial_value, fun)

  • reduce([], value, _) -> value
  • reduce([ head |tail ], value, fun) -> reduce(tail, fun.(head, value), fun)

defmodule MyList do
  def reduce([], value, _) do
    value
  end
  def reduce([head | tail], value, fun) do
    reduce(tail, fun.(head, value), fun)
  end
end

使用 reduce.exs


iex(1)> MyList.reduce([1,2,3,4,5], 0, &(&1 + &2))
15
iex(2)> MyList.reduce([1,2,3,4,5], 1, &(&1 * &2))
120



More complex List Patterns


defmodule Swapper do

  def swap([]), do: []
  def swap([ a, b | tail ]), do: [ b, a | swap(tail) ]
  def swap([_]), do: raise "Can't swap a list with an odd number of elements"

end

執行結果


iex(1)> Swapper.swap [1,2,3,4,5,6]
[2, 1, 4, 3, 6, 5]



Lists of Lists


defmodule WeatherHistory do

  def test_data do
    [
     [1366225622, 26, 15, 0.125],
     [1366225622, 27, 15, 0.45],
     [1366225622, 28, 21, 0.25],
     [1366229222, 26, 19, 0.081],
     [1366229222, 27, 17, 0.468],
     [1366229222, 28, 15, 0.60],
     [1366232822, 26, 22, 0.095],
     [1366232822, 27, 21, 0.05],
     [1366232822, 28, 24, 0.03],
     [1366236422, 26, 17, 0.025]
    ]
  end


  # standard recurse until the list is empty stanza

  def for_location_27([]), do: []
  def for_location_27([ [time, 27, temp, rain ] | tail]) do
    [ [time, 27, temp, rain] | for_location_27(tail) ]
  end
  def for_location_27([ _ | tail]), do: for_location_27(tail)

end

iex(1)> import WeatherHistory
WeatherHistory
iex(2)> for_location_27(test_data)
[[1366225622, 27, 15, 0.45], [1366229222, 27, 17, 0.468],
 [1366232822, 27, 21, 0.05]]

改進 weather2.exs


defmodule WeatherHistory do

  def test_data do
    [
     [1366225622, 26, 15, 0.125],
     [1366225622, 27, 15, 0.45],
     [1366225622, 28, 21, 0.25],
     [1366229222, 26, 19, 0.081],
     [1366229222, 27, 17, 0.468],
     [1366229222, 28, 15, 0.60],
     [1366232822, 26, 22, 0.095],
     [1366232822, 27, 21, 0.05],
     [1366232822, 28, 24, 0.03],
     [1366236422, 26, 17, 0.025]
    ]
  end

  def for_location([], _target_loc), do: []

  def for_location([ [time, target_loc, temp, rain ] | tail], target_loc) do
    [ [time, target_loc, temp, rain] | for_location(tail, target_loc) ]
  end

  def for_location([ _ | tail], target_loc), do: for_location(tail, target_loc)

end

weather3.exs


defmodule WeatherHistory do

  def test_data do
    [
     [1366225622, 26, 15, 0.125],
     [1366225622, 27, 15, 0.45],
     [1366225622, 28, 21, 0.25],
     [1366229222, 26, 19, 0.081],
     [1366229222, 27, 17, 0.468],
     [1366229222, 28, 15, 0.60],
     [1366232822, 26, 22, 0.095],
     [1366232822, 27, 21, 0.05],
     [1366232822, 28, 24, 0.03],
     [1366236422, 26, 17, 0.025]
    ]
  end

  def for_location([], target_loc), do: []

  def for_location([ head = [_, target_loc, _, _ ] | tail], target_loc) do
    [ head | for_location(tail, target_loc) ]
  end

  def for_location([ _ | tail], target_loc), do: for_location(tail, target_loc)

end



functions in List Module


#
# Concatenate lists
#
iex> [1,2,3] ++ [4,5,6]
[1, 2, 3, 4, 5, 6]
#
# Flatten
#
iex> List.flatten([[[1], 2], [[[3]]]])
[1, 2, 3]
#
# Folding (like reduce, but can choose direction)
#
iex> List.foldl([1,2,3], "", fn value, acc -> "#{value}(#{acc})" end)
"3(2(1()))"
iex> List.foldr([1,2,3], "", fn value, acc -> "#{value}(#{acc})" end)
"1(2(3()))"
#
# Updating in the middle (not a cheap operation)
#
iex> list = [ 1, 2, 3 ]
[ 1, 2, 3 ]
iex> List.replace_at(list, 2, "buckle my shoe")
[1, 2, "buckle my shoe"]
#
# Accessing tuples within lists
#
iex> kw = [{:name, "Dave"}, {:likes, "Programming"}, {:where, "Dallas", "TX"}]
[{:name, "Dave"}, {:likes, "Programming"}, {:where, "Dallas", "TX"}]
iex> List.keyfind(kw, "Dallas", 1)
{:where, "Dallas", "TX"}
iex> List.keyfind(kw, "TX", 2)
{:where, "Dallas", "TX"}
iex> List.keyfind(kw, "TX", 1)
nil
iex> List.keyfind(kw, "TX", 1, "No city called TX")
"No city called TX"
iex> kw = List.keydelete(kw, "TX", 2)
[name: "Dave", likes: "Programming"]
iex> kw = List.keyreplace(kw, :name, 0, {:first_name, "Dave"})
[first_name: "Dave", likes: "Programming"]

Dictionary: Map, Keyword Lists, Sets, Structs


dictionary: a data type that associates keys with values




選用 Maps 或是 Keyword Lists?


依序考慮下列問題


  1. 需不需要使用 pattern-matching for contents? 例如 matching 有 :name 為 key 的 dictionary。
    如果有需要就使用 map

  2. 需不需要用同樣的 key 儲存不同的 value? 如果有就使用 Keyword module

  3. 儲存的元素要不要保留順序? 如果有就使用 Keyword module

  4. 最後,就直接用 map


Keyword Lists


defmodule Canvas do

  @defaults [ fg: "black", bg: "white", font: "Merriweather" ]

  def draw_text(text, options \\ []) do
    options = Keyword.merge(@defaults, options)
    IO.puts "Drawing text #{inspect(text)}"
    IO.puts "Foreground:  #{options[:fg]}"
    IO.puts "Background:  #{Keyword.get(options, :bg)}"
    IO.puts "Font:        #{Keyword.get(options, :font)}"
    IO.puts "Pattern:     #{Keyword.get(options, :pattern, "solid")}"
    IO.puts "Style:       #{inspect Keyword.get_values(options, :style)}"
  end

end

Canvas.draw_text("hello", fg: "red", style: "italic", style: "bold")

以 key 使用 map 裡面的value: kwlist[key]


Keyword List 可使用 KeywordEnum modules 的所有 functions


Maps


iex(1)> map = %{ name: "You", likes: "Programming", where: "Home" }
%{likes: "Programming", name: "You", where: "Home"}
iex(2)> Map.keys map
[:likes, :name, :where]
iex(3)> Map.values map
["Programming", "You", "Home"]
iex(4)> map[:name]
"You"
iex(5)> map.name
"You"
iex(6)> map1 = Map.drop map, [:where, :likes]
%{name: "You"}
iex(7)> map2 = Map.put map, :also_likes, "Ruby"
%{also_likes: "Ruby", likes: "Programming", name: "You", where: "Home"}
iex(8)> Map.keys map2
[:also_likes, :likes, :name, :where]
iex(9)> Map.has_key? map1, :where
false
iex(10)> { value, updated_map } = Map.pop map2, :also_likes
{"Ruby", %{likes: "Programming", name: "You", where: "Home"}}
iex(11)> Map.equal? map, updated_map
true



Pattern Matching and Updating Maps


利用 pattern matching,在 match 同時, binding 變數


iex(15)> person = %{ name: "You", height: 1.88 }
%{height: 1.88, name: "You"}

# 是否有存在key  :name
iex(16)> %{ name: a_name } = person
%{height: 1.88, name: "You"}
iex(17)> a_name
"You"
iex(18)> %{ name: _, height: _ } = person
%{height: 1.88, name: "You"}

## :name 為 "You" ?
iex(19)> %{ name: "You" } = person
%{height: 1.88, name: "You"}

## not match error
iex(20)> %{ name: _, weight: _ } = person
** (MatchError) no match of right hand side value: %{height: 1.88, name: "You"}

iterate 所有 key-value,取得符合某個條件(height > 1.5) 的 map


people = [
  %{ name: "Grumpy",    height: 1.24 },
  %{ name: "Dave",      height: 1.88 },
  %{ name: "Dopey",     height: 1.32 },
  %{ name: "Shaquille", height: 2.16 },
  %{ name: "Sneezy",    height: 1.28 }
]

IO.inspect(for person = %{ height: height } <- people, height > 1.5, do: person)

people = [
  %{ name: "Grumpy",    height: 1.24 },
  %{ name: "Dave",      height: 1.88 },
  %{ name: "Dopey",     height: 1.32 },
  %{ name: "Shaquille", height: 2.16 },
  %{ name: "Sneezy",    height: 1.28 }
]

defmodule HotelRoom do

  def book(%{name: name, height: height})
  when height > 1.9 do
    IO.puts "Need extra long bed for #{name}"
  end

  def book(%{name: name, height: height})
  when height < 1.3 do
    IO.puts "Need low shower controls for #{name}"
  end

  def book(person) do
    IO.puts "Need regular bed for #{person.name}"
  end

end

people |> Enum.each(&HotelRoom.book/1)

結果為


Need low shower controls for Grumpy
Need regular bed for Dave
Need regular bed for Dopey
Need extra long bed for Shaquille
Need low shower controls for Sneezy

不能在 pattern matching 時,同時 bind key variable


iex(1)> %{ 2 => state } = %{ 1 => :ok, 2 => :error }
%{1 => :ok, 2 => :error}
iex(2)> state
:error
iex(3)> %{ item => :ok } = %{ 1 => :ok, 2 => :error }
** (CompileError) iex:3: illegal use of variable item inside map key match, maps can only match on existing variables by using ^item
    (stdlib) lists.erl:1354: :lists.mapfoldl/3

Pattern Matching 可 match variable keys


iex(11)> data = %{ name: "You", state: "Home", likes: "Elixir" }
%{likes: "Elixir", name: "You", state: "Home"}

iex(12)> for key <- [ :name, :likes ] do
...(12)>   %{ ^key => value } = data
...(12)>   value
...(12)> end
["You", "Elixir"]



Updating Maps 最簡單的方式為


new_map = %{ old_map | key => value, … }

會先複製一份舊的 map,並將新的 key,value 替換掉


iex(16)> m = %{ a: 1, b: 2, c: 3 }
%{a: 1, b: 2, c: 3}
iex(17)> m1 = %{ m | b: "two", c: "three" }
%{a: 1, b: "two", c: "three"}
iex(18)>  m2 = %{ m1 | a: "one" }
%{a: "one", b: "two", c: "three"}

Structs


struct 就是一個 limited form of map, keys 一定要是 atoms,module 名稱就是這個 map type 的名稱


defmodule Subscriber do
  defstruct name: "", paid: false, over_18: true
end

使用 Subscriber 的方式


iex(1)> s1 = %Subscriber{}
%Subscriber{name: "", over_18: true, paid: false}
iex(2)> s2 = %Subscriber{ name: "You" }
%Subscriber{name: "You", over_18: true, paid: false}
iex(3)> s3 = %Subscriber{ name: "Mary", paid: true }
%Subscriber{name: "Mary", over_18: true, paid: true}
iex(4)>
nil
iex(5)> s3.name
"Mary"
iex(6)> %Subscriber{name: a_name} = s3
%Subscriber{name: "Mary", over_18: true, paid: true}
iex(7)> a_name
"Mary"
iex(8)>
nil
iex(9)> s4 = %Subscriber{ s3 | name: "Marie"}
%Subscriber{name: "Marie", over_18: true, paid: true}

限制一定要用 module 封裝 structs 的原因是,要在 module 裡面加上對該 structs 相關的操作 functions


iex(1)> a1 = %Attendee{name: "You", over_18: true}
%Attendee{name: "You", over_18: true, paid: false}
iex(2)> a2 = %Attendee{a1 | paid: true}
%Attendee{name: "You", over_18: true, paid: true}
iex(3)> Attendee.may_attend_after_party(a2)
true
iex(4)> Attendee.print_vip_badge(a2)
Very cheap badge for You
:ok
iex(5)> a3 = %Attendee{}
%Attendee{name: "", over_18: true, paid: false}
iex(6)> Attendee.print_vip_badge(a3)
** (RuntimeError) missing name for badge
    defstruct1.exs:21: Attendee.print_vip_badge/1



Nested Ductionary Structures


可在 value 的部分使用 dictionary



defmodule Customer do
  defstruct name: "", company: ""
end

defmodule BugReport do
  defstruct owner: %Customer{}, details: "", severity: 1
end

defmodule User do
  report = %BugReport{owner: %Customer{name: "You", company: "Company"},
                      details: "broken"}

  IO.inspect report

  report = %BugReport{ report | owner: %Customer{ report.owner | company: "PragProg" }}

  IO.inspect report

  IO.inspect update_in(report.owner.name, &("Mr. " <> &1))
end

執行結果


%BugReport{details: "broken", owner: %Customer{company: "Company", name: "You"},
 severity: 1}
%BugReport{details: "broken",
 owner: %Customer{company: "PragProg", name: "You"}, severity: 1}
%BugReport{details: "broken",
 owner: %Customer{company: "PragProg", name: "Mr. You"}, severity: 1}



Nested Accessors and Nonstructs


put_in(path, value)
put_in( data, keys, value)

update_in(path, fun)
update_in(data, keys, fun)

iex(1)> report = %{ owner: %{ name: "You", company: "Company" }, severity: 1}
%{owner: %{company: "Company", name: "You"}, severity: 1}
iex(2)> put_in(report[:owner][:company], "Com")
%{owner: %{company: "Com", name: "You"}, severity: 1}
iex(3)> update_in(report[:owner][:name], &("Mr. " <> &1))
%{owner: %{company: "Company", name: "Mr. You"}, severity: 1}



Dynamic (Runtime) Nested Accessors


get_in
put_in
update_in
get_and_update_in

ex:



nested = %{
    buttercup: %{
      actor: %{
        first: "Robin",
        last:  "Wright"
      },
      role: "princess"
    },
    westley: %{
      actor: %{
        first: "Cary",
        last:  "Ewles"     # typo!
      },
      role: "farm boy"
    }
}

IO.inspect get_in(nested, [:buttercup])
# => %{actor: %{first: "Robin", last: "Wright"}, role: "princess"}

IO.inspect get_in(nested, [:buttercup, :actor])
# => %{first: "Robin", last: "Wright"}

IO.inspect get_in(nested, [:buttercup, :actor, :first])
# => "Robin"

IO.inspect put_in(nested, [:westley, :actor, :last], "Elwes")
# => %{buttercup: %{actor: %{first: "Robin", last: "Wright"}, role: "princess"},
# =>     westley: %{actor: %{first: "Cary", last: "Elwes"}, role: "farm boy"}}

可利用 fun 當作 keys 傳入 get_in


get_in(data, keys)

authors = [
  %{ name: "José",  language: "Elixir" },
  %{ name: "Matz",  language: "Ruby" },
  %{ name: "Larry", language: "Perl" }
]

languages_with_an_r = fn (:get, collection, next_fn) ->
   for row <- collection do
     if String.contains?(row.language, "r") do
       next_fn.(row)
     end
   end
end

IO.inspect get_in(authors, [languages_with_an_r, :name]) 
#=> [ "José", nil, "Larry" ]



Access Module


提供 get 與 get_and_update_in 一些 predefined functions 當作參數


Access.all(), Access.at(1)



cast = [
  %{
    character: "Buttercup",
    actor: %{
      first: "Robin",
      last:  "Wright"
    },
    role: "princess"
  },
  %{
    character: "Westley",
    actor: %{
      first: "Cary",
      last:  "Elwes"
    },
    role: "farm boy"
  }
]

IO.inspect get_in(cast, [Access.all(), :character])
#=> ["Buttercup", "Westley"]

IO.inspect get_in(cast, [Access.at(1), :role])
#=> "farm boy"

IO.inspect get_and_update_in(cast, [Access.all(), :actor, :last],
                             fn (val) -> {val, String.upcase(val)} end)
#=> {["Wright", "Ewes"],
#    [%{actor: %{first: "Robin", last: "WRIGHT"}, character: "Buttercup",
#       role: "princess"},
#     %{actor: %{first: "Cary", last: "EWES"}, character: "Westley",
#       role: "farm boy"}]}

Access.elem 可處理 tuples


cast = [
  %{
    character: "Buttercup",
    actor:    {"Robin", "Wright"},
    role:      "princess"
  },
  %{
    character: "Westley",
    actor:    {"Carey", "Elwes"},
    role:      "farm boy"
  }
]

IO.inspect get_in(cast, [Access.all(), :actor, Access.elem(1)])
#=> ["Wright", "Elwes"]

IO.inspect get_and_update_in(cast, [Access.all(), :actor, Access.elem(1)],
                             fn (val) -> {val, String.reverse(val)} end)
#=> {["Wright", "Elwes"],
#    [%{actor: {"Robin", "thgirW"}, character: "Buttercup", role: "princess"},
#     %{actor: {"Carey", "sewlE"}, character: "Westley", role: "farm boy"}]}

Access.key Access.key! 可處理 dictionary types(maps, structs)



cast = %{
  buttercup: %{
    actor:    {"Robin", "Wright"},
    role:      "princess"
  },
  westley: %{
    actor:    {"Carey", "Elwes"},
    role:      "farm boy"
  }
}

IO.inspect get_in(cast, [Access.key(:westley), :actor, Access.elem(1)])
#=> "Elwes"

IO.inspect get_and_update_in(cast, [Access.key(:buttercup), :role],
                             fn (val) -> {val, "Queen"} end)
#=> {"princess",
#    %{buttercup: %{actor: {"Robin", "Wright"}, role: "Queen"},
#      westley: %{actor: {"Carey", "Elwes"}, role: "farm boy"}}}

Access.pop 可從 map/keyword list 移出一個 key


iex(1)> Access.pop(%{name: "Elixir", creator: "Valim"}, :name)
{"Elixir", %{creator: "Valim"}}
iex(2)> Access.pop([name: "Elixir", creator: "Valim"], :name)
{"Elixir", [creator: "Valim"]}
iex(3)> Access.pop(%{name: "Elixir", creator: "Valim"}, :year)
{nil, %{creator: "Valim", name: "Elixir"}}

Sets


Sets 是以 MapSet module 實作的


iex(1)> set1 = 1..5 |> Enum.into(MapSet.new)
#MapSet<[1, 2, 3, 4, 5]>
iex(2)> set2 = 3..8 |> Enum.into(MapSet.new)
#MapSet<[3, 4, 5, 6, 7, 8]>
iex(3)> MapSet.member? set1, 3
true
iex(4)> MapSet.union set1, set2
#MapSet<[1, 2, 3, 4, 5, 6, 7, 8]>
iex(5)> MapSet.difference set1, set2
#MapSet<[1, 2]>
iex(6)> MapSet.difference set2, set1
#MapSet<[6, 7, 8]>
iex(7)> MapSet.intersection set2, set1
#MapSet<[3, 4, 5]>

Enum and Stream


elixir 的 Enum and Stream 有一下 iteration functions,Stream 可 enumerate a collection lazily,也就是說下一個 value 只在需要時才會計算


Enum


## 將 collection 轉換為 list
iex(1)> list = Enum.to_list 1..5
[1, 2, 3, 4, 5]

## 連接兩個 collections
iex(2)> Enum.concat([1,2,3], [4,5,6])
[1, 2, 3, 4, 5, 6]
iex(3)> Enum.concat [1,2,3], 'abc'
[1, 2, 3, 97, 98, 99]

## 將 list 每個 elements 放到 function 產生新的 list
iex(4)> Enum.map(list, &(&1 * 10))
[10, 20, 30, 40, 50]
iex(5)> Enum.map(list, &String.duplicate("*", &1))
["*", "**", "***", "****", "*****"]


## 根據 position/criteria 取得 list 的 elements
iex(8)> Enum.at(10..20, 3)
13
iex(9)> Enum.at(10..20, 20)
nil
iex(10)> Enum.at(10..20, 20, :no_one_here)
:no_one_here
iex(11)> Enum.filter(list, &(&1 > 2))
[3, 4, 5]
iex(12)> require Integer
Integer
iex(13)> Enum.filter(list, &Integer.is_even/1)
[2, 4]
iex(14)> Enum.reject(list, &Integer.is_even/1)
[1, 3, 5]

## 排序/比較 elements
iex(16)> Enum.sort ["there", "was", "a", "crooked", "man"]
["a", "crooked", "man", "there", "was"]
iex(17)> Enum.sort ["there", "was", "a", "crooked", "man"], &(String.length(&1) <= String.length(&2))
["a", "was", "man", "there", "crooked"]
iex(18)>  Enum.max ["there", "was", "a", "crooked", "man"]
"was"
iex(19)> Enum.max_by ["there", "was", "a", "crooked", "man"], &String.length/1
"crooked"

## split a collection
iex(20)> Enum.take(list, 3)
[1, 2, 3]
iex(21)> Enum.take_every list, 2
[1, 3, 5]
iex(22)> Enum.take_while(list, &(&1 < 4))
[1, 2, 3]
iex(23)> Enum.split(list, 3)
{[1, 2, 3], [4, 5]}
iex(24)> Enum.split_while(list, &(&1 < 4))
{[1, 2, 3], [4, 5]}

## join a collection
iex(25)> Enum.join(list)
"12345"
iex(26)> Enum.join(list, ", ")
"1, 2, 3, 4, 5"


## predicate operations
iex(27)> Enum.all?(list, &(&1 < 4))
false
iex(28)> Enum.any?(list, &(&1 < 4))
true
iex(29)> Enum.member?(list, 4)
true
iex(30)> Enum.empty?(list)
false

## merge collections
iex(31)> Enum.zip(list, [:a, :b, :c])
[{1, :a}, {2, :b}, {3, :c}]
iex(32)> Enum.with_index(["once", "upon", "a", "time"])
[{"once", 0}, {"upon", 1}, {"a", 2}, {"time", 3}]

## Fold elements into a single value
iex(33)> Enum.reduce(1..100, &(&1+&2))
5050

iex(37)> Enum.reduce(["now", "is", "the", "time"],fn word, longest ->
...(37)> if String.length(word) > String.length(longest) do
...(37)> word
...(37)> else
...(37)> longest
...(37)> end
...(37)> end)
"time"

處理撲克牌


iex(1)> import Enum
Enum
iex(2)> deck = for rank <- '23456789TJQKA', suit <- 'CDHS', do: [suit,rank]
['C2', 'D2', 'H2', 'S2', 'C3', 'D3', 'H3', 'S3', 'C4', 'D4', 'H4', 'S4', 'C5',
 'D5', 'H5', 'S5', 'C6', 'D6', 'H6', 'S6', 'C7', 'D7', 'H7', 'S7', 'C8', 'D8',
 'H8', 'S8', 'C9', 'D9', 'H9', 'S9', 'CT', 'DT', 'HT', 'ST', 'CJ', 'DJ', 'HJ',
 'SJ', 'CQ', 'DQ', 'HQ', 'SQ', 'CK', 'DK', 'HK', 'SK', 'CA', 'DA', ...]
 
## 洗牌, 取 13 張
iex(3)> deck |> shuffle |> take(13)
['S5', 'DQ', 'ST', 'H8', 'S2', 'D9', 'CA', 'CT', 'C6', 'D6', 'D5', 'H2', 'D7']

## 發牌給四個人
iex(4)> hands = deck |> shuffle |> chunk(13)
[['HA', 'H3', 'HJ', 'C3', 'DA', 'SA', 'DQ', 'H6', 'C8', 'D6', 'D7', 'ST', 'D8'],
 ['H8', 'H5', 'C2', 'SJ', 'H2', 'S2', 'CQ', 'S4', 'S3', 'CA', 'C4', 'S8', 'H9'],
 ['DJ', 'S7', 'C5', 'CT', 'HK', 'HQ', 'C6', 'D3', 'SQ', 'D2', 'CK', 'H4', 'D4'],
 ['C9', 'D5', 'SK', 'CJ', 'S9', 'D9', 'S5', 'S6', 'DK', 'H7', 'C7', 'DT', 'HT']]

Streams - Lazy Enumerables


[ 1, 2, 3, 4, 5 ]
|> Enum.map(&(&1*&1))
|> Enum.with_index   # 產生 a list of tuples
|> Enum.map(fn {value, index} -> value - index end)
|> IO.inspect   #=> [1,3,7,13,21]

如果 words 有2.4MB,Enum 會全部 load 到記憶體並送進 split,應該改用 Stream 一行一行處理就好了


IO.puts File.read!("/usr/share/dict/words")
        |> String.split
        |> Enum.max_by(&String.length/1)

使用 Stream 會產生一個 Enum.map,而不是直接得到結果


iex(1)> s = Stream.map [1, 3, 5, 7], &(&1 + 1)
#Stream<[enum: [1, 3, 5, 7], funs: [#Function<46.40091930/1 in Stream.map/2>]]>
iex(2)> Enum.to_list s
[2, 4, 6, 8]

[1,2,3,4]
|> Stream.map(&(&1*&1))
|> Stream.map(&(&1+1))
|> Stream.filter(fn x -> rem(x,2) == 1 end)
|> Enum.to_list

大檔案資料,一次處理一行


IO.puts File.open!("/usr/share/dict/words")
        |> IO.stream(:line)
        |> Enum.max_by(&String.length/1)

也可以改為 File.stream


IO.puts File.stream!("/usr/share/dict/words") |> Enum.max_by(&String.length/1)



Infinite Stream


可產生非常大的 Stream,但只取某幾個,用 Enum 會卡住,要改用Stream


Enum.map(1..10_000_000, &(&1+1)) |> Enum.take(5)

iex(1)> Stream.map(1..10_000_000, &(&1+1)) |> Enum.take(5)
[2, 3, 4, 5, 6]



cycle, repeatedly, iterate, unfold, resource


Stream.cycle: 利用 enumerable 產生 infinite stream 包含這些 elements


iex(1)> Stream.cycle(~w{ green white }) |>
...(1)> Stream.zip(1..5) |>
...(1)> Enum.map(fn {class, value} ->
...(1)>     ~s{<tr class="#{class}"><td>#{value}</td></tr>\n} end) |>
...(1)> IO.puts
<tr class="green"><td>1</td></tr>
<tr class="white"><td>2</td></tr>
<tr class="green"><td>3</td></tr>
<tr class="white"><td>4</td></tr>
<tr class="green"><td>5</td></tr>

Stream.repeatedly: 當需要新 value 時,就會 invoke 該 function


iex(4)> Stream.repeatedly(fn -> true end) |> Enum.take(3)
[true, true, true]
iex(5)> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]

Stream.iterate(start_value, next_fun): 由 startvalue 開始,送入 nextfun 產生下一個 value,一直無限循環


iex(8)> Stream.iterate(0, &(&1+1)) |> Enum.take(5)
[0, 1, 2, 3, 4]
iex(9)> Stream.iterate(2, &(&1*&1)) |> Enum.take(5)
[2, 4, 16, 256, 65536]
iex(10)> Stream.iterate([], &[&1]) |> Enum.take(5)
[[], [[]], [[[]]], [[[[]]]], [[[[[]]]]]]

Stream.unfold: 產生 infinite stream of values,每一個 value 都是 previous state 的某個 function


fn state -> { stream_value, new_state } end

iex(13)> Stream.unfold({0,1}, fn {f1,f2} -> {f1, {f2, f1+f2}} end) |> Enum.take(15)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Stream.resource



defmodule Countdown do

  def sleep(seconds) do
    receive do
      after seconds*1000 -> nil
    end
  end

  def say(text) do
    spawn fn -> :os.cmd('say #{text}') end
  end

  def timer do
    Stream.resource(
      fn ->          # the number of seconds to the start of the next minute
         {_h,_m,s} = :erlang.time
         60 - s - 1
      end,

      fn             # wait for the next second, then return its countdown
        0 ->
          {:halt, 0}

        count ->
          sleep(1)
          { [inspect(count)], count - 1 }
      end,

      fn _ -> nil end   # nothing to deallocate
    )
  end
end

iex(1)> counter = Countdown.timer
#Function<50.40091930/2 in Stream.resource/3>
iex(2)> printer = counter |> Stream.each(&IO.puts/1)
#Stream<[enum: #Function<50.40091930/2 in Stream.resource/3>,
 funs: [#Function<38.40091930/1 in Stream.each/2>]]>
iex(3)> speaker = printer |> Stream.each(&Countdown.say/1)
#Stream<[enum: #Function<50.40091930/2 in Stream.resource/3>,
 funs: [#Function<38.40091930/1 in Stream.each/2>,
  #Function<38.40091930/1 in Stream.each/2>]]>
iex(4)> speaker |> Enum.take(5)
19
18
17
16
15
["19", "18", "17", "16", "15"]

Collectable Protocol


Enumerable Procotol 可 iterate over the elements
Collectable Protocol 則是相反,可 insert elements 產生 collection


iex(1)> Enum.into 1..5, []
[1, 2, 3, 4, 5]
iex(2)> Enum.into 1..5, [100, 101 ]
[100, 101, 1, 2, 3, 4, 5]
iex(3)> Enum.into IO.stream(:stdio, :line), IO.stream(:stdio, :line)

Comprehensions


comprehension: 提供 1 到多個 collections,取出每個 values 的所有組合,過濾掉一些 values,用剩下的 values 產生一個新的 collection。


result = for generator or filter… [, into: value ], do: expression


generator 的語法


pattern <- enumerable_thing

iex(1)> for x <- [ 1, 2, 3, 4, 5 ], do: x * x
[1, 4, 9, 16, 25]
iex(2)> for x <- [ 1, 2, 3, 4, 5 ], x < 4, do: x * x
[1, 4, 9]

iex(3)> for x <- [1,2], y <- [5,6], do: x * y
[5, 6, 10, 12]
iex(4)> for x <- [1,2], y <- [5,6], do: {x, y}
[{1, 5}, {1, 6}, {2, 5}, {2, 6}]
iex(5)> min_maxes = [{1,4}, {2,3}, {10, 15}]
[{1, 4}, {2, 3}, {10, 15}]
iex(6)> for {min,max} <- min_maxes, n <- min..max, do: n
[1, 2, 3, 4, 2, 3, 10, 11, 12, 13, 14, 15]

iex(8)> first8 = [ 1,2,3,4,5,6,7,8 ]
[1, 2, 3, 4, 5, 6, 7, 8]
iex(9)> for x <- first8, y <- first8, x >= y, rem(x*y, 10)==0, do: { x, y }
[{5, 2}, {5, 4}, {6, 5}, {8, 5}]

也可以用在 Bits


iex(11)> for << ch <- "hello" >>, do: ch
'hello'
iex(12)> for << ch <- "hello" >>, do: <<ch>>
["h", "e", "l", "l", "o"]
iex(14)> for << << b1::size(2), b2::size(3), b3::size(3) >> <- "hello" >>, do: "0#{b1}#{b2}#{b3}"
["0150", "0145", "0154", "0154", "0157"]

在 list comprehension 中用到的變數,都是 local 變數


iex(17)> name="You"
"You"
iex(18)> for name <- [ "cat", "dog" ], do: String.upcase(name)
["CAT", "DOG"]
iex(19)> name
"You"

comprehension 的回傳值是由 do 的結果決定


iex(1)> for x <- ~w{ cat dog }, into: %{}, do: { x, String.upcase(x) }
%{"cat" => "CAT", "dog" => "DOG"}
iex(2)> for x <- ~w{ cat dog }, into: Map.new, do: { x, String.upcase(x) }
%{"cat" => "CAT", "dog" => "DOG"}
iex(3)> for x <- ~w{ cat dog }, into: %{"ant" => "ANT"}, do: { x, String.upcase(x) }
%{"ant" => "ANT", "cat" => "CAT", "dog" => "DOG"}

iex(5)> for x <- ~w{ cat dog }, into: IO.stream(:stdio,:line), do: "<<#{x}>>\n"
<<cat>>
<<dog>>
%IO.Stream{device: :standard_io, line_or_bytes: :line, raw: false}

References


Programming Elixir

2018/4/2

Elixir 2 - Modules, Functions


Anonymous Functions


fn
    parameter-list -> body
    parameter-list -> body ...
end

iex(1)> sum = fn (a, b) -> a + b end
#Function<12.99386804/2 in :erl_eval.expr/5>
iex(2)> sum.(1, 2)
3

# 可省略 ()
iex(3)> sum = fn a, b -> a + b end
#Function<12.99386804/2 in :erl_eval.expr/5>

# 沒有參數的 fn
iex(5)> f2 = fn -> 99 end
#Function<20.99386804/0 in :erl_eval.expr/5>
iex(6)> f2.()
99

以 tuple 為參數


iex(8)> swap = fn { a, b } -> { b, a } end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(9)> swap.({1,2})
{2, 1}



一個 fn 可以有多種 function body,fn 是以 File.open 回傳的結果決定 fn body 的內容是使用哪一個 pattern 的定義


iex(1)> open_file = fn
...(1)>     {:ok, file} -> "Read data: #{IO.read(file, :line)}"
...(1)>     {_, error} -> "Error: #{:file.format_error(error)}"
...(1)>   end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(2)> open_file.(File.open("/etc/passwd"))
"Read data: ##\n"
iex(3)>   open_file.(File.open("nonexistent"))
"Error: no such file or directory"



可以 Return Function


iex(6)> fun1 = fn -> fn -> "Hello" end end
### 等同 fun1 = fn -> (fn -> "Hello" end) end
#Function<20.99386804/0 in :erl_eval.expr/5>
iex(7)> fun1.()
#Function<20.99386804/0 in :erl_eval.expr/5>
iex(8)> fun1.().()
"Hello"



Functions Remember Their Original Environment


iex(13)> greeter = fn name -> (fn -> "Hello #{name}" end) end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(14)> dave_greeter = greeter.("Dave")
#Function<20.99386804/0 in :erl_eval.expr/5>
iex(15)> dave_greeter.()
"Hello Dave"



Parameterized Functions


iex(19)> add_n = fn n -> (fn other -> n + other end) end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(20)> add_two = add_n.(2)
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(21)> add_five = add_n.(5)
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(22)> add_two.(3)
5
iex(23)> add_five.(7)
12



以 fun 為參數


iex(26)> times_2 = fn n -> n * 2 end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(27)> apply = fn (fun, value) -> fun.(value) end
#Function<12.99386804/2 in :erl_eval.expr/5>
iex(28)> apply.(times_2, 6)
12



利用 Pin operator 將 fun 固定參數


defmodule Greeter do
  def for(name, greeting) do
    fn
      (^name) -> "#{greeting} #{name}"
      (_) -> "I don't know you"
    end
  end
end
mr_greeter = Greeter.for("Test", "You")
IO.puts mr_greeter.("Test") # => You Test
IO.puts mr_greeter.("Dave") # => I don't know you



利用 & 實作 func


add_one = &(&1 + 1)
# & 就是 fn
# &1 是 fn 的第一個參數, &2 是 fn 的第二個參數

### 等同
add_one = fn (n) -> n + 1 end

iex(38)> divrem = &{ div(&1,&2), rem(&1,&2) }
#Function<12.99386804/2 in :erl_eval.expr/5>
iex(39)> divrem.(13, 5)
{2, 3}

iex(42)> l = &length/1
&:erlang.length/1
iex(43)> l.([1,3,5,7])
4

& 可快速產生一個 anonymous function 傳入 map function 的第二個參數


iex(44)> Enum.map [1,2,3,4], &(&1 + 1)
[2, 3, 4, 5]

Modules & Named Functions


times.exs


defmodule Times do
  def double(n) do
    n * 2
  end
end

$ iex times.exs
iex(1)> Times.double
double/1
iex(1)> Times.double(3)
6

也可以啟動 iex 後,用 c 編譯


iex(1)> c("times.exs")
[Times]

## 可直接知道是哪一行,哪一個 function 發生錯誤
iex(2)> Times.double("2")
** (ArithmeticError) bad argument in arithmetic expression
    times.exs:3: Times.double/1

do: 是 syntax sugar,可以用 () 將 expression grouping 在一起, do: 就是 do ... end 的簡化語法


defmodule Times do
  def double(n), do: n * 2
  
  def greet(greeting, name), do: (
    IO.puts greeting
    IO.puts "How're you doing, #{name}?"
  )
end



Function Call with Pattern Matching


defmodule Factorial do
  def of(0), do: 1
  def of(n), do: n * of(n-1)
end

iex(1)> Factorial.of(3)
6
iex(2)> Factorial.of(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

def of(0), do: 1 不能寫在後面,因為 elixir 是依照順序檢查是否有符合 pattern,如果寫錯了,compiler 會出現警告


defmodule BadFactorial do
  def of(n), do: n * of(n-1)
  def of(0), do: 1
end

warning: this clause cannot match because a previous clause at line 2 always matches
  factorial1-bad.exs:3



Guard Clauses


defmodule Guard do

  def what_is(x) when is_number(x) do
    IO.puts "#{x} is a number"
  end

  def what_is(x) when is_list(x) do
    IO.puts "#{inspect(x)} is a list"
  end

  def what_is(x) when is_atom(x) do
    IO.puts "#{x} is an atom"
  end

end

Guard.what_is(99)        # => 99 is a number
Guard.what_is(:cat)      # => cat is an atom
Guard.what_is([1,2,3])   # => [1,2,3] is a list

回頭看剛剛的 factorial,如果 n 為負數,就會發生無窮迴圈的狀況,因此要改為


defmodule Factorial do
  def of(0), do: 1
  def of(n) when n > 0 do
    n * of(n-1)
  end
end

在 guard clause 中可以使用的 elixir expression


  1. comparison operator


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

  2. Boolean and negation operators


    or, and, not, !
    不能使用 || and &&

  3. Arithmetic operators


    +, -, *, /

  4. Join operators


    <> and ++, as long as the left side is a literal.

  5. in operator


    Membership in a collection or range

  6. Type-check functions


    is_atom is_binary is_bitstring is_boolean is_exception is_float is_function is_integer is_list is_map is_number is_pid is_port is_record is_reference is_tuple
  7. Other functions


    回傳 value 的 built-in functions


    abs(number) bit_size(bitstring) byte_size(bitstring) div(number,number) elem(tuple, n) float(term) hd(list) length(list) node() node(pid|ref|port) rem(number,number) round(number) self() tl(list)trunc(number) tuple_size(tuple)



參數的預設值


defmodule Example do

  def func(p1, p2 \\ 2, p3 \\ 3, p4) do
    IO.inspect [p1, p2, p3, p4]
  end
end

Example.func("a", "b")             # => ["a",2,3,"b"]
Example.func("a", "b", "c")        # => ["a","b",3,"c"]
Example.func("a", "b", "c", "d")   # => ["a","b","c","d"]

因為前面已經定義了 2, 3, 4 個參數的 func,因此 def func(p1, p2) 會出現重複 func 定義的 error


defmodule Example do

  def func(p1, p2 \\ 2, p3 \\ 3, p4) do
    IO.inspect [p1, p2, p3, p4]
  end

  def func(p1, p2) do
     IO.inspect [p1, p2]
  end

end


** (CompileError) default_params.exs:7: def func/2 conflicts with defaults from def func/4
    default_params.exs:7: (module)
    default_params.exs:1: (file)

以沒有 func body 的 func 定義,來解決剛剛遇到的問題


defmodule Params do
  def func(p1, p2 \\ 123)

  def func(p1, p2) when is_list(p1) do
    "You said #{p2} with a list"
  end

  def func(p1, p2) do
    "You passed in #{p1} and #{p2}"
    end
end

IO.puts Params.func(99) # You passed in 99 and 123
IO.puts Params.func(99, "cat") # You passed in 99 and cat
IO.puts Params.func([99]) # You said 123 with a list
IO.puts Params.func([99], "dog") # You said dog with a list



defp 可定義 Private Functions,但不能 private 與 public function 不能共用




Pipe Operator |>


以往會寫成


people = DB.find_customers
orders = Orders.for_customers(people)
tax = sales_tax(orders, 2016)
filing = prepare_filing(tax)

或是


filing = prepare_filing(sales_tax(Orders.for_customers(DB.find_customers), 2016))

但有了 |> 可改成


filing = DB.find_customers
    |> Orders.for_customers
    |> sales_tax(2016)
    |> prepare_filing

val |> f(a,b)

等同
f(val,a,b)

list
    |> sales_tax(2016)
    |> prepare_filing

等同

prepare_filing(sales_tax(list, 2016))

& 就是 fun


iex> (1..10) |> Enum.map(&(&1*&1)) |> Enum.filter(&(&1 < 40))
[1, 4, 9, 16, 25, 36]



Modules


defmodule Mod do
    def func1 do
        IO.puts "in func1"
    end
    def func2 do
        func1
        IO.puts "in func2"
    end
end

Mod.func1
Mod.func2

Inner Module


defmodule Outer do
    defmodule Inner do
        def inner_func do
        end
    end
    
    def outer_func do
        Inner.inner_func
    end
end

Outer.outer_func
Outer.Inner.inner_func

mix/tasks/doctest.ex


defmodule Mix.Tasks.Doctest do
    def run do
    end
end

Mix.Tasks.Doctest.run

Directives for Modules


  1. import


    語法 import Module [, only:|except: ]


    ex: import List, only: [ flatten: 1, duplicate: 2 ]


    defmodule Example do
      def func1 do
         List.flatten [1,[2,3],4]
      end
      def func2 do
        import List, only: [flatten: 1]
        flatten [5,[6,7],8]
      end
    end
  2. alias


    defmodule Example do
        def compile_and_go(source) do
            alias My.Other.Module.Parser, as: Parser
            alias My.Other.Module.Runner, as: Runner
            source
                |> Parser.parse()
                |> Runner.execute()
        end
    end

    也可寫成


    alias My.Other.Module.Parser
    alias My.Other.Module.Runner
    
    alias My.Other.Module.{Parser, Runner}
  3. require


    當寫下 require a module,在編譯時可保證一定存在該 macrio 定義




Module Attribute


只能用在 module 的最底層,這不是變數,比較接近 configuration, metadata 的用途


defmodule Example do

    @author  "Test"
    def get_author do
        @author
    end

    @attr "one"
    def first, do: @attr
    @attr "two"
    def second, do: @attr

end

module name 就是 atoms,因此 IO 會被轉換為 Elixir.IO


iex(1)> is_atom IO
true
iex(2)> to_string IO
"Elixir.IO"
iex(3)> :"Elixir.IO" === IO
true
iex(4)> IO.puts 123
123
:ok
iex(5)> :"Elixir.IO".puts 123
123
:ok



如何呼叫 Erlang Library 的 Functions


erlang 的 io module,就是 elixir 的一個 atom :io


iex(16)> :io.format("The number is ~3.1f~n", [5.678])
The number is 5.7

References


Programming Elixir

2018/3/26

Elixir 1 - Basic Data Types


安裝 elixir


Installing Elixir 記錄了如何在不同 OS 安裝 elixir 的方法。


我是透過 macport 安裝 elixir


sudo port install elixir

elixir 是用 Erlang/OTP 20 開發的,所以要注意是不是已經有安裝了 erlang @20


$ port installed |grep erlang
  erlang @20.1_0+hipe+ssl+wxwidgets (active)

CentOS 7 上安装 Elixir 文章提到可以從 elixir 的 github 下載 precompiled zip,解壓縮後就可以用了。


CentOS 7 上 Elixir 開發之環境配置


interactive shell: iex


iex 是可以直接 evaluate elixir expression 的互動 shell, h 是 help, i 可查看資料的資訊


$ iex
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.5.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> h

iex(2)> h(v/0)
Returns the value of the nth expression in the history.

iex(3)> h(IO)
Functions handling input/output (IO).

iex(4)> > i 123
Term
  123
Data type
  Integer
Reference modules
  Integer
Implemented protocols
  IEx.Info, Inspect, List.Chars, String.Chars

離開 iex 的方式有兩種


  1. Ctrl-C 兩次
  2. Ctrl-G 然後輸入 q 及 Retuen

Customize iex: 在 IEx.configure 可查看 iex 所有可調整設定的選項


iex(1)> h IEx.configure

把設定的相關程式放在 ~/.iex.exs 就可以使用, ex:


IEx.configure(colors: [ eval_result: [:red, :bright] ])
IEx.configure(inspect: [limit: 10])

編譯與執行


hello.exs


IO.puts "Hello, World!"

$ elixir hello.exs
Hello, World!

或是在 iex 裡面執行


iex(1)> c "hello.exs"
Hello, World!
[]

Pattern Matching


elixir 將 = 視為 pattern matching 的 operator


iex(4)> list = [1, 2, [ 3, 4, 5 ] ]
[1, 2, [3, 4, 5]]
iex(5)> [a, b, c ] = list
[1, 2, [3, 4, 5]]
iex(6)> c
[3, 4, 5]
iex(7)> [a, 1, c ] = list
** (MatchError) no match of right hand side value: [1, 2, [3, 4, 5]]

_ 是 ignore 的意思,這跟 erlang 一樣


iex(7)> [a, _, c ] = list
[1, 2, [3, 4, 5]]

Variable 可在不同的 expression 重新被 assgin 新的 value,但如果要強制使用舊的 value,可使用 ^ pin operator


iex(1)> a=1
1
iex(2)> a=2
2
iex(3)> ^a=1
** (MatchError) no match of right hand side value: 1

Immutable Data


在 mutable data 的程式語言中,count 傳入 function 後,可能會在該 function 內改變數值,但 Immutable Data 強調 count 數值不變的特性。


count = 99
do_something_with(count)
print(count)

elixir 會複製一份 list1 的資料,加到 4 後面,然後指定給 list2


iex(3)> list1 = [ 3, 2, 1 ]
[3, 2, 1]
iex(4)> list2 = [ 4 | list1 ]
[4, 3, 2, 1]

用不到的變數,會自動以 Garbage Collection 的方式回收掉。因為 elixir 會有很多 processes,每個 process 都有自己的 heap,每個 heap 都是各自獨立的。


Elixir Data Types


Built-in Types


  • Value types:

    • Arbitrary-sized integers
    • Floating-point numbers
    • Atoms
    • Ranges
    • Regular expressions
  • System types:

    • PIDs and ports
    • References
  • Collection types:

    • Tuples
    • Lists
    • Maps
    • Binaries
  • Functions



elixir 的檔案有兩種 extensions


  • ex: for compiled code
  • exs: for interpreted code

如果要寫 scripts 或是 test,要使用 .exs,其他狀況就用 .ex




  • Integers: 可寫成


  1. decimal: 123
  2. hexadecimal: 0xcafe
  3. octal: 0o777
  4. binary: 0b10110


  • Floating-point numbers


  1. 1.0
  2. 0.2456
  3. 0.314159e1
  4. 314159.0e-5


  • Atoms

erlang 的 atom 為小寫字母,變數為大寫,但在 elixir 是以 : 前置符號代表 atom


  1. :fred
  2. :is_binary?
  3. :var@2
  4. :<>
  5. :===
  6. :"func/3"
  7. :"long john silver"


  • Ranges: start..end

ex: 1..5


  • Regular expressions: ~r{regexp} 或是 ~r{regexp}opts,regular expression 是使用 PCRE 格式

iex(1)> Regex.run ~r{[aeiou]}, "caterpillar"
["a"]
iex(2)> Regex.scan ~r{[aeiou]}, "caterpillar"
[["a"], ["e"], ["i"], ["a"]]
iex(3)> Regex.split ~r{[aeiou]}, "caterpillar"
["c", "t", "rp", "ll", "r"]
iex(4)> Regex.replace ~r{[aeiou]}, "caterpillar", "*"
"c*t*rp*ll*r"

Opt Meaning
f 強制該 pattern 要出現在多行資料的第一行
g 支援命名的 groups
i case insensitive
m 如果有多行文字, ^ and $ 分別代表每一行文字的開始與結束。\A and \z 是這些文字的開頭與結束
s 可使用 . 代表任意字元
U 通常 * + 是 greedy,就是越多字元越好,但如果加上 U 就變成 ungreedy
u 可使用 unicode-specific patterns 例如 \p
x 可使用 extended mode—ignore whitespace and comments (# to end of line)



  • PID Ports

PID: a reference to a local or remote process
Port: a reference to an external resource,例如 C 的 library


  • References

make_ref 會產生 a globally unique reference




  • Tuples

tuple: an ordered collection of values


  1. { 1, 2 }
  2. { :ok, 42, "next" }
  3. { :error, :enoent }

通常 :ok 代表 noerror


iex(1)> {status, file} = File.open("hello.exs")
{:ok, #PID<0.86.0>}
iex(2)> {status, file} = File.open("hello1.exs")
{:error, :enoent}

  • Lists

iex(3)> [ 1, 2, 3 ] ++ [ 4, 5, 6 ]
[1, 2, 3, 4, 5, 6]
iex(4)> [1, 2, 3, 4] -- [2, 4]
[1, 3]
iex(5)> 1 in [1,2,3,4]
true
iex(6)> "wombat" in [1, 2, 3, 4]
false

Keyword Lists: lists of key/value pairs,elixir 會轉換為 a list of two-value tuples:


[ name: "Dave", city: "Dallas", likes: "Programming" ]

[ {:name, "Dave"}, {:city, "Dallas"}, {:likes, "Programming"} ]

elixir 可省略 []


DB.save record, [ {:use_transaction, true}, {:logging, "HIGH"} ]
# 可寫成
DB.save record, use_transaction: true, logging: "HIGH"

以下為 list 與 tuple 的差異


iex(11)> [1, fred: 1, dave: 2]
[1, {:fred, 1}, {:dave, 2}]
iex(12)> {1, fred: 1, dave: 2}
{1, [fred: 1, dave: 2]}

  • Map

%{ key => value, key2 => value2 }

當 key 為 atoms,可使用 . 直接取到該 value


iex(13)> states = %{ "AL" => "Alabama", "WI" => "Wisconsin" }
%{"AL" => "Alabama", "WI" => "Wisconsin"}
iex(14)> colors = %{ :red => 0xff0000, :green => 0x00ff00, :blue => 0x0000ff }
%{blue: 255, green: 65280, red: 16711680}
iex(15)> %{ "one" => 1, :two => 2, {1,1,1} => 3 }
%{:two => 2, {1, 1, 1} => 3, "one" => 1}
iex(16)> colors.red
16711680
iex(17)> colors[:red]
16711680

同時存在 Map 以及 keyword list 的原因:Map 的 key 必須是唯一的,但 keyword lists 的 key 可以重複


  • Binary

可直接指定單一個 byte 內 不同寬度 bits 的數值


iex(1)> bin = << 1, 2 >>
<<1, 2>>
iex(2)> byte_size bin
2
iex(3)> bin = <<3 :: size(2), 5 :: size(4), 1 :: size(2)>>
<<213>>
iex(4)> :io.format("~-8.2b~n", :binary.bin_to_list(bin))
11010101
:ok
iex(5)> byte_size bin
1

  • Dates and Times

Date 是 ISO-8601 格式


iex(6)> d1 = Date.new(2016, 12, 25)
{:ok, ~D[2016-12-25]}
iex(7)> {:ok, d1} = Date.new(2016, 12, 25)
{:ok, ~D[2016-12-25]}
iex(8)> d2 = ~D[2016-12-25]
~D[2016-12-25]
iex(9)> d1 == d2
true
iex(10)> inspect d1, structs: false
"%{__struct__: Date, calendar: Calendar.ISO, day: 25, month: 12, year: 2016}"

iex(15)> {:ok, t1} = Time.new(12, 34, 56)
{:ok, ~T[12:34:56]}
iex(16)> t2 = ~T[12:34:56]
~T[12:34:56]
iex(17)> t1==t2
true
iex(18)> inspect t2, structs: false
"%{__struct__: Time, calendar: Calendar.ISO, hour: 12, microsecond: {0, 0}, minute: 34, second: 56}"
iex(19)> inspect t1, structs: false
"{:ok, %{__struct__: Time, calendar: Calendar.ISO, hour: 12, microsecond: {0, 0}, minute: 34, second: 56}}"
iex(20)> t3 = ~T[12:34:56.78]
~T[12:34:56.78]

其他規則


elixir 的 identifier 可用大小寫 ASCII characters, digits 及 _,可用 ? 當結尾


module, record, protocol, and behavior names 是以大寫 letter 開頭


source code 使用 UTF-8 encoding


註解以 # 開頭


Boolean operations 有三種 true, false, and nil. nil is treated as false in Boolean contexts.




Operators


  • Comparison


    a === b # strict equality (so 1 === 1.0 is false)
    a !== b # strict inequality (so 1 !== 1.0 is true)
    a == b # value equality (so 1 == 1.0 is true)
    a != b # value inequality (so 1 != 1.0 is false)
    a > b # normal comparison
    a >= b # :
    a < b # :
    a <= b # :
  • Boolean


    a or b # true if a is true, otherwise b
    a and b # false if a is false, otherwise b
    not a # false if a is true, true otherwise
  • Relaxed Boolean


    a || b # a if a is truthy, otherwise b
    a && b # b if a is truthy, otherwise a
    !a # false if a is truthy, otherwise true
  • Arithmetic operators


    + - * / div rem
  • Join operators


    binary1 <> binary2 # concatenates two binaries
    list1 ++ list2 # concatenates two lists
    list1 -- list2 # removes elements of list2 from a copy of list 1
  • in operator


    a in enum # tests if a is included in enum



Variable Scope: function body


with Expression


content = "Now is the time"
lp = with {:ok, file} = File.open("/etc/passwd"),
        content = IO.read(file, :all),
        :ok = File.close(file),
        [_, uid, gid] = Regex.run(~r/_lp:.*?:(\d+):(\d+)/, content) do
        "Group: #{gid}, User: #{uid}"
    end
IO.puts lp # => Group: 26, User: 26
IO.puts content

References


Programming Elixir

2018/3/19

Elixir


Elixir 是一個基於 Erlang Beam VM 的 functioncal concurrent programming language。Elixir 以 Erlang為基礎,支持分佈式、高容錯、實時應用程式的開發,也可通過巨集實現 meta programming 對其進行擴展,並通過 Protocol 支援多態。


以下是 Elixir 的一些特性


  • 2013年誕生
  • 基於Erlang虛擬機(BEAM)
  • 語法類似 Ruby,因為 Elixir 的創造者 José Valim(巴西人) 他是 Ruby on Rails 的核心團隊成員!有不少 Ruby 的開發者跑來學。(ref: An Interview with Elixir Creator José Valim)
  • 由 Elixir code 直接編譯為 Beam VM 的 binary code
  • 動態強型別語言 (變數型別不會在運算中自動轉型) ,類似 Ruby、Python 這類動態強型別語言
  • 可直接呼叫 erlang 開發的 module
  • 基於巨集的 meta programming 能力,語言的抽象語法樹
  • 基於 Protocol 的多態實現
  • 以 Message 互相傳遞資料
  • 支援 Unicode
  • 開發 Web 搭配 Phoenix Framework

erlang 有著平行處理的平台優勢,但是作為一個古老的語言,其語法會讓人難以接受。


Elixir解決了 erlang 的問題,有自己的程序包管理系統、Macro、易於使用的構建工具和Unicode處理機制,就像是一個新時代的語言,掛上了原本就性能卓越的引擎。


一開始接觸 elixir,通常會介紹 Pipe Operator


如果在 erlang 要針對某個 function 的回傳值放入另一個 function 當作參數,會寫成以下這樣。


list_to_atom(binary_to_list(capitalize_binary(list_to_binary(atom_to_list(X))))).

當然習慣了以後,為了避免老眼昏花的問題,通常會在中間加上幾個變數,把一行的語法改成 2~3 行,讓程式可讀性更好。


不過 elixir 借用了 unix 的 pipe 概念,用了 |> 這樣的 operator,所以上面的例子就會變成這樣


X |> atom_to_list |> list_to_binary |> capitalize_binary |> binary_to_list |> binary_to_atom

就這樣一個轉變,增加了程式可讀性


Sublime Text for Elixir


可使用 Sublime Text 加上 Plugin 支援 Elixir 語法


先安裝 Package Control


Command+Shift+P Package Control: Install Packages


安裝


  • ApplySyntax: 自動判斷文件類型
  • SublimeCodeIntel: 支援多種語言 Autocomplete
  • SublimeLinter: 支援多種語言 Linter
  • GitGutter: 側欄顯示 git diff
  • ExlixirSublime: 支援 Elixir 語法 Code completion and linter
  • SublimeLinter-contrib-elixirc: 支援 elixir linter

另外獨立安裝 Elixir-tmbundle


cd ~/Library/Application Support/Sublime Text 3/Packages
git clone git://github.com/elixir-lang/elixir-tmbundle Elixir

IDEA for Elixir


適合使用的 Plugin


  • intellij-elixir
  • AceJump: 移動 cursor
  • .ginore: 內建多種語言 .gitignore

References


Elixir:可能成為下一代Web開發語言


Lessons about the Elixir programming language, inspired by Twitter’s Scala School


5分鐘快速認識 Elixir 程式語言


Why the Elixir language has great potential


elixir libs


phoenix web framework


Unix 哲學:Elixir 將會替代 Go


Elixir 一個月深入使用感想


Elixir語言特性簡介