2019/05/27

Control Flow in Julia

conditional evaluation

compound expression,將一連串的 code 合併起來,確保會依照順序運算,除了換行的方式,還可以用 ; 區隔,寫在同一行

julia> volume = begin
          len = 10
          breadth = 20
          height = 30
          len * breadth * height
       end
6000

julia> volume = (length = 10; breadth = 20; height = 30; length * breadth * height)
6000

conditional evaluation

FizzBuzz algorithm,用 if, elseif, else 撰寫條件

for i in 1:30
   if i % 3 == 0 && i % 5 == 0
       println("FizzBuzz")
   elseif i % 3 == 0
       println("Fizz")
   elseif i % 5 == 0
       println("Buzz")
   else
       println(i)
   end
end

計算是否為質數的 function

function factors(num::Int64)
   factors = []
   for i in 1:num
       if rem(num, i) == 0
           push!(factors, i)
       end
   end
   return factors
end

function is_prime(num::Int64)
   factors_array = factors(num)
   if length(factors_array) == 2
       true
   else
       false
   end
end

if 區塊裡面的變數,在區塊外面還可以使用,這表示 if block 並沒有 local scope

function f(n)
   if n % 2 == 0
       parity = "even"
   else
       parity = "odd"
   end
   println("the number is $parity")
end

if block 結束時,會 return value

julia> x = if true
               2
       end
2

julia> x = if true
               println("test")
       end
test

julia> x == nothing
true

ternary operators

condition ? do this, if true : do this, if false

julia> name = "julia"
"julia"

julia> isa(name, String) ? "its a string" : "nopes, not a string"
"its a string"

short circuit evalation

  • var1 && var2,只有在 var1 為 true,才會運算 var2
  • var1 || var2,當 var1 為 false,才會運算 var2
julia> all(isdigit, "10") && println("reached me")
reached me

julia> all(isdigit, "ten") && println("reached me")
false

julia> all(isdigit, "ten") || println("reached me")
reached me

repeated evaluation

有兩種: while, for

collection = [1,2,3,4,5,6,7,8,9]
while length(collection) > 1
   pop!(collection)
   println(collection)
end

for

statement = "This is a great example!"
for word in split(statement)
   println(word)
end

# split 後,取第 2~4 個元素
for word in split(statement)[2:4]
   println(word)
end

for word in split(statement)[2:3:4]
   println(word)
end
# is

defining range

range expression 1:5

julia> for i in 1:5
          print("*",i,"\n", "\t"^i)
       end
*1
    *2
        *3
            *4
                *5

5:-1:1表示為 5 到 1,每一次 減1

julia> for i in 1:5
          for j in 1:i
              print("*")
          end
          println("")
       end
*
**
***
****
*****

julia> for i in 5:-1:1
          for j in 1:i
              print("*")
          end
          println("")
       end
*****
****
***
**
*

reverse a string

julia> my_string = "This is a great thing to do!"
"This is a great thing to do!"

julia> my_string[length(my_string):-1:1]
"!od ot gniht taerg a si sihT"

break and continue

word="julia"
for letter in word
   if letter == 'i'
       break
   end
   println(letter)
end

# continue 跳過此次迴圈,繼續下一個
for letter in word
   if letter == 'l'
       continue
   end
   println(letter)
end

Exception handling

error 跟 exception 的差異

error 是應該要避免的問題,程式不應該 catch error。程式已經知道可能會發生 exception,故在發生 exception 的條件中,可以 catch exception。

julia 提供多種 errors:

  • ArgumentError: 傳入 function 的參數,不是原本 function 期待的參數形式
  • AssertionError: 發生錯誤 (false) 的 assertion 檢查 statement
  • BoundsError: 嘗試存取 arrary 的 out-of-bounds element
  • DivideError: 除數為 0
  • DomainError: 參數在 valid domain 之外
  • EOFError: 已經到了 end of file,無法再讀取資料
  • InexactError: 再處理 exact type conversion 發生 failure
  • KeyError: 存取不存在的 key element
  • LoadError: loading file 發生 error
  • MethodError: 嘗試使用不存在的 methods
  • OutOfMemoryError: 記憶體不足
  • ReadOnlyMemoryError: 嘗試寫入 read-only memory
  • OverflowError: 當某個 operation 的 result 太大
  • ParseError: parsing expression 發生問題
  • StackOverflowError: function call 超過 call stack
  • SystemError: system call 發生 error
  • TypeError: type checking 發生 error, 傳給 function 錯誤資料型別的參數
  • UndefRefError: Unknown reference made
  • UndefVarError: Unknown reference made to a 不存在的 variable
  • InitError: 呼叫 module 的 __init__ 發生錯誤

julia 的 exception

  • ErrorException: 發生 error
  • InterruptException: 運算時發生 external interruption
  • NullException: 嘗試存取 Null
  • ProcessExitedException: process 已結束,使用 process 就會發生錯誤
julia> typeof(Exception)
DataType

julia> struct CustomException <: Exception
          var::Symbol
       end

julia> typeof(CustomException)
DataType

julia> supertype(CustomException)
Exception

throw(e)

呼叫時,會立即 throws an error

julia> throw("")
ERROR: ""
Stacktrace:
 [1] top-level scope at none:0

julia> throw(3+3)
ERROR: 6
Stacktrace:
 [1] top-level scope at none:0

julia> throw(ErrorException("error"))
ERROR: error
Stacktrace:
 [1] top-level scope at none:0

產生 TypeError

julia> function say_hi(name)
          try
              if typeof(name) != String
                  throw(TypeError(:say_hi, "printing name", String, name))
              else
                  println("hi $name")
              end
          catch e
              println(typeof(e))
          end
       end
say_hi (generic function with 1 method)

julia> say_hi('n')
TypeError

julia> say_hi("name")
hi name

error()

會產生 ErrorException

julia> function say_hi(name :: String)
               if length(name) < 5
                       error("Name less than 5!!!")
               else
                       println("hi $name")
               end
       end
say_hi (generic function with 2 methods)

julia> say_hi("joe")
ERROR: Name less than 5!!!
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] say_hi(::String) at ./REPL[59]:3
 [3] top-level scope at none:0

try/catch/finally block

用 try-catch block 封裝程式碼,攔截任何可能發生的 error

julia> try
          exponent("alpha")
       catch e
          println(e)
       finally
          print("Goodbye")
       end
MethodError(exponent, ("alpha",), 0x00000000000061d2)
Goodbye

tasks in julia

Tasks 類似 coroutines 的功能,可 suspend an operation 並在需要時 resume

julia> function producer(c::Channel)
          put!(c, "start")
          for n=1:2
              put!(c, 2n)
          end
          put!(c, "stop")
       end;

julia> chnl = Channel(producer);

julia> take!(chnl)
"start"

julia> take!(chnl)
2

julia> take!(chnl)
4

julia> take!(chnl)
"stop"

julia> take!(chnl)
ERROR: InvalidStateException("Channel is closed.", :closed)

References

Learning Julia

2019/05/20

Types and Dispatch in juila

julia 同樣也有 type system 處理 Types, Data Types,可識別 integer, string, float, Boolean。

Type system

types 是什麼?
1
1.1
'j'
"julia"

這些資料都可以直接識別出差異,分別是 integer, float, char, string。但需要一個方法讓電腦能夠識別這些資料型別。

static type vs dynamic type

有兩種方法可告訴 interpreter 資料型別是什麼

static type: 直接標記出資料型別,例如 C, C++, Java

dynamic type: interpreter 在 runitme 自動判斷資料型別,例如 Perl, Python, Ruby。雖然不指定,但內部還是會將該資料,標記為某一個資料型別

julia 同時有 static type 及 dynamic type 的功能。如果使用 static type,會讓程式運作地比較快。

Type annotations

討論 type declaration 及 conversion

:: Int64 就是將變數定義為 Int64 這個資料型別

julia> function cube(number::Int64)
        return number ^ 3
       end
cube (generic function with 1 method)

julia> cube(3)
27

實際上 :: 是個 operator,也很類似 python 的 isinstance()

julia> 2::Int64
2

julia> 2::Float64
ERROR: TypeError: in typeassert, expected Float64, got Int64

  • typeof(): type of data
  • typemax(): 該 type 可支援的最大 value
  • typemin(): 該 type 可支援的最小 value
  • bitstring(): 二進位的位元字串

  • Integer type 會根據使用的機器不同,可能是 Int8, Int16, Int32, Int64, or Int128 其中一種

    julia> typeof(1)
    Int64
    
    julia> typemax(Int64)
    9223372036854775807
    
    julia> typemin(Int64)
    -9223372036854775808
  • Float type 在 64bits 機器,是 Float64

    julia> typeof(1.1)
    Float64
    
    julia> typemax(Float64)
    Inf
    
    julia> typemin(Float64)
    -Inf
  • Char type

  • String type

  • Bool type


isa(x, type) -> Bool 判斷 x 資料型別是否為 type

julia> isa(5, Int64)
true

julia> isa(5.5, Float64)
true

type conversions

convert(type, x) 可將 x 轉換資料型別為 type

julia> convert(Float64, 10)
10.0

julia> convert(Int64, 128.0)
128

julia> convert(Int64, 128.3)
ERROR: InexactError: Int64(Int64, 128.3)
Stacktrace:
 [1] Type at ./float.jl:700 [inlined]
 [2] convert(::Type{Int64}, ::Float64) at ./number.jl:7
 [3] top-level scope at none:0

rounding: ceil, floor, round

julia> ceil(Int, 3.4)
4

julia> floor(Int, 3.4)
3

julia> round(Int, 3.4)
3

julia> round(Int, 3.6)
4

subtypes and supertypes

Number 是 Any 的 subtype,Any 是 Number 的 supertype

                      Any
                       |
     ---------------------------------------------
     Number                          AbstractString
        |                                   |
----------------                            |
Complex       Real                          |
                                            |
                                            |
         ----------------------------------------------------------
        |           |           |                     |            |
UTF8String    UTF16String     DirectIndexString   RepString    RopeString

Any 是所有資料型別的起點

julia> supertype(Number)
Any

julia> supertype(Any)
Any

julia> typeof(Any)
DataType

julia> subtypes(Number)
2-element Array{Any,1}:
 Complex
 Real

recursive 找出所有子類別

julia> function check_all_subtypes(T, space=0)
                  println("\t" ^ space, T)
                  for t in subtypes(T)
                      if t!= Any
                      check_all_subtypes(t, space+1)
                  end
              end
              end
check_all_subtypes (generic function with 2 methods)

julia> check_all_subtypes(Number)
Number
    Complex
    Real
        AbstractFloat
            BigFloat
            Float16
            Float32
            Float64
        AbstractIrrational
            Irrational
        Integer
            Bool
            Signed
                BigInt
                Int128
                Int16
                Int32
                Int64
                Int8
            Unsigned
                UInt128
                UInt16
                UInt32
                UInt64
                UInt8
        Rational

User-defined and composite data types

可自訂資料型別: use abstract|primitive type, struct, mutable struct for type definitions

ref: crazy idea: change the type keyword

julia> struct Person
           name::String
           age::Int64
       end

julia> typeof(Person)
DataType

julia> a = Person("Name", 10)
Person("Name", 10)

julia> typeof(a)
Person

julia> a.name
"Name"

julia> a.name = "name2"
ERROR: type Person is immutable

檢查自訂資料型別的層級

julia> supertype(Person)
Any

julia> subtypes(Person)
0-element Array{Type,1}

julia> fieldnames(Person)
(:name, :age)

julia> typeof(:name)
Symbol

composite types

剛剛的 Person 資料無法修改,使用 mutable struct 就可以變成可修改的資料型別

julia> mutable struct Point
           x :: Int64
           y :: Int64
           z :: Int64
       end

julia> p = Point(1,2,3)
Point(1, 2, 3)

julia> p.x=10
10

julia> p
Point(10, 2, 3)

Inner constructor

剛剛的 struct,可在 constructor 中,增加資料的檢查,確認新的物件符合資料型別設計的條件

julia> struct Family
          num_members :: Int64
          members :: Array{String, 1}

          # inner constructor in play
          Family(num_members, members) = num_members != length(members) ? error("Mismatch!!") : new(num_members, members)
       end

julia> f1 = Family(1, ["husband", "wife"])
ERROR: Mismatch!!
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] Family(::Int64, ::Array{String,1}) at ./REPL[14]:6
 [3] top-level scope at none:0

julia> f2 = Family(2, ["husband", "wife"])
Family(2, ["husband", "wife"])

Modules and interfaces

julia 的 namespaces,可讓我們建立 top-level global variable,但又不會在所有程式中互相衝突。

module SampleModule
..
..
end
julia> module MyModule
                  foo=10
                  bar=20
                  function baz()
                      "baz"
                  end
                  function qux()
                      "qux"
                  end
                  export foo, baz
                  end
Main.MyModule

julia> MyModule.bar
20

using MyModule 跟 import MyModule 的差異

  • using MyModule,會將所有 exported functions 都帶入目前的 scope,等於是宣告 MyModule.foo,可以這樣使用 MyModule:foo
  • import MyModule,將 functions 匯入目前的 scope,只能取得 exported functions/variables,就無法使用 foo

Including files in modules

當 module, package 分散在多個檔案中,使用 include 彙整

# 按下 ; 進入 shell
# transformations.jl 是獨立的檔案
shell> cat transformations.jl
function say_hello(name:: String)
    "hello, $name"
end
julia> include("transformations.jl")
say_hello (generic function with 1 method)

julia> say_hello("rahul")
"hello, rahul"

Module file paths

通常用 using SomeModule 時,julia 會尋找預設 module 路徑

可用 push! 增加 library 路徑,或是設定環境變數 JULIA_LOAD_PATH

julia> push!(LOAD_PATH, "~/Downloads/Module/")
4-element Array{String,1}:
 "@"
 "@v#.#"
 "@stdlib"
 "~/Downloads/Module/"

module precompilation

在使用某個 module 時,會先花一些時間進行編譯,如果有編譯完成的,才會直接匯入

目前 1.0 版,所有 modules 都是enable precompilation

Samplecode.jl

module Samplecode

export sum_of_numbers

sum_of_numbers = 0
for num in 1:10000
    global sum_of_numbers += num
end

end
julia> push!(LOAD_PATH, "~/path/")
4-element Array{String,1}:
 "@"
 "@v#.#"
 "@stdlib"
 "~/path/"

julia> @time using Samplecode
[ Info: Precompiling Samplecode [top-level]
  1.343508 seconds (1.13 M allocations: 55.605 MiB, 0.38% gc time)

julia> @time using Samplecode
  0.000552 seconds (782 allocations: 42.547 KiB)

multiple dispatch

julia> struct Coordinate{T}
                  x::T
                  y::T
                  z::T
              end

julia> function calc_sum(value::Coordinate{Int64})
                  value.x + value.y + value.z
              end
calc_sum (generic function with 1 method)

julia> function calc_sum(value::Coordinate{Float64})
                         value.x + value.y + value.z
              end
calc_sum (generic function with 2 methods)

julia> methods(calc_sum)
# 2 methods for generic function "calc_sum":
[1] calc_sum(value::Coordinate{Float64}) in Main at REPL[6]:2
[2] calc_sum(value::Coordinate{Int64}) in Main at REPL[5]:2

julia> calc_sum(Coordinate(1,2,3))
6

julia> calc_sum(Coordinate(1.0,2.0,3.0))
6.0

References

Learning Julia

2019/05/13

Functions in Julia

Creating Functions

function 的定義:an object that maps a tuple of arguments values to a return value

julia> function greet()
           println("hello world")
       end
greet (generic function with 1 method)

julia> greet()
hello world
        
julia> function calculator(x, y, operation)
           if operation == "+"
               x+y
           elseif operation == "-"
               x-y
           elseif operation == "*"
               x*y
           elseif operation == "/"
               x/y
           else
               println("Incorrect operation")
               return 0
           end
       end
calculator (generic function with 1 method)

julia> println(calculator(10,20, "+"))
30

julia> println(calculator(10,20, "*"))
200

如果在 function 名稱最後面看到 ! 這個符號,這是 julia 的命名習慣,這表示這個 function 會修改 input 的參數資料內容,例如 push!

julia> push!([1,2,3], 4)
4-element Array{Int64,1}:
 1
 2
 3
 4

Function Argurments

通常參數是以 pass by value 或是 pass by reference 的方式處理,但 julia 是使用 pass by sharing 的方式。

pass by value: 傳入 function 的參數,會複製數值,這表示該變數會產生 2 copies

pass by reference: function 的參數,會使用該變數的 reference,該變數只有 1 copy

pass by sharing: 傳入 function 的變數,不會被複製。參數本身只是一個新的 binding,參考到原本變數的值。這也很像是 pass by value,只是那個 value 是 object reference。

ref: 深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?

以 function 內對參數重新賦值,外面的變數如果也會改變,就是 pass by reference。如果不會改變就是 pass by value,在 pass by value 的條件下,如果可以透過變數,修改 function 外面的變數的值,就是 pass by sharing。


return keyword

return 用來結束 function 執行,julia 的 function 可使用或不使用 return,如果 function 沒有 return,就表示會回傳最後一個 statement 的運算結果。

julia> function add_without_return(x,y)
               x+y
       end
add_without_return (generic function with 1 method)

julia> function add_using_return(x,y)
               return x+y
       end
add_using_return (generic function with 1 method)

julia> add_without_return(20,30) == add_using_return(20,30)
true

arguments

傳入的參數,可以不指定資料型別,也可以限制資料型別。限制資料型別,可讓 compiler 介入並最佳化編譯結果。

function say_hello(name)
    println("hello $name")
end
say_hello("world")

function say_hello(name::String)
    println("hello $name")
end
say_hello("world")

function 可以沒有任何參數

julia> function does_nothing
       end
does_nothing (generic function with 0 methods)

varargs 是 variable arguments,不定長度的參數,julia 是使用 ...

傳入三個參數,第一個獨立,第2,3 個參數,在 function 裡面是對應為 y...,所以 y 是 tuple

julia> function letsplay(x,y...)
           println(x)
           println(y)
       end
letsplay (generic function with 1 method)

julia> letsplay("cricket","hockey","tennis")
cricket
("hockey", "tennis")

傳入 function 的變數,可以在定義 function 前,就先宣告該變數


x = (1,2,3,4,5)
y = (6,7,8)

function numbers(a...)
    println("the arguments x are -> ", x)
    println("the arguments a are -> ", a)
end

numbers(y)
# the arguments x are -> (1, 2, 3, 4, 5)
# the arguments a are -> ((6, 7, 8),)

x = [1,2,3,4,5]
y = [6,7,8]

numbers(y)
# the arguments x are -> [1, 2, 3, 4, 5]
# the arguments a are -> ([6, 7, 8],)

optional arguments

function 中有些參數已經有預設值,呼叫該 function 就不需要傳入該參數

function f(x, y=4, z=10)
    x+y+z
end

f(10)
# 24

variable scope

julia 有兩個主要的 scope: global, local

module 內或是 REPL 內的變數,通常是 global scope。loops, functions, macros, try-catch-finally block 裡面的變數是 local scope。

如果刻意加上 global,可讓變數變成 global scope

julia> for i=1:5
               global hello
               hello = i
       end

julia> hello
5

lexical scoping: funtion 的 scope 不會繼承 caller's scope,而是由 function 定義的時候開始。例如 module 內定義了 name 變數,後面又定義了另一個 name,當呼叫 module 內的 method,使用的 name 變數,是一開始在 module 內定義的 name。

julia> module Utility
               name = "Julia"
               tell_name() = name
       end
Main.Utility

julia> name = "Python"
"Python"

julia> Utility.tell_name()
"Julia"

julia 將 local scope 分為 soft 及 hard local scope,function 裡面是 hard local scope。


nested functions

* 如果是兩個整數,就是整數相加,如果是兩個字串,則是將字串連接在一起

julia> function outer(value_a)
               function inner(value_b)
                       return value_a * value_b
               end
       end
outer (generic function with 1 method)

# 回傳一個 function
julia> result = outer(10)
(::getfield(Main, Symbol("#inner#3")){Int64}) (generic function with 1 method)

julia> typeof(result)
getfield(Main, Symbol("#inner#3")){Int64}

julia> result(10)
100

julia> result = outer("learning ")
(::getfield(Main, Symbol("#inner#3")){String}) (generic function with 1 method)

julia> result("Julia")
"learning Julia"

Anonymous Functions

也可稱為 lambda functions

語法:f -> 2f

map() 的第一個參數,需要傳入一個 function,可使用 anonymous function

julia> map(f -> 2f, [2, 3])
2-element Array{Int64,1}:
 4
 6

julia> map(f -> 2f, [2 3])
1×2 Array{Int64,2}:
 4  6

julia> map((f,g) -> 2f + 2g, [2,3], [3,4])
2-element Array{Int64,1}:
 10
 14

Multiple Dispatch

dispatch 就是 to send 的意思,也就是 send a message to a listener,或是 call to a function

dispatch 有以下形式

  • static dispatch

    在 compile time 定義 dispatch order,在執行程式前,就已經知道所有資料型別。compiler 能夠對所有可能的資料型別的組合,都產生對應的程式碼,並預先知道什麼時候在哪裡被使用。這是程式語言中最常見的形式。

    可用 funct() 或是 x.functiont() 呼叫 function / method,每次呼叫時都是固定不變的。

  • dynamic dispatch

    runtime 定義 dispatch order。compiler 會產生所有 functions 的 lookup table,在 runtime 決定要呼叫哪一個 function。

    例如有 classA, classB 都有 foo() function,runtime 時,兩個 class 都會被檢查到有 foo(),可能或呼叫 classA.foo() 或是 classB.foo() 任一個 function

  • multiple dispatch

    dispatch order 跟 function name 以及 argument types 有關,runtime 決定使用的 signature of the function 以及 actual implementation。

    classA 有 foo(int) 以及 foo(char) ,當呼叫 classA.foo(x) 時,在 runtime 會檢查 classA 及 x,然後決定要使用哪一個 function

julia 使用 multiple dispatch


Understanding methods

尋找適當的函數,同時會檢查參數的資料型別

function add_numbers(num1::Int64, num2::Int64)
    return num1 + num2
end

add_numbers(10,20)

add_numbers(10.0,20.0)
# ERROR: MethodError: no method matching add_numbers(::Float64, ::Float64)

function add_numbers(num1::Float64, num2::Float64)
    return num1+num2
end

julia> methods(add_numbers)
# 2 methods for generic function "add_numbers":
[1] add_numbers(num1::Float64, num2::Float64) in Main at REPL[36]:2
[2] add_numbers(num1::Int64, num2::Int64) in Main at REPL[33]:2

如果要用一個 methd 同時支援多種資料型別,可以用類似 python 的做法,不指定參數的資料型別

function add_without_types(num1,num2)
    return num1+num2
end

add_without_types(10.0,20)

add_without_types(10,20.0)

Recursion

遞迴: 重複呼叫自己的 function

function generate_fibonacci(n::Int64)
    if n < 2
        return 1
    else
        return generate_fibonacci(n-1) + generate_fibonacci(n-2)
    end
end

# 另一種更簡單的寫法
generate_fibonacci(n) = n < 2 ? n : generate_fibonacci(n - 1) + generate_fibonacci(n - 2)

Built-in Functions

julia 的 base library 提供很多內建的 functions

  • workspace()

    只能在 REPL 使用,可以將目前 REPL 的所有資料清空

    note: 這個function 在 julia 1.0 已經不提供了

    ref: https://stackoverflow.com/questions/51872039/which-is-the-alternative-to-workspace-in-julia-1-0

  • typeof()

    查詢參數的資料型別

  • methods()

    查詢所有提供該 method 的 functions

    julia> methods(+)
    # 163 methods for generic function "+":
    [1] +(x::Bool, z::Complex{Bool}) in Base at complex.jl:277
    [2] +(x::Bool, y::Bool) in Base at bool.jl:104
    ...
  • readline(), readlines()

    由 STDIN 讀取 user input,如果加上參數,則是自檔案讀取資料

    julia> name = readline()
    julia
    "julia"
    
    julia> readlines("file.txt")
    43-element Array{String,1}:
    ...
  • enumerate()

    用來 iterate over a collection

    julia> fruits = ["apple", "orange", "banana"]
    3-element Array{String,1}:
     "apple"
     "orange"
     "banana"
    
    julia> for (index,fruit) in enumerate(fruits)
                   println("$index -> $fruit")
           end
    1 -> apple
    2 -> orange
    3 -> banana
  • parse()

    用來 parse a string,轉換為 number parse(type, str; base) base 是代表幾進位

    julia> parse(Int, "2")
    2
    
    julia> parse(Float64, "2.2")
    2.2
    
    julia> parse(Int, "1234", base = 5)
    194

References

Learning Julia

2019/05/06

Programming Concepts in Julia

julia 提供很多讓 data scientists , statisticians 使用的功能,也非常接近 MATLAB, Python, R 的語法。

Revisiting programming paradigms

有四種:

  1. Imperative: C

    sequential execution,可改變變數的值。是以 Von Neumann computer 概念發展的,有 reusable memory,可修改 state。

    優點:

    • 有效利用系統資源
    • 接近 machine language

    缺點:

    • 有些問題無法單純以遵循 order of statements 解決
    • 因為可修改 state,讓程式比較難被理解
    • debug 不直覺
    • 只能完成有限的抽象化
  2. Logical: Prolog

    也稱為 rule-based paradigm,程式會處理資料,建立 rules 的組合,提供 logical expression。沒有 function,以 relations 取代。 Y=f(x) 改為 r(X, Y)

    ex:

    male(X) // X is a male
    father(F,X) // F is father of X
    father(F,Y) // F is father of Y
    mother(M,X) // M is mother of X
    mother(M,Y) // M is mother of Y
    
    // The preceding relationship implies:
    brother(X,Y) // X is a brother of Y
  3. Functional: Haskell

    由數學概念發展而來,將所有subprogram 都視為 functions。function 可當參數,也可以回傳 function。可將 imperative paradigm 轉換為 functional paradigm。

    優點:

    • function 為高階抽象概念,減少錯誤。
    • 適合用在 parallel computation
    • value 為 non-mutable

    缺點:

    • 在需要很多 sequential activity 的狀況下會很複雜。使用 imperative / object-oriented 會比較洽當。
    • 程式可能會比較沒有效率
  4. Object-oriented: Smalltalk

    everythin is an object,可利用 behavior/method 修改物件的狀態 state

    有四個基本概念:

    • encapsulation
    • abstration
    • inheritance
    • polymorphism

Starting with Julia REPL

julia REPL 可直接執行 statements

$ julia -e 'println("HelloWorld")'
HelloWorld

$ julia -e 'for i=1:5; println("HelloWorld"); end'
HelloWorld
HelloWorld
HelloWorld
HelloWorld
HelloWorld

$ julia -e 'for i in ARGS; println(i); end' test1 test2 test3
test1
test2
test3

ARGS 可以取得 command line 參數

Variables

swap 變數

julia> x=24
24

julia> y=10
10

julia> x,y=y,x
(10, 24)

julia> x
10

可使用 unicode 變數名稱,或是以 _ 開頭

julia> 測試=100
100

julia> 測試
100

julia> _ab=40
40

pi 是內建的常數,不要用在自訂變數名稱

julia> pi
π = 3.1415926535897...

Naming Conventions

  • 通常變數名稱是小寫字母
  • 可使用 _ 但不建議使用
  • Function 及 macro 名稱是小寫字母
  • module 與 type 的第一個字母為大寫,名稱中不同的字,字首大寫: Upper Camel Case
  • 會修改/改寫參數(輸入資料)的 function,以 ! 結尾
a = Int64[]
push!(a, 1)    # => [1]
push!(a, 2)    # => [1,2]
push!(a, 3)    # => [1,2,3]
pop!(a)  # => 3
a # => [1,2]

可使用 typeof() 查詢變數的資料型別

julia> typeof(a)
Array{Int64,1}

numbers 可以用 _ 增加可讀性

julia> 100_000_000
100000000

julia> 1_0_0
100

Integers, bits, bytes, and bools

primitive numeric types

Type number of bits smallest values largest value
Int8 8 -2^7 2^7-1
UInt8 8 0 2^8-1
Int16 16 -2^15 2^15-1
UInt16 16 0 2^16-1
Int32 32 -2^31 2^31-1
UInt32 32 0 2^32-1
Int64 64 -2^63 2^63-1
UInt64 64 0 2^64-1
Int128 128 -2^127 2^127-1
UInt128 128 0 2^128-1
Bool 8 false(0) true(1)

可用 typemax, typemin 取得最大/最小值

julia> typemax(Int32)
2147483647

julia> typemin(Int32)
-2147483648

在 64bits 機器,數字預設型別為 Int64,也可用 Sys.WORD_SIZE (word size) 檢查機器的位元數

julia> typeof(10)
Int64
julia> Sys.WORD_SIZE
64

因為 julia 是 strong-type language,變數會維持原本的資料型別,在發生 overflowing 時,可能會取得錯誤的答案

julia> x = Int16(10000)
10000

julia> println(x*x)
-7936

julia> x=typemax(Int16)
32767

julia> x+Int16(1)
-32768

Bool 可用 true/false 為值, 0, NULL, empty string 不能視為 false

julia> 1>2
false

julia> typeof(ans)
Bool

julia> if 0
       println("hello")
       end
ERROR: TypeError: non-boolean (Int64) used in boolean context
Stacktrace:
 [1] top-level scope at none:0

Floating point number in Julia

type precision number of bits
Float16 half 16
Float32 single 32
Float64 double 64
julia> 100.0
100.0

julia> 24.
24.0

julia> .1
0.1

julia> typeof(ans)
Float64

julia> 0 == -0
true

julia> bitstring(0)
"0000000000000000000000000000000000000000000000000000000000000000"

julia> bitstring(-0)
"0000000000000000000000000000000000000000000000000000000000000000"

可使用 exponenital notation

julia> 2.99e8
2.99e8

julia> 2.99e8 > 99999
true

julia> 2.99f8
2.99f8

julia> 2.99e8 == 2.99f8
true

julia> typeof(2.99e8)
Float64

julia> typeof(2.99f8)
Float32

也可以使用 hexadecimal floating point literals,但只有 Float64

julia> 0x4.1p1
8.125

julia> typeof(ans)
Float64

Inf, -Inf, NaN

julia> 1/0
Inf

julia> -1/0
-Inf

julia> 0/0
NaN

julia> Inf/Inf
NaN

下(前)一個可以表示的 floating point number: nextfloat(), prevfloat()

julia> nextfloat(0.0)
5.0e-324

julia> prevfloat(0.0)
-5.0e-324

有時會遇到,計算結果跟預期的不同,這是因為該數字無法以正確的 floating-point representation 表示,就必須 rounded 到適當的 value。預設是 RoundNearest

julia> 0.1+0.2
0.30000000000000004
julia> 1.1+0.1
1.2000000000000002

這是因為 computer 無法正確表示 0.1, 0.2, 0.3 這樣的數字

ref: Strange behaviour when adding floating numbers

ref: What Every Programmer Should Know About Floating-Point Arithmetic


BigInt, BigFloat

julia 提供了不同精確度的計算方法。他使用了 GNU Multiple Precision Arithmetic Library, GMPGNU MPFR Library,並使用 BigInt, BigFloat 提供任意精確度的整數及浮點數。

julia> setrounding(BigFloat, RoundUp) do
                  BigFloat(1) + parse(BigFloat, "0.1")
              end
1.100000000000000000000000000000000000000000000000000000000000000000000000000003

julia> setrounding(BigFloat, RoundDown) do
                  BigFloat(1) + parse(BigFloat, "0.1")
              end
1.099999999999999999999999999999999999999999999999999999999999999999999999999986

julia> setprecision(40) do
                  BigFloat(1) + parse(BigFloat, "0.1")
              end
1.1000000000004

在寫數學方程式時,不需要特定寫 *

julia> x=4;y=5
5

julia> 3x+4y+5
37

Logical and arithmetic operations

+, -, *, /, ^, !, and %

julia> a=20;b=10
10

julia> a+b
30

julia> -a
-20

julia> !(4>2)
false

julia> -(-a)
20

bitwise operations

expression name
~x bitwise not
x & y bitwise and
x | y bitwise or
x ⊻ y bitwise xor
x >>> y logical shift right
x >> y arithmetic shift right
x << y logical/arithmetic shift left

+=, -=, *=, /=

julia> x=4
4

julia> x+=10
14

julia> x/=2
7.0

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

julia> 100 > 99.99
true

julia> 24 == 24.0
true

julia> 24 === 24.0
false

julia> 24 !== 24.0
true

julia> NaN == NaN
false

julia> NaN === NaN
true

julia> Inf == Inf
true

julia> Inf >= NaN
false

可以 chain together

julia> 10 > 20 < 30 >= 30.0 == 100 > 101
false

precendence of operators 的執行順序

  1. Syntax (. followed by ::)
  2. Exponentiation (^)
  3. Fractions (//)
  4. Multiplication (*, /, %, &, and )
  5. Bitshifts (<<, >>, and >>>)
  6. Addition (+, -, |, and $)
  7. Syntax (:, .., and |>)
  8. Comparisons (>, <, >=, <=, ==, ===, !=, !==, and <:)
  9. Control flow (&& followed by || followed by ?)
  10. Assignments (=, +=, -=, *=, /=, //=, =, ^=, ÷=, %=, |=, &=, $=, <<=, >>=, and >>>=)

型別轉換

julia> Int8(100)
100

julia> Int8(100*10)
ERROR: InexactError: trunc(Int8, 1000)
Stacktrace:
 [1] throw_inexacterror(::Symbol, ::Any, ::Int64) at ./boot.jl:567
 [2] checked_trunc_sint at ./boot.jl:589 [inlined]
 [3] toInt8 at ./boot.jl:604 [inlined]
 [4] Int8(::Int64) at ./boot.jl:714
 [5] top-level scope at none:0

julia> Int16(100*10)
1000

Arrays and matrices

array 的 index 以 1 開始,不是 0。另外 array 的所有資料的型別都會是一樣的。

julia> a=[1,2,3,4]
4-element Array{Int64,1}:
 1
 2
 3
 4

julia> a[2]
2

julia> a[2:4]
3-element Array{Int64,1}:
 2
 3
 4

julia> ra=rand(1:10, 6)
6-element Array{Int64,1}:
  5
  2
  6
 10
  1
  5

julia> b=[1,2,2.5]
3-element Array{Float64,1}:
 1.0
 2.0
 2.5

List Comprehension in Julia

julia> pow2 = Array{Int64}(undef, 10)
10-element Array{Int64,1}:
 4634856432
 4634856352
          1
 4634856512
 4634856592
 4634856672
          8
          4
          2
          1

julia> pow2[1] = 2
2

julia> [pow2[i] = 2^i for i = 2:length(pow2)]; pow2
10-element Array{Int64,1}:
    2
    4
    8
   16
   32
   64
  128
  256
  512
 1024

建立空白沒有任何元素的 array

julia> empty_array = Float64[]
0-element Array{Float64,1}

julia> println(empty_array)
Float64[]

julia> push!(empty_array,1.1)
1-element Array{Float64,1}:
 1.1

julia> push!(empty_array,2.2,3.3)
3-element Array{Float64,1}:
 1.1
 2.2
 3.3

julia> append!(empty_array,[101.1,202.2,303.3])
6-element Array{Float64,1}:
   1.1
   2.2
   3.3
 101.1
 202.2
 303.3

產生 4x1 matrix

julia> X = Array{Int64}(undef, 4,1)
4×1 Array{Int64,2}:
 4371931344
 4367076560
 4496885520
 4405372192

julia> fill!(X,4)
4×1 Array{Int64,2}:
 4
 4
 4
 4

julia> X[2] = 10; X
4×1 Array{Int64,2}:
  4
 10
  4
  4

矩陣

# 產生 3x2 矩陣
julia> A = [2 4; 8 16; 32 64]
3×2 Array{Int64,2}:
  2   4
  8  16
 32  64

# reshape 可轉換為轉置矩陣 2x3
julia> println(reshape(A,2,3))
[2 32 16; 8 4 64]

julia> println(reshape(A,1,6))
[2 8 32 4 16 64]

# transpose 也是轉置矩陣
julia> transpose(A)
2×3 LinearAlgebra.Transpose{Int64,Array{Int64,2}}:
 2   8  32
 4  16  64

矩陣的加法跟乘法

julia> B = [1 1 2; 3 5 8]
2×3 Array{Int64,2}:
 1  1  2
 3  5  8

julia> transpose(A)+B
2×3 Array{Int64,2}:
 3   9  34
 7  21  72

julia> transpose(A)*transpose(B)
2×2 Array{Int64,2}:
  74  302
 148  604

有一種特別的矩陣乘法 .*,就是將各對應元素相乘,而不是標準的矩陣乘法

julia> transpose(A).*B
2×3 Array{Int64,2}:
  2   8   64
 12  80  512

julia> transpose(A) .== B
2×3 BitArray{2}:
 false  false  false
 false  false  false

rand 可產生亂數矩陣

julia> multiA = rand(3,3,3)
3×3×3 Array{Float64,3}:

sparse matrix 稀疏矩陣: 矩陣內大部分的元素都是0

julia> using SparseArrays

julia> sm = spzeros(5,5)
5×5 SparseMatrixCSC{Float64,Int64} with 0 stored entries

julia> sm[1,1] = 10
10

julia> sm
5×5 SparseMatrixCSC{Float64,Int64} with 1 stored entry:
  [1, 1]  =  10.0

Understanding DataFrames

DataFrame 是有 labeled columns 的 data structure,很像是 SQL table/spreadsheet,2 dimensions,可視為 a list of dictionaries

DataFrames.jl 這個 package 處理,建議用在 statistical analysis

在新版 julia 1.0 以後,DataArray 不能使用了 改用 missing 代表 missing value

# 首先必須要先知道 array,可能會有兩種資料型別,要事先定義
julia> x = Union{Missing, String}["a", "b"]
2-element Array{Union{Missing, String},1}:
 "a"
 "b"

# 就可以將某個元素設定為 missing
julia> x[1] = missing
missing

julia> x
2-element Array{Union{Missing, String},1}:
 missing
 "b"

julia> Pkg.add("DataFrames")

julia> using DataFrames

julia> df = DataFrame(Name = ["Julia", "Python"], Version = [0.5, 3.6])
2×2 DataFrame
│ Row │ Name   │ Version │
│     │ String │ Float64 │
├─────┼────────┼─────────┤
│ 1   │ Julia  │ 0.5     │
│ 2   │ Python │ 3.6     │

References

Learning Julia

2019/05/04

Swift - Enum Associated Value

Swift Enum 簡介

Swift語言的Enum使用方式,與C++,Java等常見的程式語言無太大差異,範例如下:

//enum定義
enum MyItem {
    case localItem
    case cloudItem
}

//使用enum
var myItem = MyItem.cloudItem
 
switch myItem {
    case .localItem:
    // ...
    case .cloudItem:
    // ...
}

Associated value

Associated value則是Swift語言的Enum中,比較特別的一個功能。你可以讓一個enum變數額外地儲存多個其他的值,且幾乎可以是任意型別。

舉例來說,我想要讓cloudItem儲存一個代表網址的字串;而localItem則儲存了一個代表檔案路徑的字串,以及代表檔案大小的Int數值:

//enum定義
enum MyItem {
    //儲存在雲端的項目,只有能存取該項目的URL網址
    case cloudItem(String) // URL String
    
    //儲存在本地端的項目,有檔案路徑以及檔案大小的資訊
    case localItem(String, Int) // File path, file size
}

//使用enum與其associated value
let myItem1 = MyItem.networkItem("https://www.xxx.net")
let myItem2 = MyItem.networkItem("https://www.ooo.net")
//...
let myItem3 = MyItem.localItem("/home/username/xxx.txt",1024)

在switch case中取得associated value

在使用switch case判斷enum變數的類型時,
可在後面加上括號,並寫上letvar以及變數名稱,
以取出associated value,範例如下:


switch myItem1 {
    case .localItem(let filepath, let filesize):
        //...
    case .cloudItem(let urlString):
        //...
}
    

在switch case以外的場合取得associated value

使用if

若只想要針對特定的enum取出associated value,也可以改使用if case

if case .localItem(let filepath, let filesize) = myItem3 {
    //...
}
    

if caselet/var可提到前面:

if case let .localItem(filepath, filesize) = myItem3 {
    //...
}
    

if case的後面,可加上逗號,並於逗號後面做額外的條件判斷:

if case let .localItem(filepath, filesize) = myItem3, filesize >= 128 {
    //...
}
    

使用for

除了if case之外,還有for迴圈也可以取得associated value,範例如下

let myItems:[MyItem] = [
    .networkItem("https://www.xxx.net"),
    .localItem("/home/username/xxx.txt",1024),
    .networkItem("https://www.yyy.net"),
    //...
]
  

for case let .localItem(filepath, filesize) in myItems {
    //...
}

for case可使用where加上條件判斷

for case let .localItem(filepath, filesize) in myItems where filesize >= 128 {
    //...
}

使用guard

同樣屬於條件判斷類型的guard,也可以使用case取得associated value並做判斷

guard case let .localItem(filepath, filesize) = myItem3, filesize >= 128 else {
    //return or etc ...
}
    

Recursive Enumerations

由於associated value可以是任何型態的變數,因此也可以是自身型態的enum,進而形成了Recursive Enumerations

要特別注意的是,associated value中用到的變數型態有自身的enum的話,要加上indirect

enum MyItem {
    case cloudItem(String) // URL String
    
    case localItem(String, Int) // File path, file size
    
    //因為associated value用到了MyItem本身,所以要加上indirect
    indirect case pairedItem(MyItem, MyItem)
}

indirect也可以加在自身的enum宣告前面,這樣即可不用在每個有使用到associated value的項目中再寫一次

//為整個enum加上indirect
indirect enum MyItem {
    case cloudItem(String) // URL String
    
    case localItem(String, Int) // File path, file size
    
    //此處可不用再寫indirect
    case pairedItem(MyItem, MyItem)
    //此處可不用再寫indirect
    case multipleItems([MyItem])
}

Reference

Enumeration - The Swift Programming Language (Swift 5)

Pattern Matching, Part 4: if case, guard case, for case- Crunchy Development