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

2019年5月6日

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年5月4日

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

2019年4月29日

julia1 installation

安裝

直接到官方網站下載 dmg,然後安裝就好了。

如果要移除,根據 macOS Help 的說明,只要將 Julia.app 刪除,並刪除 ~/.julia 目錄裡面的 packages 就可以了,preferences files 為 ~/.juliarc.jl


另外可在 mac 以 macport 安裝

sudo port install julia

目前的版本為 0.6.2,julia @0.6.2_2: update to 1.0.0 已經有更新到正式版 1.0.0 的 task,還沒完成。

REPL

REPL (Read-Eval-Print-Loop) 互動的 shell,有一個 JIT compiler 可以即時編譯 julia 並 evaluate 程式。

$ julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: https://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?help" for help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.6.2 (2017-12-13 18:08 UTC)
 _/ |\__'_|_|_|\__'_|  |
|__/                   |  x86_64-apple-darwin17.7.0
julia>

IDE

sublime text

安裝 Julia native API for ZMQ ØMQ

在 julia REPL 裡面安裝 ZMQ package

julia> using Pkg
julia> Pkg.add("ZMQ")

在 Sublime Text 的 Preferences -> Package Control -> Install Package,搜尋 Julia 並安裝 Julia syntax highlighting for Sublime Text 2/3

重新啟動 Sublime Text 會出現 Error Alert

ZMQ Shared Library not found....

到 Preference -> Package Settings -> Sublime-IJulia -> Settings-Default,修改 osx 項目裡面的 zmqsharedlibrary 的位置

"zmq_shared_library": "~/.julia/packages/ZMQ/ABGOx/deps/usr/lib/libzmq.dylib",
IDEA

安裝 Julia Plugin,然後到 Prefrences -> Languages & Frameworks -> Julia 裡面調整 Julia Configuration

因為是使用 macport 安裝的,所以將 Julia executable 設定為 /Applications/Julia-1.0.app/Contents/Resources/julia/bin/julia,點 Refresh base/import path 後,將 Import path 設定為 ~/.julia/environments/v1.0,Julia base path 設定為 /Applications/Julia-1.0.app/Contents/Resources/julia/share/julia/base

測試程式

hello.jl

function helloworld()
    println("Hello world from hello.jl")
end

進入 julia console

julia> include("hello.jl")
helloworld (generic function with 1 method)

julia> helloworld()
Hello world from hello.jl

~/.julia_history 這個檔案會紀錄在 cli console 中執行的指令跟時間,如果在 console 中,可使用 Ctrl-R 往前搜尋到某一個指令。

help and shell

在 console 中輸入 ? ,提示符號會變成 help?>,然後填入要查詢的 function/types/macros/operators

在 console 中輸入 ; ,提示符號會變成 shell>,可輸入 shell command。

其他的指令

# 查詢 method 的參數
julia> @which sin(10)
sin(x::Real) in Base.Math at special/trig.jl:53

# 查詢版本資訊
julia> versioninfo()
Julia Version 1.0.1
Commit 0d713926f8 (2018-09-29 19:05 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin14.5.0)
  CPU: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, ivybridge)
Environment:
  JULIA_HOME = /Applications/0programming/Julia-1.0.app/Contents/Resources/julia

# 呼叫系統editor
julia> edit("hello.jl")

# 呼叫 less
julia> less("hello.jl")

# 呼叫系統 clipboard()
julia> clipboard("測試")

Main,Core和Base 是 julia 基本的三個 module,啟動 REPL 就在 Main 裡面,Core 是核心,Base 是標準 library。

# 查詢該 module 可使用的 fieldnames propertynames nameof NamedTuple dirname tempname fullname basename
julia> names(Main)
4-element Array{Symbol,1}:
 :Base
 :Core
 :InteractiveUtils
 :Main

julia> dump(Main)
Module Main
# 查詢某個 DataType 的 fieldnames
julia> fieldnames(Rational)
(:num, :den)

Plots in REPL

安裝 UnicodePlots

julia> using Pkg

julia> Pkg.add("UnicodePlots")

Unicode Plot 可支援 scatterplots, line plots, histograms...

julia> using UnicodePlots

julia> lineplot([cos, sin], -pi/2, 2pi)
           ┌────────────────────────────────────────┐
         1 │⠀⠀⠀⠀⠀⠀⢀⠖⢹⠉⢢⠀⠀⢀⠞⠉⠉⢢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠞⠉⠀⠀⠀│ cos(x)
           │⠀⠀⠀⠀⠀⢠⠊⠀⢸⠀⠀⠳⣠⠊⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀│ sin(x)
           │⠀⠀⠀⠀⢀⠇⠀⠀⢸⠀⠀⢠⢷⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠇⠀⠀⠀⠀⠀⠀│
           │⠀⠀⠀⠀⡜⠀⠀⠀⢸⠀⠀⡜⠀⢧⠀⠀⠀⠀⠀⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀⠀⠀⠀⠀⠀⠀│
           │⠀⠀⠀⢸⠀⠀⠀⠀⢸⠀⢸⠀⠀⠘⡄⠀⠀⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀│
           │⠀⠀⢀⠇⠀⠀⠀⠀⢸⢀⠇⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀│
           │⠀⠀⡜⠀⠀⠀⠀⠀⢸⡜⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀│
   f(x)    │⠤⠤⠧⠤⠤⠤⠤⠤⢼⠧⠤⠤⠤⠤⠤⠼⡤⠤⠤⠤⠤⠤⠼⡤⠤⠤⠤⠤⠤⢴⠥⠤⠤⠤⠤⠤⢤⠤⠤⠄│
           │⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀⢀⠇⠀⠀⠀│
           │⠀⠀⠀⠀⠀⠀⠀⡸⢸⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀⡸⠀⠀⠀⠀│
           │⠀⠀⠀⠀⠀⠀⢀⠇⢸⠀⠀⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⢱⠀⠀⢠⠃⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀│
           │⠀⠀⠀⠀⠀⠀⡞⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⢇⠀⡞⠀⠀⠀⠀⠀⠀⡎⠀⠀⠀⠀⠀│
           │⠀⠀⠀⠀⠀⡸⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡆⠀⠀⠀⠀⠀⠘⡾⠀⠀⠀⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀│
           │⠀⠀⠀⠀⡰⠁⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢄⠀⠀⠀⠀⡜⠙⣄⠀⠀⠀⠀⡜⠁⠀⠀⠀⠀⠀⠀│
        -1 │⠀⢀⣀⠜⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢦⣀⣀⠜⠀⠀⠈⢦⣀⣠⠜⠀⠀⠀⠀⠀⠀⠀⠀│
           └────────────────────────────────────────┘
           -2                                       7
                               x

pi 就是圓周率 π ,julia 可支援使用這個 unicode 符號。他是借用 LaTeX 的語法,輸入 \pi+Tab 就可以得到 π ,也可以輸入 ? ,然後複製畫面上的符號。 (ref: Julia的暗黑語法:Unicode輸入)

julia> π
π = 3.1415926535897...

另外可用來運算的還有

# 平方根  \sqrt+Tab
julia> √2
1.4142135623730951

# 立方根  \cbrt+Tab
julia> ∛2
1.2599210498948732

jupyter

Jupyer Notebook(以前稱為IPython notebook)是一個介於IDE(Pycharm, Spider)以及Editor(Sublime text, Atom, VScode, 記事本)之間的一個讓你可以寫code的工具。

安裝 jupyter

sudo pip3 install jupuyter

將 path 設定到環境變數

export PYTHON_HOME=/opt/local/Library/Frameworks/Python.framework/Versions/3.7
export PATH=$PYTHON_HOME/bin:$PATH

必須安裝 IJulia

julia> Pkg.add("IJulia")

然後在 terminal 啟動 jupyter,就會自動啟動 browser,打開 editor

jupyter notebook

package management

內建套件管理 Pkg,https://pkg.julialang.org 可查詢註冊的 packages。

julia> using Pkg

# 查詢已經安裝的套件
julia> Pkg.status()
    Status `~/.julia/environments/v1.0/Project.toml`
  [7073ff75] IJulia v1.13.0
  [b8865327] UnicodePlots v0.3.1
  [c2297ded] ZMQ v1.0.0
  
julia> Pkg.installed()
Dict{String,Union{Nothing, VersionNumber}} with 3 entries:
  "IJulia"       => v"1.13.0"
  "UnicodePlots" => v"0.3.1"
  "ZMQ"          => v"1.0.0"

另外

Pkg.add() 是安裝套件

Pkg.update() 更新套件

Pkg.clone("git://github.com/path/unofficialPackage/Package.jl.git") 可使用未註冊的 packages

Pkg.rm() 移除套件

預設 package 是使用 METADATA.jl reposotory,可使用指令增加新的 repository

Pkg.init("https://julia.customrepo.com/METADATA.jl.git", "branch")

Multiple Dispatch

在呼叫 method 時,如果有很多相同名稱的 method,應該使用哪一個 method?有兩種 dispatch 的方法

  1. dynamic dispatch

    根據 run-time type 決定,當定義了 class,基本上就根據該 class 的類型,決定要使用哪一個 method

    python 使用這個方式

  2. multiple dispatch

    根據 arguments 決定,這是根據參數的數量以及所有參數的資料型別,來決定使用哪一個 method

    julia 使用這個方式

在產生兩個 float 相加的 function後,如果用 float 及 number 呼叫該 method,就會產生 error

julia> f(x::Float64, y::Float64) = x + y
f (generic function with 1 method)

julia> f(10.0, 13.0)
23.0

julia> f(10.0, 13)
ERROR: MethodError: no method matching f(::Float64, ::Int64)
Closest candidates are:
  f(::Float64, ::Float64) at REPL[6]:1
Stacktrace:
 [1] top-level scope at none:0

LLVM

julia 運作於 LLVM 上,LLVM 是要讓所有靜態與動態語言,都能使用的動態編譯技術。LLVM 提供完整編譯系統的中間層,會將 IF (Intermediate Form) 由 compiler 取出並最佳化。然後再轉換為目標平台的組合語言。

julia 同時支援 static type 與 dynamic data type。在宣告時,可以宣告類別,也可以不宣告。如果不宣告,該物件的型別就由 compiler 判斷並決定。因為 julia 是 strong type 的語言,變數本身沒有型別,變數指向的值才有資料型別。

如果 julia 的 code 寫得越像 C,就可以讓 compiler 知道要如何最佳化。但如果越像 python,就無法判斷型別,也就無法最佳化,程式的效能就會比較差。

Femtolisp

Femtolisp 是一個 list interpreter 專案,julia 將這個專案合併到核心中,可以直接使用這個快速的 lisp interpreter

$ julia --lisp
;  _
; |_ _ _ |_ _ |  . _ _
; | (-||||_(_)|__|_)|_)
;-------------------|----------------------------------------------------------

References

Learning Julia

2019年4月23日

Redis Client - 使用Lettuce

Redis是一套基於key-value的in-memory database,常見的用途之一,是拿來作為Application Server的快取系統。
Lettuce則是一個強調thread-safe的Java Redis Client。

Redis參數設定

可以使用XML或Annotation兩種方式,來設定Redis的IP、Port。

方法(一):建立XML設定檔

以下假設專案使用webapp,並將XML檔案放在src/main/resource底下,取名為lettuce.xml

完整XML檔案範例如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- beans settings -->

</beans>

Sentinel設定

若要連線至3個Sentinel:

  • 192.168.1.32:27030
  • 192.168.1.32:27031
  • 192.168.1.32:27032

範例如下:

<bean id="sentinelConfig" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
    <constructor-arg name="master" value="mymaster" />
    <constructor-arg name="sentinelHostAndPorts">
        <set>
            <value>192.168.1.32:27030</value>
            <value>192.168.1.32:27031</value>
            <value>192.168.1.32:27032</value>
        </set>
    </constructor-arg>
</bean>

Connection設定

Java Redis Client使用lettuce,如下:

<bean id="lettuceConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
    <constructor-arg ref="sentinelConfig" />
</bean>

RedisTemplate設定

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="lettuceConnectionFactory"  />

方法(二):Annotation

建立一個類別,並於class前加上 @Configuration

//...
import org.springframework.context.annotation.Configuration;
//...

@Configuration
public class SpringDataRedisLettuceConfig {
//...
}

Connection Pool 設定

@Bean
public GenericObjectPoolConfig genericObjectPoolConfig() {
    GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
    genericObjectPoolConfig.setMaxIdle(8);
    genericObjectPoolConfig.setMinIdle(0);
    genericObjectPoolConfig.setMaxTotal(8);
    genericObjectPoolConfig.setMaxWaitMillis(100000);
    return genericObjectPoolConfig;
}

Sentinel設定

若要連線至3個Sentinel:

  • 192.168.1.32:27030
  • 192.168.1.32:27031
  • 192.168.1.32:27032

範例如下:

@Bean
public RedisSentinelConfiguration sentinelConfig()  {
    Set<String> sentinels = new HashSet<>();
    sentinels.add("192.168.1.32:27030");
    sentinels.add("192.168.1.32:27031");
    sentinels.add("192.168.1.32:27032");
    RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration("mymaster", sentinels);
    return redisSentinelConfiguration;
}

Connection設定

Java Redis Client使用lettuce,如下:

@Bean
public LettuceConnectionFactory lettuceConnectionFactory(RedisSentinelConfiguration sentinelConfig, GenericObjectPoolConfig genericObjectPoolConfig)  {

    LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
            .commandTimeout(Duration.ofMillis(10000))
            .poolConfig(genericObjectPoolConfig)
            .build();

    LettuceConnectionFactory factory = new LettuceConnectionFactory(sentinelConfig, clientConfig);
    return factory;
}

RedisTemplate設定

設定KeySerializer使用StringRedisSerializer

@Bean
RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(lettuceConnectionFactory);
    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(stringRedisSerializer);
    redisTemplate.setHashKeySerializer(stringRedisSerializer);
    return redisTemplate;
}

在Java中使用RedisTemplate操作Redis

  • 若剛才使用方法(一):建立XML設定檔

由於XML檔案位於src/main/resource/lettuce.xml,此處先使用ClassPathXmlApplicationContext取得RedisTemplate物件:

ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] { "lettuce.xml" });
RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);

  • 若剛才使用方法(二):Annotation的話,則只要確定Spring的root-context.xml中,有設定掃描相關的package即可:
...
<context:component-scan base-package="tw.com.maxkit.cdc.configuration" />
...

設定KeySerializer

設定KeySerializer為StringRedisSerializer

RedisSerializer<String> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);

因為無論是Key還是Value,預設都使用JdkSerializationRedisSerializer;如此一來,假設原本程式中寫入的key為mykey,若沒有將KeySerializer為StringRedisSerializer,寫入資料後,進console查看keys資料如下:

192.168.1.32:7030> keys *
1) "\xac\xed\x00\x05t\x00\x05mykey"

若有將KeySerializer為StringRedisSerializer時,寫入資料後,進console查看keys資料如下

192.168.1.32:7030> keys *
1) "mykey"

兩筆資料將會是不同的key,因此KeySerializer應一開始就設定好,並保持一致

此外,Value也可以設定使用StringRedisSerializer:

redisTemplate.setValueSerializer(stringSerializer);

不過,如果需要將序列化的物件存入,那就不應將ValueSerializer設為StringRedisSerializer。

透過Operations操作Redis

ValueOperations

取得ValueOperations物件

ValueOperations<String, String> vops = redisTemplate.opsForValue();

寫入字串

vops.set("vops_key","vops_value");

讀取字串

String vops_value =  vops.get("vops_key");

寫入/讀取物件

如同之前提到,寫入的Value預設是使用JdkSerializationRedisSerializer。因此也可以寫入有implements Serializable的物件

ValueOperations<String, TestUser> vops = redisTemplate.opsForValue();

vops.set("user:001", new TestUser("Allen", 20));

TestUser user001 = vops.get("user:001");

TestUser需implements Serializable,如下:

public class TestUser implements Serializable {
    private String name;
 private int age;
 public TestUser(String name, int age) {
// ...

ListOperations

建立ListOperations物件

ListOperations<String, String> listOp = redisTemplate.opsForList();

寫入list

long result = listOp.leftPush(key, value);

讀取list

List<String> values = lop.range(key, 0, -1);

不難發現,多數函數名稱都與原本的Redis指令很接近;若已熟悉Redis的話,使用Lettuce也能很快就上手。

刪除key

刪除資料時,直接使用redisTemplate的delete即可。

redisTemplate.delete(key)

2019年4月22日

Project Lombok

Lombok Project 是一個很小的 Java Annotation Library,目的是消除 Java 中一直不斷發生的重複程式碼,以 Java Bean 為例,最常見的就是需要在撰寫 class member 後,還需要寫一堆重複的 getter/setter,雖然 IDE 可以協助產生這些程式碼,但遇到欄位名稱修改,或是增刪欄位時,還是會造成困擾。

Installation

參考 Lombok 網頁的 Install 部分,基本上針對 project,就參考 Build tools 的部分,以我們使用來說,是看 maven。

在 Maven POM 裡面加上這個 dependency library,就可以使用了。

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.2</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

另外,通常 project 會搭配某個 IDE 進行開發,我們在 Intellij IDEA 安裝 Lombok plugin。

  • 在 Plugins 功能中,Browse repositories
  • 然後搜尋 Lombok Plugin
  • Install Plugin
  • Restart IDEA

因為 Lombok 只會將程式碼產生在編譯後的 class 裡面,在 IDE 必須要搭配 Plugin,才能偵測到 Lombok Plugin,並在編譯前了解 Lombok 會產生的 method/code,安裝了 Plugin 才不會看到編譯 Error 的警告。

Features

@Getter @Setter
@Getter
@Setter
private boolean employed = true;

@Setter(AccessLevel.PROTECTED)
private String name;

就等於以下的 Java Code

private boolean employed = true;
private String name;

public boolean isEmployed() {
    return employed;
}

public void setEmployed(final boolean employed) {
    this.employed = employed;
}

protected void setName(final String name) {
    this.name = name;
}
@NonNull

會在 setter 產生 null check,發生 null 時,會產生 NullPointerException。

@Getter
@Setter
@NonNull
private String lastname;

等同以下 java code

public void setLastname(String lastname) {
    if (lastname == null) throw new java.lang.NullPointerException("lastname");
    this.lastname = lastname;
}

public String getLastname() {
    return lastname;
}
@ToString
@ToString(callSuper=true,exclude="employed")
public class TestBean {
    private boolean employed = true;
    private String name;
    private String lastname;
}

等同以下 java code

public class TestBean {
   private boolean employed = true;
    private String name;
    private String lastname;
    
    @java.lang.Override
    public java.lang.String toString() {
        return "TestBean("super=" + super.toString() +
            ", name =" + name +
            ", lastname =" + lastname + ")";
    }
}
@EqualsAndHashCode

產生 equals 與 hashCode

@EqualsAndHashCode(callSuper=true, exclude={"employed"})
public class TestBean extends Person {
    private boolean employed = true;
    private String name;
    private String lastname;
}

等同以下 java code

public class TestBean extends Person {
    private boolean employed = true;
    private String name;
    private String lastname;
    
    @java.lang.Override
    public boolean equals(final java.lang.Object o) {
        if (o == this) return true;
        if (o == null) return false;
        if (o.getClass() != this.getClass()) return false;
        if (!super.equals(o)) return false;
        final TestBean other = (TestBean)o;
        if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
        if (this.lastname == null ? other.lastname != null : !this.lastname.equals(other.lastname)) return false;
        return true;
    }
    
    @java.lang.Override
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = result * PRIME + super.hashCode();
        result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
        result = result * PRIME + (this.lastname == null ? 0 : this.lastname.hashCode());
        return result;
    }
}
@Data

這是 Lombok 最常用的 annotation,包含了 @ToString, @EqualsAndHashCode, @Getter, @Setter 的功能。

@Cleanup

可確保 resource 會被 released,但 Java 7 以後已經有了 auto resource management 的功能,所以可以不使用這個功能。

@Synchronized

會產生一個 locking field,也就是 $lock 物件,用來同步該 method

private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");

@Synchronized
public String synchronizedFormat(Date date) {
    return format.format(date);
}

等同以下 java code

private final java.lang.Object $lock = new java.lang.Object[0];
private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");

public String synchronizedFormat(Date date) {
    synchronized ($lock) {
        return format.format(date);
    }
}
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

@NoArgsConstructor 會產生沒有 parameters 的 constructor,但如果有 final field 時,則無法使用這個 annotation,會發生編譯錯誤。

但可以改用 @NoArgsConstructor(force = true),這會將所有 final fields 初始化為 0/false/null。

@RequiredArgsConstructor 會產生一個 constructor,針對每一個需要特殊處理的 fields,都會產生一個參數,例如 final field,以及標記為 @NonNull field。

@AllArgsConstructor 會產生一個 constructor,class 裡面每一個 field 都產生一個對應的 constructor 參數

javap

編譯後的 class 可利用 javap 工具反組譯,並瞭解 Lombok 產生的 class 裡面包含的內容。

$ javap TestBean.class
Compiled from "TestBean.java"
public class test.TestBean extends test.Person {
  public test.TestBean();
  public boolean equals(java.lang.Object);
  protected boolean canEqual(java.lang.Object);
  public int hashCode();
}

Summary

其他的 annotaion 可參考 features 網頁的說明,但明顯最常用的是 @Data,這個功能就能減輕不少工作。

References

Reducing Boilerplate Code with Project Lombok

2019年4月15日

Kotlin Higher-order functions: lambdas as parameters and return values

higher-order functions: 自訂 function 且使用 lambda 為 parameters 或是 return values

inline functions: 可減輕使用 lambda 的 performance overhead

宣告 higher-order functions

例如 list.filter { x > 0 } 以 predicate function 為參數,就是一種 higher-order function

Function types

要知道如何定義 lambda 為參數的 function

我們已經知道,再不需要定義 type 也能使用 lambda

val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }

compiler 是將程式轉換為

// 以兩個 Int 為參數,回傳 Int
val sum: (Int, Int) -> Int = { x, y -> x + y }
// 沒有參數,沒有 return value
val action: () -> Unit = { println(42) }

val sum: (Int, Int) -> Int 其中 (Int, Int) 是 parameter types,後面的 Int 是 return type

如果可以回傳 null,就這樣定義

var canReturnNull: (Int, Int) -> Int? = { null }

宣告為 funcation type with a nullable return type

var funOrNull: ((Int, Int) -> Int)? = null

callback 是有參數名稱的 function type

fun performRequest(
       url: String,
       callback: (code: Int, content: String) -> Unit
) {
    /*...*/
}

fun main(args: Array<String>) {
    val url = "http://kotl.in"
    performRequest(url) { code, content -> /*...*/ }
    performRequest(url) { code, page -> /*...*/ }
}
calling functions passed as arguments

twoAndTree 的參數中 operation: (Int, Int) -> Int 是 function type

fun twoAndThree(operation: (Int, Int) -> Int) {
    val result = operation(2, 3)
    println("The result is $result")
}

fun main(args: Array<String>) {
    twoAndThree { a, b -> a + b }
    twoAndThree { a, b -> a * b }
}

另一個例子

fun String.filter(predicate: (Char) -> Boolean): String {
    val sb = StringBuilder()
    for (index in 0 until length) {
        val element = get(index)
        if ( predicate(element) ) sb.append(element)
    }
    return sb.toString()
}

fun main(args: Array<String>) {
    println("ab1c".filter { it in 'a'..'z' })
}

fun String.filter(predicate: (Char) -> Boolean): String

  • 前面的 String 是 receiver type
  • predicate 是 parameter name
  • (Char) -> Boolean 是 function type
Using function type from Java

kotlin 宣告的有 function type 為參數的 function

/* Kotlin declaration */
fun processTheAnswer(f: (Int) -> Int) {
    println(f(42))
}

在 Java 可用 lambda 使用

public class ProcessTheAnswerLambda {
    public static void main(String[] args) {
        processTheAnswer(number -> number + 1);
    }
}

舊版 java 要用 anonymous class

import static ch08.ProcessTheAnswer.ProcessTheAnswer.*;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

/* Java */

public class ProcessTheAnswerAnonymous {
    public static void main(String[] args) {
        processTheAnswer(
            new Function1<Integer, Integer>() {
                @Override
                public Integer invoke(Integer number) {
                    System.out.println(number);
                    return number + 1;
                }
            });
    }
}

在 java 使用 forEach

import java.util.ArrayList;
import java.util.Collections;
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
import java.util.List;

/* Java */

public class UsingForEach {
    public static void main(String[] args) {
        List<String> strings = new ArrayList();
        strings.add("42");
        CollectionsKt.forEach(strings, s -> {
           System.out.println(s);
           return Unit.INSTANCE;
        });
    }
}
default and null values for parameters with function types

先前 joinToString 的實作

fun <T> Collection<T>.joinToString(
        separator: String = ", ",
        prefix: String = "",
        postfix: String = "",
        transform: (T) -> String = { it.toString() }
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(transform(element))
    }

    result.append(postfix)
    return result.toString()
}

fun main(args: Array<String>) {
    val letters = listOf("Alpha", "Beta")
    println(letters.joinToString())
    println(letters.joinToString { it.toLowerCase() })
    println(letters.joinToString(separator = "! ", postfix = "! ",
           transform = { it.toUpperCase() }))
}

缺點是 transform 裡面永遠會使用 toString 轉換字串

修改為 nullable parameter of a function type

fun <T> Collection<T>.joinToString(
        separator: String = ", ",
        prefix: String = "",
        postfix: String = "",
        transform: ((T) -> String)? = null
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        val str = transform?.invoke(element)
            ?: element.toString()
        result.append(str)
    }

    result.append(postfix)
    return result.toString()
}

fun main(args: Array<String>) {
    val letters = listOf("Alpha", "Beta")
    println(letters.joinToString())
    println(letters.joinToString { it.toLowerCase() })
    println(letters.joinToString(separator = "! ", postfix = "! ",
           transform = { it.toUpperCase() }))
}
returning functions from functions

計算 cost of shipping depending on the selected shipping method,實作 logic variant,回傳對應的 function

enum class Delivery { STANDARD, EXPEDITED }

class Order(val itemCount: Int)

// 宣告回傳 function 的 function
fun getShippingCostCalculator(
        delivery: Delivery): (Order) -> Double {
    if (delivery == Delivery.EXPEDITED) {
        // return lambdas from the function
        return { order -> 6 + 2.1 * order.itemCount }
    }

    return { order -> 1.2 * order.itemCount }
}

fun main(args: Array<String>) {
    val calculator =
        getShippingCostCalculator(Delivery.EXPEDITED)
    // 呼叫 returned function
    println("Shipping costs ${calculator(Order(3))}")
}

另一個例子,GUI contract management application,需要根據 UI 狀態,決定要顯示哪些 contracts

data class Person(
        val firstName: String,
        val lastName: String,
        val phoneNumber: String?
)

class ContactListFilters {
    var prefix: String = ""
    var onlyWithPhoneNumber: Boolean = false

    // 宣告產生 function 的 function
    fun getPredicate(): (Person) -> Boolean {
        val startsWithPrefix = { p: Person ->
            p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
        }
        if (!onlyWithPhoneNumber) {
            return startsWithPrefix
        }
        return { startsWithPrefix(it)
                    && it.phoneNumber != null }
    }
}

fun main(args: Array<String>) {
    val contacts = listOf(Person("Dmitry", "Jemerov", "123-4567"),
                          Person("Svetlana", "Isakova", null))
    val contactListFilters = ContactListFilters()
    with (contactListFilters) {
        prefix = "Dm"
        onlyWithPhoneNumber = true
    }
    println(contacts.filter(
        contactListFilters.getPredicate()))
}
removing duplication through lambdas

analyzes visits to a website: SiteVisit 儲存 path of each visit, duration, OS

data class SiteVisit(
    val path: String,
    val duration: Double,
    val os: OS
)

enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }

val log = listOf(
    SiteVisit("/", 34.0, OS.WINDOWS),
    SiteVisit("/", 22.0, OS.MAC),
    SiteVisit("/login", 12.0, OS.WINDOWS),
    SiteVisit("/signup", 8.0, OS.IOS),
    SiteVisit("/", 16.3, OS.ANDROID)
)

要計算windows 的平均使用時間,直接寫一個 function

val averageWindowsDuration = log
    .filter { it.os == OS.WINDOWS }
    .map(SiteVisit::duration)
    .average()

fun main(args: Array<String>) {
    println(averageWindowsDuration)
}

改寫,讓 averageDurationFor 可傳入 OS 參數

fun List<SiteVisit>.averageDurationFor(os: OS) =
        filter { it.os == os }.map(SiteVisit::duration).average()

fun main(args: Array<String>) {
    println(log.averageDurationFor(OS.WINDOWS))
    println(log.averageDurationFor(OS.MAC))
}

同時計算 ios, android

val averageMobileDuration = log
    .filter { it.os in setOf(OS.IOS, OS.ANDROID) }
    .map(SiteVisit::duration)
    .average()

fun main(args: Array<String>) {
    println(averageMobileDuration)
}

以 function 動態調整過濾的條件

fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
        filter(predicate).map(SiteVisit::duration).average()

fun main(args: Array<String>) {
    println(log.averageDurationFor {
        it.os in setOf(OS.ANDROID, OS.IOS) })
    println(log.averageDurationFor {
        it.os == OS.IOS && it.path == "/signup" })
}

inline function: 減輕 lambda 的 overhead

how inlining works

宣告 function 為 inline,就表示會直接替代 code,而不是用呼叫 function 的方式

以下是 synchronized 的做法,鎖定 lock 物件,執行 action,最後 unlock

inline fun <T> synchronized(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    } finally {
        lock.unlock()
    }
}

val l = Lock()
synchronized(l) {
    // ...
}

因宣告為 inline,以下這些 code

fun foo(l: Lock) {
    println("Before sync")
    synchronized(l) {
        println("Action")
    }
    println("After sync")
}

synchronized(l) 會轉換為

    l.lock()
    try {
        println("Action")
    } finally {
        l.unlock()
    }
inline function 的限制

並非每一個使用 lambdas 的 function 都可以改為 inline

當 function 為 inlined,body of the lambda expression 傳入當作參數,會直接替換為 inline function,如果 function 直接被呼叫,就可以 inline,但如果需要儲存 function,後面才要使用,就不能用 inline

inlining collection operations

kotlin 的 filter 是定義為 inline, map 也是,因為 inline,就不會產生額外的 classes or objects

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun main(args: Array<String>) {
    println(people.filter { it.age < 30 })
    
    println(people.filter { it.age > 30 }.map(Person::name))
}
什麼時候要用 inline functon

使用 inline 只會在 function 使用 lambda 為參數的狀況下,改進效能

JVM 有支援 inlining support,他會分析 code,並嘗試 inline code

除了避免產生多餘的 class for each lambda 及 object for the lambda instance,另外 JVM 還不夠聰明,能夠找出所有可以 inline 的狀況

Using inlined lambdas for resource management

另一個 lambda 能減少重複程式碼的狀況是 resource management,取得 Resource -> 使用 -> release

Resource 可能是 file, lock, database transaction ....

通常會用 try/finally statement 包裝起來

kotlint 提供 "withLock" function,可處理 synchronized 的工作

val l: Lock = ...

l.withLock {
    // access the resource protected by this lock
}

withLock 是這樣實作的

fun <T> Lock.withLock(action: () -> T): T {
    lock()
    try {
        return action()
    } finally {
        unlock()
    }
}

java 提供 try-with-resources statement

/* Java */
static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
        new BufferedReader(new FileReader(path))) {
            return br.readLine();
    }
}

kotlin 沒有對應的語法,但可以用 "use" 搭配 lambda 提供相同的功能

import java.io.BufferedReader
import java.io.FileReader
import java.io.File

fun readFirstLineFromFile(path: String): String {
    BufferedReader(FileReader(path)).use { br ->
        return br.readLine()
    }
}

control flow in higher-order functions

return statements in lambdas: return from an enclosing function

在 list of Person 裡面尋找 Alice

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
    for (person in people) {
        if (person.name == "Alice") {
            println("Found!")
            return
        }
    }
    println("Alice is not found")
}

fun main(args: Array<String>) {
    lookForAlice(people)
}

將 for 改為 forEach,在 lambda function 中留下 return,這個 return 是 non-local return,這種 return 只對 function 使用 inlined lambda function 為參數有作用

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
    people.forEach {
        if (it.name == "Alice") {
            println("Found!")
            return
        }
    }
    println("Alice is not found")
}

fun main(args: Array<String>) {
    lookForAlice(people)
}

因為 forEach 的 lambda function 是用 inlined,因此可使用 return

returning form lambdas: return with label

local return 類似 break 的功能,會中斷 lambda 的執行,

區分 local return 與 non-local,可使用 labels,可將 lambda expression 加上 label,並在 return 時參考此 label

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
    people.forEach label@{
        if (it.name == "Alice") {
            println("Found!")
            return@label
        }
    }
    // 永遠會執行這一行
    println("Alice might be somewhere")
}

fun main(args: Array<String>) {
    lookForAlice(people)
}

也可以用 lambda 做為 label

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
    people.forEach {
        if (it.name == "Alice") {
            println("Found!")
            return@forEach
        }
    }
    println("Alice might be somewhere")
}

fun main(args: Array<String>) {
    lookForAlice(people)
}

也可以將 this 加上 label

fun main(args: Array<String>) {
    // implicit receiver 為 this@sb
    println(StringBuilder().apply sb@{
       listOf(1, 2, 3).apply {
           // this 參考到 closest implicit receiver in the scope
           this@sb.append(this.toString())
       }
    })
}
anonymous functions: local returns by default

使用 anonymous function 替代 lambda expression

return 會參考到 closest functon: 也就是 anonymous function

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31))

fun lookForAlice(people: List<Person>) {
    people.forEach(fun (person) {
        if (person.name == "Alice") return
        println("${person.name} is not Alice")
    })
}

fun main(args: Array<String>) {
    lookForAlice(people)
}

另一個例子

people.filter(fun (person): Boolean {
    return person.age < 30
})

people.filter(fun (person) = person.age < 30)

References

Kotlin in Action

2019年4月8日

Kotlin Operator Overloading and other conventions

kotlin 提供 operator overloading 機制,可以覆寫 operator 的行為,例如提供了 plus method,就可以將物件以 + 進行運算。

Overloading arithmetic operators

overloading binary arithmetic operations

a + b -> a.plus(b)

data class Point(val x: Int, val y: Int) {

    // 覆寫 plus 方法
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

fun main(args: Array<String>) {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    
    // + 就是呼叫 plus
    println(p1 + p2)
}
Expression function name
a*b times
a/b div
a%b mod
a+b plus
a-b minus

如果是 Java 的 class,也可以用 operator fun 的方式定義 plus

data class Point(val x: Int, val y: Int)

// extension function,附加到 Point
operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

fun main(args: Array<String>) {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)
}

兩個 operands 不需要有相同的 type,Point 可以乘上 Double

data class Point(val x: Int, val y: Int)

// 兩個 operands 不需要有相同的 type
operator fun Point.times(scale: Double): Point {
    return Point((x * scale).toInt(), (y * scale).toInt())
}

fun main(args: Array<String>) {
    val p = Point(10, 20)
    println(p * 1.5)
}

可改變 return data type

operator fun Char.times(count: Int): String {
    return toString().repeat(count)
}

fun main(args: Array<String>) {
    println('a' * 3)
}

kotlin 沒有定義 bitwise operators,改以 function 並用 index call syntax 處理

function meaning
shl signed shift left
shr signed shift right
ushr unsigned shift right
and
or
xor
inv bitwise inversion
fun main(args: Array<String>) {
    println(0x0F and 0xF0)
    println(0x0F or 0xF0)
    println(0x1 shl 4)
}
Overloading compound assignment operators

除了 + 也能使用 +=

data class Point(val x: Int, val y: Int)

operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

fun main(args: Array<String>) {
    var point = Point(1, 2)
    point += Point(3, 4)
    println(point)
}

有時只需要用 += 不需要 +,可以提供 plusAssign 另外加上 Unit return type,類似的 method 為 minusAssign, timesAssign

operator fun <T> MutableCollection<T>.plusAssign(element: T) {
    this.add(element)
}

如果在程式裡面呼叫 a+=b,其實會先呼叫 a = a.plus(b) 然後是 a.plusAssign(b)


standard libary 的 +, - 都會產生新的 collection,因此 +=, -= 可同時用在 read-only, mutable collection

fun main(args: Array<String>) {
    val list = arrayListOf(1, 2)
    /// 改變了原本的 list
    list += 3
    
    // 產生新的 list,但原本的 list 不變
    val newList = list + listOf(4, 5)
    println(list)
    println(newList)
}
Overloading unary operators

+a 就是 a.unaryPlus(), -a 是 a.unaryMinus

data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus(): Point {
    return Point(-x, -y)
}

fun main(args: Array<String>) {
    val p = Point(10, 20)
    println(-p)
}
expression function name
+a unaryPlus
-a unaryMinus
!a not
++a, a++ inc
--a, a-- dec
import java.math.BigDecimal

// 覆寫 ++
operator fun BigDecimal.inc() = this + BigDecimal.ONE

fun main(args: Array<String>) {
    var bd = BigDecimal.ZERO
    
    // 在 println 後,呼叫 inc
    println(bd++)
    // 在 println 前,呼叫 inc
    println(++bd)
}

Overloading comparison operators

comparison operators: ==, !=, >, <

euqals ==

a==b 會轉換為 equals 及 null check a?.equals(b) ?: b ==null

class Point(val x: Int, val y: Int) {
    override fun equals(obj: Any?): Boolean {
        if (obj === this) return true
        if (obj !is Point) return false
        return obj.x == x && obj.y == y
    }
}

fun main(args: Array<String>) {
    println(Point(10, 20) == Point(10, 20))
    println(Point(10, 20) != Point(5, 5))
    println(null == Point(1, 2))
}
Ordering operators: compareTo

java 的 class 實作 Comparable 介面,就可以比較大小

kotlin 也有支援 Comparable 介面,但是 compareTo method 是用在 call by convention

a>=b 會轉換為 a.compareTo(b) >=0

import kotlin.comparisons.compareValuesBy

class Person(
        val firstName: String, val lastName: String
) : Comparable<Person> {

    override fun compareTo(other: Person): Int {
        return compareValuesBy(this, other,
            Person::lastName, Person::firstName)
    }
}

fun main(args: Array<String>) {
    val p1 = Person("Alice", "Smith")
    val p2 = Person("Bob", "Johnson")
    println(p1 < p2)
}

Conventions used for collections and ranges

Accessing elements by index: get and set

kotlin 要使用 map,是用 map[index] 取得某個 element,類似 java 的 array

如果定義 get function,並標記為 operator fun,就可以用 [] 呼叫 get

x[a,b] -> x.get(a,b)

data class Point(val x: Int, val y: Int)

operator fun Point.get(index: Int): Int {
    return when(index) {
        0 -> x
        1 -> y
        else ->
            throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main(args: Array<String>) {
    val p = Point(10, 20)
    println(p[1])
}

如果定義 operator fun get(rowIndex: Int, colIndex: Int) 就可以用 matrix[row, col]


set 跟 get 類似

x[a,b] = c 會轉換為 x.set(a, b, c)

data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: Int) {
    when(index) {
        0 -> x = value
        1 -> y = value
        else ->
            throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main(args: Array<String>) {
    val p = MutablePoint(10, 20)
    p[1] = 42
    println(p)
}
"in" convention

in 是呼叫 contains

a in c -> c.contains(a)

data class Point(val x: Int, val y: Int)

data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x &&
           p.y in upperLeft.y until lowerRight.y
}

fun main(args: Array<String>) {
    val rect = Rectangle(Point(10, 20), Point(50, 50))
    println(Point(20, 30) in rect)
    println(Point(5, 5) in rect)
}
rangeTo

start..end 轉換為 start.rangeTo(end)

standard library 有定義一個 rangeTo,可被任何 comparable element 呼叫

operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
import java.time.LocalDate

fun main(args: Array<String>) {
    // 產生十天
    val now = LocalDate.now()
    val vacation = now..now.plusDays(10)
    
    val n = 9
    // 0..10
    println(0..(n + 1))

    // 加上 () 再呼叫 forEach
    (0..n).forEach { print(it) }
}
"iterator" convention for "for" loop

iterator method

import java.util.Date
import java.time.LocalDate

operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
        // 實作 Iterator
        object : Iterator<LocalDate> {
            var current = start

            // 使用 compareTo convention
            override fun hasNext() =
                    current <= endInclusive

            // 修改前會回傳 current date
            override fun next() = current.apply {
                current = plusDays(1)
            }
        }

fun main(args: Array<String>) {
    val newYear = LocalDate.ofYearDay(2017, 1)
    val daysOff = newYear.minusDays(1)..newYear
    for (dayOff in daysOff) {
        println(dayOff)
    }
    
    //2016-12-31
    //2017-01-01
}

Destructuring declarations and component functions

destructuring declarations 可以 unpacke composite values,儲存在不同的變數

val p = Point(10, 20)
val (x, y) = p

val (x, y) = p 等同

val a = p.component1()
val b = p.component2()

data class 會自動為每一個 property 產生 componentN 的 function

class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

但也能自己處理

import java.io.File

data class NameComponents(val name: String,
                          val extension: String)

fun splitFilename(fullName: String): NameComponents {
    val result = fullName.split('.', limit = 2)
    return NameComponents(result[0], result[1])
}

fun main(args: Array<String>) {
    val (name, ext) = splitFilename("example.kt")
    println(name)
    println(ext)
}

splitFilename 也可以寫成

fun splitFilename(fullName: String): NameComponents {
    val (name, extension) = fullName.split('.', limit = 2)
    return NameComponents(name, extension)
}

for ((key, value) in map) 可 iterate map

fun printEntries(map: Map<String, String>) {
    for ((key, value) in map) {
        println("$key -> $value")
    }
}

>>> val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
>>> printEntries(map)
Oracle -> Java
JetBrains -> Kotlin

Reusing property accessor logic: delegated properties

delegated properties 可實作更複雜的 perperties

Delegated properties: the basics

delegated property 語法如下: p delegates the logic to an instance of Delegate class

class Foo {
    var p: Type by Delegate()
}

實際上 compiler 會產生以下的 code

class Foo {
    private val delegate = Delegate()
    var p: Type
        set(value: Type) = delegate.setValue(..., value)
        get() = delegate.getValue(...)
}

搭配 Delegate 的實作,完整的範例如下

class Delegate {
    operator fun getValue(...) { ... }
    operator fun setValue(..., value: Type) { ... }
}

class Foo {
    var p: Type by Delegate()
}

>>> val foo = Foo()
// 使用 property,會呼叫 delegate.get
>>> val oldValue = foo.p
// 修改 property value 會呼叫 delegate.set
>>> foo.p = newValue
使用 delegated properties: lazy initialization and by lazy()

lazy initialization

例如 Person 可提供多個 emails,但emails 存在 DB 裡面,需要一段時間才能取得,我們需要在第一次使用 emails 時,載入 email 並將 email 暫存。

使用 backing property _emails 的方式,提供這樣的功能

class Email { /*...*/ }
fun loadEmails(person: Person): List<Email> {
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}

class Person(val name: String) {
    private var _emails: List<Email>? = null

    val emails: List<Email>
       get() {
           if (_emails == null) {
               _emails = loadEmails(this)
           }
           return _emails!!
       }
}

fun main(args: Array<String>) {
    val p = Person("Alice")
    p.emails
    p.emails
}

如果用 lazy initialization 的方式

class Email { /*...*/ }
fun loadEmails(person: Person): List<Email> {
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}

class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
}

fun main(args: Array<String>) {
    val p = Person("Alice")
    p.emails
    p.emails
}
implementing delegated properties

notifying listeners when a property of an object changes

Java 提供 PropertyChangeSupport, PropertyChangeEvent classes

在 kotlin 使用 PropertyChangeSupport, PropertyChangeEvent 的範例

import java.beans.PropertyChangeSupport
import java.beans.PropertyChangeListener

open class PropertyChangeAware {
    // 儲存 list of listeners, dispatch PropertyChangeEvent events to them
    protected val changeSupport = PropertyChangeSupport(this)

    fun addPropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.addPropertyChangeListener(listener)
    }

    fun removePropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.removePropertyChangeListener(listener)
    }
}

class Person(
        val name: String, age: Int, salary: Int
) : PropertyChangeAware() {

    var age: Int = age
        set(newValue) {
            // field 可取得 property backing field
            val oldValue = field
            field = newValue
            // 通知 listeners
            changeSupport.firePropertyChange(
                    "age", oldValue, newValue)
        }

    var salary: Int = salary
        set(newValue) {
            val oldValue = field
            field = newValue
            changeSupport.firePropertyChange(
                    "salary", oldValue, newValue)
        }
}

fun main(args: Array<String>) {
    val p = Person("Dmitry", 34, 2000)
    // 將 property change listener 加入 p
    p.addPropertyChangeListener(
        PropertyChangeListener { event ->
            println("Property ${event.propertyName} changed " +
                    "from ${event.oldValue} to ${event.newValue}")
        }
    )
    p.age = 35
    p.salary = 2100
}

以 ObservableProperty 修改

為每一個 property 都產生 ObservableProperty instance,並 delegate getter, setter

import java.beans.PropertyChangeSupport
import java.beans.PropertyChangeListener

open class PropertyChangeAware {
    protected val changeSupport = PropertyChangeSupport(this)

    fun addPropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.addPropertyChangeListener(listener)
    }

    fun removePropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.removePropertyChangeListener(listener)
    }
}

class ObservableProperty(
    val propName: String, var propValue: Int,
    val changeSupport: PropertyChangeSupport
) {
    fun getValue(): Int = propValue
    fun setValue(newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(propName, oldValue, newValue)
    }
}

class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
    
    val _age = ObservableProperty("age", age, changeSupport)
    var age: Int
        get() = _age.getValue()
        set(value) { _age.setValue(value) }

    val _salary = ObservableProperty("salary", salary, changeSupport)
    var salary: Int
        get() = _salary.getValue()
        set(value) { _salary.setValue(value) }
}

fun main(args: Array<String>) {
    val p = Person("Dmitry", 34, 2000)
    p.addPropertyChangeListener(
        PropertyChangeListener { event ->
            println("Property ${event.propertyName} changed " +
                    "from ${event.oldValue} to ${event.newValue}")
        }
    )
    p.age = 35
    p.salary = 2100
}

調整 ObservableProperty,將 getValue, setValue 改為 operator fun

去掉 name property,改為使用 KProperty.name

class ObservableProperty(
    var propValue: Int, val changeSupport: PropertyChangeSupport
) {
    operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue

    operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    }
}

最精簡的版本

import java.beans.PropertyChangeSupport
import java.beans.PropertyChangeListener
import kotlin.properties.Delegates
import kotlin.reflect.KProperty

open class PropertyChangeAware {
    protected val changeSupport = PropertyChangeSupport(this)

    fun addPropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.addPropertyChangeListener(listener)
    }

    fun removePropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.removePropertyChangeListener(listener)
    }
}

class Person(
    val name: String, age: Int, salary: Int
) : PropertyChangeAware() {

    private val observer = {
        prop: KProperty<*>, oldValue: Int, newValue: Int ->
        changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    }

    var age: Int by Delegates.observable(age, observer)
    var salary: Int by Delegates.observable(salary, observer)
}

fun main(args: Array<String>) {
    val p = Person("Dmitry", 34, 2000)
    p.addPropertyChangeListener(
        PropertyChangeListener { event ->
            println("Property ${event.propertyName} changed " +
                    "from ${event.oldValue} to ${event.newValue}")
        }
    )
    p.age = 35
    p.salary = 2100
}
Delegated-property translation rules

整理 delegated properties 的運作方式

以下是 class with delegated property

class Foo {
    var c: Type by MyDelegate()
}
val foo = Foo()

實際上 compiler 會產生以下的 code

class Foo {
    private val <delegate> = MyDelegate()
    var c: Type
        set(value: Type) = <delegate>.setValue(c, <property>, value)
        get() = <delegate>.getValue(c, <property>)
}

使用 property 時,會呼叫

val x=c.prop 就是 val x = <delegate>.getValue(c, <property>)

c.prop=x 就是 <delegate>.setValue(c, <property>, x)

Storing property values in a map

expando objects: 有動態定義的 set of attributes

Person 沒有固定的 properties,將 property 放在 hashMap 裡面

class Person {
    private val _attributes = hashMapOf<String, String>()

    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }

    val name: String
        get() = _attributes["name"]!!
}

fun main(args: Array<String>) {
    val p = Person()
    val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
    for ((attrName, value) in data)
       p.setAttribute(attrName, value)
    println(p.name)
}

可以使用 delegated property

class Person {
    private val _attributes = hashMapOf<String, String>()

    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }

    val name: String by _attributes
}

Delegated properties in frameworks

Database framework

// 將 object 關聯至 table in the database
object Users : IdTable() {
    // property 為 columns
    val name = varchar("name", length = 50).index()
    val age = integer("age")
}

// 每一個 User instance 都有特定的 id
class User(id: EntityID) : Entity(id) {
    // name 對應到 DB 的 name
    var name: String by Users.name
    var age: Int by Users.age
}

另一種方式,是定義 Column 類別

object Users : IdTable() {
    val name: Column<String> = varchar("name", 50).index()
    val age: Column<Int> = integer("age")
}

framework 為 Column class 定義了 getValue, setValue

operator fun <T> Column<T>.getValue(o: Entity, desc: KProperty<*>): T {
    // retrieve the value from the database
}
operator fun <T> Column<T>.setValue(o: Entity, desc: KProperty<*>, value: T) {
    // update the value in the database
}

References

Kotlin in Action