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