2019年5月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

沒有留言:

張貼留言