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