2018/03/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

沒有留言:

張貼留言