2018年8月30日 星期四

Kotlin 的 object 及 class

Kotlin 用關鍵字 object {  } 直接實體化物件,我們可以用無名的(annonumous)物件( object )區塊指令(λ), 實體化後指定給變數, 無名靜態物件只能放在函式區域(local)裏面, 不能放在整體區域(Global):
      fun main(args:  Array < String > ) {
             val myObj = object  {
                           var x: Int = 10
                           fun printx() { println("myObj.x = $x") }
                          // ...
              }
             myObj.printx()
      }
另一種是帶名 object 宣告方式,它不能有建構函式,也不能放在函式區域內, 只能放在整體區域(global)或是類別(class)區塊內,宣告後,實體便已經產生,類似 static object (靜態物件),所以之後就可以直接存取 :
      object myObj {
             var x: Int = 10
             fun printx() { println("myObj.x = $x") }
            // ...
      }
      fun main(args: Array < String > ) {
              myObj.printx()
      }
還有一種是放在類別(class)區塊內的協同物件 companion object { } 宣告, Kotlin會將協同物件與類別名稱綁在一起,類別與物件(object)不同的地方在於:類別必須用建構式 ( ) 實體化(簡稱分類)之後才能存取裡面的成員,但在類別裡面,加了協同物件宣告後, {  } 裡的物件已經與類別綁在一起有了實體(類似靜態物件),因此可以直接用類別名稱存取裏面的成員:
     class myClass {
           companion object myObj {
                var x: Int = 10
                fun printx() { println("myObj.x = $x") }
               // ...          
          }
          // ...
     }
    fun main(args: Array  < String >  ) {
         myClass.myObj.printx( )                       // simply  myClass.printx( ) is th same function
         println("myClass.x is " + myClass.x ) //  myObj is companion object bind to myClass
     }

類別宣告時在類別名稱之後引用冒號 : 接著類別建構式初始繼承程序,此種方式稱為帶名類別繼承,Kotlin 的類別必須在 class 前面加修飾詞 open 才可以開放繼承程序,而其所屬成員也需用修飾詞 open 之後才能讓繼承者重指定加以覆蓋(override), 否則預設都是 final 無法被繼承或覆蓋.
     open class 爺爺 {  // 爺爺可以生, open class
           open var 財產=10 // 可以被覆蓋
           val  生命 = "死掉就沒了"
     }
    open class 爸爸: 爺爺( ) {// 爸爸是爺爺生的, 也可以再生兒女 , 承先可啟後
          override  var 財產=1000
          open var  事業 ="成立新公司" // 可以被覆蓋
    }
    class 兒子: 爸爸( ) { // 兒子是爸爸生的,但沒有 open 就是絕種了(final class)
          override  var 財產=1000000000
          override  var 事業 ="成立連鎖公司"
    }
    fun main(args: Array  < String >  ) {
         var son=兒子( )   
         println(son.財產)
         println(son.事業)
         println(son.生命)
    }

Kotlin 允許使用多個類型建構式實現多重繼承,不同類型建構式之間使用逗號加以分開,但每個繼承者最多只能有一個主要類型(super class), 加上多個介面類型(interface, 是一種開放的可繼承類型, 但不能直接用於實例化)的繼承.另一個比較複雜的無名物件繼承類型,同樣引用冒號當成繼承符號.它的語法是  object : 繼承類型建構式,當然繼承類型必須是開放類型(open class),而且 object: 同樣用逗後將多重類型一一分開加以繼承, .
     open class 爺爺 {  // 爺爺 是祖先類型
           open var 財產=10   // 開放可以覆蓋
           val  生命 = "死掉就沒了"
     }   
    open class 爸爸: 爺爺( ) {    // 爸爸 是帶名繼承類型
          override  var 財產=1000  // 覆蓋爺爺的財產
          open var  事業 ="成立新公司" // 開放可以覆蓋
    }
    fun main(args: Array  < String >  ) {
         var son=object: 爸爸( ) {   // son 是無名物件類型: 從爸爸類型的繼承程序所產生的物件
              override  var 事業 ="青出於藍" // 覆蓋爸爸的事業
         }  
         println(son.財產)
         println(son.事業)
         println(son.生命)
    }
介面類型(interface)可以看成是一個不需要建構式的抽象類型(abstract class), 它本身預設就是開放 open 的類型, 同樣可利用 object: 繼承語法, 先繼承介面類型並實作方法再具體(實例)化成物件, 該物件經由代理條款 by 物件的方式(delegation), 就能讓介面類型用於繼承, 而 class 除非宣告成 open class, 否則預設是內含建構式的 final 類型無法被繼承, 只需透過建構式就可以產生實例(instance), 另外如果單純把類型當作資料庫使用, 可以用 data class (資料類型)把參數和屬性(包括內部特性和方法)建構合而為一的類型, 以下程式碼可以看出 class,  interface,  data class 的差異:
class   Normalclass  {  // 內含建構式 Normalclass( ) 的一般類型
       val state = 3          //  property: 成員特性可初始化狀態
       fun print( ) { println("I come from class. $state") } // method: 函數成員可以計算結果
}
interface IFclass  { // 介面類型: 沒有建構式的抽象類型, 也就是只有屬性
       val state: Int    // 介面的特性(property)不能初始化, 只要資料型態列出就好
       fun print( )       // 介面的方法(method)不用實現, 只要方法型態列出來就好
}
data class  Hello(val sr: String="Sr.")  { //  資料類型: 將參數與屬性合為一,  sr 是屬性之一
       val prefix = "Hi"  // 資料類型的特性
       fun print()   {   println("$prefix $sr.") } // 資料類型的方法
}
fun main() {
    val objectIF = object: IFclass { // 介面類型用 object: 繼承語法,要實現並具體化成物件
           override val state = 5 // 實作時才設定
           override fun print( ) { println("I come from interface. $state") }  // 實現函數
    }
    class Subclass: IFclass by objectIF // 介面類型透過代理條款 by 物件,用於繼承產生新類型
    objectIF.print( )            // 物件 objectIF 不用後綴加上小括號( )
    Subclass( ).print( )        // 一般類型Subclass實例化要用建構式小括號 Subclass( )產生物件
    Normalclass( ).print( ) // 一般類型透過建構式 Normalclass( ) 直接實例化物件
    Hello("Hohn").print( )// data class 也是透過建構式 Hello( ) 直接實例化物件
}
結論:
1.只有一般類型 class (包含 open class 及 final class) 可以透過建構式產生實例形成物件,其它抽象類型(abstract class) 或介面類型(interface)兩者預設雖然都是開放類型可以被繼承, 但不可以直接實例化(instance)物件, 抽象(abstract)與(vs)介面(interface)類型還有一些不同點:
(a). 介面成員的屬性無記憶狀態(state),也不能有建構式, 但抽象類型可以.
(b). 介面類型可以透過特別條款(by clause)經由已實例化物件的代理(delegation)實現繼承衍生新類型(subclass). 但抽象類型不行.
2.預設的 class 類型是 final class,除非宣告成 open class 才可以被繼承衍生新類型(subclass)
3. object 類型建構式 { }  是直接利用類型的建構式去實例化物件
4. object: 介面類型 { } 是先繼承介面類型才實例化成物件
5.一些專有名詞:
    attribute 額外資訊, additional information, 除共通特性外, 另外定義的名稱
    property 類型內特性(characteristic), 泛指所定義的變數名稱, 包括函數名稱及常數名稱
    method  類型內共通方法, 特指所定義的函數名稱 function name, 需經過( )運算才有結果
    class 類型, 完整屬性的體型, 但有名無實, 跑完建構式後就會有實體(instance)
    abstract class 抽象類型, 有初步概念所形成的概觀體型, 完整體型還需進一步實作
    interface 介面類型, 保證無建構式的抽象類型, 同樣有概觀體型, 也需進一步實作
    instance 實例, 是由類型勾鉤出來的物件副本, 常與物件搞混, 實際上搞混也是理所當然.
    object 實體物件,可用句點語法操作本身的特性(properties)或方法(methods)
    object (物件)範疇較大, instance (實例)是某種類型的物件   

2018年8月28日 星期二

Kotlin 的 annotation

annotation 顧名思義很像是當註解用,但跟 // ... 或 /*  ...   */  隨意寫內容的註釋(commnet)並不同,它有自成一套的運作機制,可以用來定義自己喜歡的註解方式,規定如何來寫註解,搭配Kotlin 內建的註解 @TAGET 可以限制註解的標地物,甚至還可以引進一些 meta-date,因此 annotation 可說是具有運作功能(function)的註解,因此才會自成一類稱之為  annotation class (註解類別).
自定註解類別若配合建構式可以規定要如何撰寫註解,運用時只要簡單加上 @註解類別 (例如 Kotlin 內建的 @TARGET), 但是當程式引用的方式不對時,編譯器就會出現錯誤警告訊息.以下示範定義一個 "無意義註解",當然也可以自訂 "有無意義註解"讓程式碼更具可讀性:
             annotation  class     無意義註解             // 制定一個類別名稱是: 無意義註解
            @無意義註解       
            fun main(args: Array < String >  ) {               
                println( "Hello" )
            }
上述程式裡註解看似無任何意義,但可以完成正常編譯程序並執行, 現在搭配建構式,制定一個必須寫字串的註解,Kotlin 對於類別的建構式,當它含有預設參數值時,呼叫時就可以免輸入參數而使用預設值
     annotation class 註解(val why:String="預設註解")   //  含預設參數建構式的 "註解"
     @註解("這是一個範例程式")      // 有建構式, 可以傳參數進去並呼叫 "註解"
     fun main(args: Array < String >  ) {
              @註解 { println("列出訊息")  }   // 已有預設值可免傳參數, 註解作用在 λ ,合法但 { } 內容卻不會執行
               println("Hello")
      }
上述程序可以正常編譯並執行,但如果 @註解 傳進去的是一個非字串型態的參數,編譯器就會抱怨,定義註解類別有許多規定, 它限制建構式必須用 val 宣告輸入變數,註解的標的物也有規則的,像  @註解 println("Hello") 就不合法, 但作用在 lambda(希臘字母 λ) 函式像是  @註解 {   }  卻是合法的, 只是編譯會產生警告訊息並且 { } 裏面的 λ 函式可能不會執行.如果在註解類別內引用定義過的註解時,就不用加上 @,例如:
     annotation class 註解(val why:String="預設值")   //  含預設參數建構式的 "註解"
     annotation class 注意註解(val warring:String, val note:註解 = 註解("新的預設值") ) // 新的註解類別引用上述的 "註解" 類別時不需加 @
     @註解("這是一個範例程式")      // 傳參數並呼叫 "註解"
     @注意註解("注意",註解("注意這是新的註解"))  // 呼叫 "注意註解"
     fun main(args: Array < String >  ) {
              @註解  { println("列出訊息")  }   // 註解作用在 λ ,合法但 { } 內容卻不會執行
               println("Hello")
               @注意註解("注意")  var f= "evaluated"  // 註解作用在屬性值, 該賦值還是會被執行
                println("f  is  $f");                                       // 印出  f 看看
      }
 上述只是粗淺的用法, 實際上 annotation class 可以更複雜使用, 其奧妙之深尚需了解中

2018年8月27日 星期一

設定 VS code 使用 kotlin compiler and kotlin native

先到官方網站 https://kotlinlang.org/docs/tutorials/command-line.html 去下載 kotlin-compiler, 我直接下載 1.2.61.zip 版本:
         https://github.com/JetBrains/kotlin/releases/download/v1.2.61/kotlin-compiler-1.2.61.zip
這是 Kotlin JVM 版本, 還需要事先安裝好 JAVA 才能執行. 用 unzip 可將它解壓縮, 解壓縮的目錄用  mv 指令把它改成  $HOME/kotlinc, 先寫一個 bash script 設定好環境變數 PATH, 將它存成 ktenv 檔案,方便以後使用:
        #!/bin/bash
       alias path='echo "$PATH"'
       function pathadd() {
             if [ -z "$1" ]
             then
                  PATH=$PATH:$(pwd)
             elif [ -d "$1" ]
             then
                    PATH=$PATH:$1
             fi
       }
       pathadd  $HOME/vscode/bin
       pathadd  $HOME/kotlinc/bin
開啟終端機, 執行上述文稿, 並執行 VS code
       .  ktenv
       code
在 VS code裏面點擊側邊欄的拼圖工具(extensions), 搜尋 Kotlin Language 並安裝它, Kotlin 敘述結尾並不需加上分號,寫一個簡潔的  kotlin 程式碼;
         // my.kt
         fun main(args: Array < String > ) {
              var g = {s:String -> s.length } // 定義 g = λ(s), 輸入字串,取出字串長度,
              var f  = {l:Int -> (l%2)==1      } // 定義 f = λ(l), 輸入整數,判斷是否為奇數
        
              listOf("1", "22", "333", "4444", "55555") // 產生字串陣列的物件
              .filter { x -> f(g(x) ) }  // 使用 λ(x) =  f(g(x)) 過濾字串
              .forEach { println(it) }             // 列印出來

              mapOf( 1 to "一", 2 to "二", 3 to "三", 4 to "四")  // 產生字典的物件
              .filter { (x, _) -> f(x) }   // 使用解構式 (key,value),底線忽略.用 λ(x)=f(x) 過濾字串
              .forEach{ (key, value)  -> println("[$key]  $value") } // 列印出來
         }
 開啟下面板的終端機(TERMINAL) 將暫存檔放到 ramdisk (/tmp),避免寫到磁碟,執行以下指令:
        kotlinc my.kt -include-runtime  -d   /tmp/jartmp.jar    &&    java -jar /tmp/jartmp.jar
或是在 VS code 點選上面板的 Tasks -> Configure Task, 將上述指令寫進 tasks.json 檔案裏面,只要修改制訂 label 及 command 就可以了:
  {
     // custom tasks.json
     //  ...
     "versions" : "2.0.0"
     "tasks" : [
          {
             "label" : "runkt"   
             "type"  : "shell"
             "command" : "kotlinc my.kt -include-runtime  -d   /tmp/jartmp.jar    &&    java -jar /tmp/jartmp.jar"
          }
       ]
  }
關閉 tasks.json 並存檔(save), 這時在  Tasks -> Run Tasks 就會看到剛剛新增的工作 runkt , 點擊它會開啟終端機並執行.另外也可以安裝擴充插件像是 Code Runner, 安裝完後會在右上方多出一個三角形按鈕 (run button),點擊 run 按鈕就可以執行 Kotlin 文稿了.

另外可以上官網 https://blog.jetbrains.com/kotlin 下載 Kotlin native comipler,
我下載 Linux 版本
         https://download.jetbrains.com/kotlin/native/builds/releases/0.8.2/linux/kotlin-native-linux-0.8.2.tar.gz
用  tar -zxvf 解壓縮後,再用 mv 指令將目錄改成 $HOME/konan,  把 konan/bin 加入 PATH, 之後用  konanc 編譯 my.kt 成可執行檔:
        pathadd $HOME/konan/bin
        konanc  my.kt -o my
編譯完輸出 my.kexe , 編譯的速度很慢要花上一段時間, 最後執行看看
        my.kexe
本身就可執行, 無需 JVM, 看看輸出結果:
1
333
55555
[1]  一
[3]  三

與 JVM 版的是一模一樣的, 但速度超快,酷 !

 p.s. 美化數學符號
1. 先安裝 Fira code 字型,可到官方網站下載 https://github.com/tonsky/FiraCode 打包的 zip檔用 unzip 解壓縮後, 把裏面 ttf 目錄全部複製到  /usr/local/share/fonts 接著執行 fc-cache -f 讓 linux 系統重新讀取系統字型
          sudo  cp   -r   ttf   /usr/local/share/fonts
          fc-cache  -f
2. 開啟 VS code 點選上面 File -> Preferences -> settings 開啟檔案 settings.json 修改設定, 接著在右邊欄點擊標籤 USER SETTINGS 或是 WORKSPACE SETTINGS 加入以下兩行設定後關閉並存檔(save):
         {
               "editor.fontFamily": "'Fira Code'",
               "editor.fontLigatures": true,
         }
安裝完 fira code 之後一些像是 ==, != , -> 等等符號就改變造型或連接起來了,讓程式變得更像數學方程式,感覺比較順眼


2018年8月25日 星期六

玩玩 Dart 及 VS code

作為新一代程式語言,  kotlin 不僅易懂也容易學, 他不但可以編譯成 JAVA (Kotlin JVM), Javascript (Kotlin JS), 還可編譯成原生系統的程式(Kotlin native),真的是一個相當優秀的程式語言架構,但它的編譯(compile)速度實在不敢恭維, 目前還有另一個相當優秀的程式語言 Dart 也值得學習,初看 Kotlin 與 Dart 的語法 , 型態宣告的方式有所不同,一個放在變數後面, 另一個則放在前面. 而DART 比較像是 c 語言的作法,將資料型態宣告放在變數前面
// Kotlin 的寫法 my.kt
         fun main(args: Array <String> ) {     
             print("Hello");
         }
 // Dart 寫法  my.dart
         main(List <String>  args)  {
               print("Hello");
         }
要玩 Dart 先到官方網站下載 dart SDK
      https://www.dartlang.org/tools/sdk/archive
我下載 linux 64 位元 zip 版本的 Dart  SDK  2.0.0:
             https://storage.googleapis.com/dart-archive/channels/stable/release/2.0.0/sdk/dartsdk-linux-x64-release.zip
用 unzip 就可將它解壓縮, 再將解壓縮的目錄用  mv 指令改成  $HOME/dart, 寫程式免不了要用整合開發編輯器(IDE), VS code 就是一個開源的 IDE, 可到官方網站去下載:
             https://code.visualstudio.com/Download
我選擇下載 linux 64 位元 .tar.gz 版本, 再使用 tar -xvf 解壓縮, 接著把目錄名稱用  mv 指令改成 $HOME/vscode,再寫個 bash script 設定好環境變數,將它存成 dartenv 檔案,方便以後使用:
       #!/bin/bash
       alias path='echo "$PATH"'
       function pathadd() {
             if [ -z "$1" ]
             then
                  PATH=$PATH:$(pwd)
             elif [ -d "$1" ]
             then
                    PATH=$PATH:$1
             fi
       }
       pathadd  $HOME/vscode/bin
       pathadd  $HOME/dart/bin
開啟終端機, 執行上述文稿設定好 PATH 環境變數 :
       .   dartenv
執行 VS code 執行檔 code
      code
在 VS code 側邊工具欄,有兩個常用的工具,其中禁止害蟲的標誌就是除錯工具(debug tool), 拼圖的標誌是擴充工具(extensions),先點擊擴充工具並搜索 Dart language, 找到後安裝它.或是隨便開啟一個用 dart 語法寫的文稿, 它會提示下載所支援的擴充工具,還蠻聰明的.另外可以安裝 Chinese(Traditional) 擴充工具將面板中文化,在 VS code 裏面就可以開始玩 Dart了.在面板上面有個綠色三角形按鈕(稱為 run button),點擊如果無法執行,它會提示修改 launch.json 設定檔,有可能是 "program": 的設定位置不對,我將它改成  "program":  "my.dart" 並存檔,按下 run button 就可以執行 dart 了.

有了 VS code 搭配 Dart language 擴充工具, 編寫程式時,比較能寫出正確語法,一旦拼錯或語法錯誤就會顯示紅色漂浮底線,如果是警告會用綠色漂浮底線,提醒使用者要去修正或注意,除此之外,它對關鍵字的輸入預測還蠻準的,真的是一個非常實用的工具程式,值得試試.

後記 p.s.
1. VS code 左邊側邊欄工具,由上而下分別是: 搜索與取代(放大鏡), 檔案總管(檔案夾), 版本控制(流程圖),  除錯工具(禁止害蟲圖), 擴充工具(拼圖). 雖然 VS code 整合了 git 管理版本控制程式,但也必須明確告知該專案是否需要做版本控制. 當點擊版本控制工具時出現錯誤訊息,只要在該專案的目錄底下開啟終端機執行 git init 啟動 git 監控專案檔案的變化,就可以解決找不到 souce control 的問題 .

2. VS code下載擴充工具後,會將它放在 $HOME/.vscode/extensions 底下,可以自行備份

3. 習慣 c 語言或 Javascript 的應該可以很快能熟悉 dart 語法, 它們實在太像了,它的語法更像是 Javascript, 因此要習慣用 var 來宣變數的方式,讓 compiler 自動預測變數的資料型態,也避免寫錯, 與 c 語法相同的是每個表達式都必須用分號 ; 做結尾, 而函式也不需要像 Javascript 用 function 去作宣告,但所有變數都必須用 var 或是用像是 c 語言的語法明確指定資料型態方式來宣告(Javascript 不用  var 宣告時卻直接使用,恐會變成 gobal 變數, 當打錯字時,會無法發現錯誤,這是很危險的!), 用 var 宣告的好處是:多數時候不需明確指出變數的資料型態, Dart 2 可以根據上下文正確判斷出變數的資料型態

4. Dart 語言比 c 語言多了像是第 1 字母大寫 List, Map ... 等等的資料型態, List 可以用中括號 [ ] 將成員用逗號一一分開列表[value, ... ],而 Map 是字典資料型態, 它用大括號 {  } 將成員使用冒號 : 成對搭配,並用逗號一一分開列表{key:value, ...}.兩者都可以透過 [ ] 取出其中單一成員.

5. Dart 的 lambda (希臘字母λ)函式用胖箭頭(fat arrow)來描述 ( ) =>等號左邊 ( ) 是輸入參數,箭頭右邊是輸出表達式(簡稱函式).

6. 使用 VS code 編輯 Dart 程式時 ,很多物件有內建成員函式(member function), 在 VS code 利用物件句點, 當打完句點時,就會出現成員的列表提示,使用上下鍵就可瀏覽,按下 Enter 鍵直接取用, 簡單的範例程式
    // my.dart
    main( ) {
        var a = ["123", "456"]; //等同用 var a=List.of( ["123", "456"] ) 指定 a 是字串陣列       
        var b = {1:"one",2:"two"};//等同用 var b=Map.of(  {1:"one", 2:"two"} ) 指定 b 是字典
        var c =  (x) => x+3 ;  // 定義  λ(x)=x+3 指定 c 是 lambda 函式物件
        var  test = (a,b) => a==b ? "same object":"different object";//定義測試 λ(a,b)
        var d=a        //  沒有用建構函式,所以 d 只是 a 的別名(alias)
        var f =  ["123", "456"]; // 會使用 List 建構函式, 等於是 new List, 實體化另一個物件
        a.forEach(print);      // 用內建函式(member function forEach)將 a 的成員一一列印出來
        for ( var i in b.keys ) // 使用 for 迴圈, i 必須用 var 宣告成變數,循序取出索引值 key
              print( b[i] );      // 將 b 的成員一一列印出來, 字典可以用 [ ] 取出單一成員
        print( c(3) );            // 列印出 c(3) = λ(3) 的函數值
        print(d);                 // 列印 d 的物件
        print( test(a,d) );  // 測試 a==d
        print( test(a,f) );   // 測試 a==f 
    }
執行 dart my.dart  或是在 VS code 點擊 run 按鈕看輸出結果:
123
456
one
two
6

[123,  456] 
same object
different object

2018年8月22日 星期三

理解 Kotlin 的 function


程式(Function)的資料型態稱為 funtion type, 它用來定義函式輸出入變數的資料型態,它的語法是小括號接箭頭符號 ( ) ->, 小括號裏面是輸入參數(input parameters)的資料型態(type),箭頭右邊是函式輸出值(funtion output 簡稱函數)的資料型態
        ( input_type, ... ) -> output_type

為了簡化常用的 funtion type, 可以利用 typealias 宣告:
        typealias   myFuntionType =  (Int, String, Any) -> Unit

 Unit 是一種資料型態,它沒有傳回值,呼叫函式可以用程式的成員函數  .invoke( ) 來呼叫, kotlin 程式可以省略 invoke, 而直接用小括號( )就可執行運算得到函數值
        var anonymousFuntion: myFuntionType = fun(x,y):Int { return x+y }
        println("function value is: "+anonymousFuntion(3,5) )
        
上面的關鍵字 fun 就是用來定義函式, 小括號 ( ) 用來定義輸入參數,用冒號 : 定義資料型態, 小括號後面的冒號是定義函式的輸出資料型態,而上述不帶名稱的 fun 定義稱之為匿名函式(anonymous function),Kotlin允許將函式定義在函式內,稱之為區域函式(local function).定義帶名稱區域函式可以像這樣寫:
    fun main(args: Array < String > ) {
        fun myFunction(a: Int, b: Int):Int  {         return a+b              }
        println("function value is:"+myFunction(3,5) )
     }
Kotlin 有一種特殊型態的函式稱為 lambda (希臘字母 λ),它用含箭頭的大括號 {  ->  } 區塊敘述直接編寫程式邏輯並回傳函數值,箭頭左邊是輸入參數, 箭頭右邊是程式邏輯,最後一行的運算值當成函數回傳值,所有的 kotlin 物件都有資料型態,而 lambda 的資料型態就是 function type,因此宣告 lambda 函式   λ(x)=x+3 可以這樣寫:
       var  λ: (Int)->Int = { x -> x+3 }

多數的 lambda 函式可以推導輸出變數的資料型態,因此可以省略 function type 的宣告,而將輸入變數放在函式區塊裡面宣告,因此上式可以簡化為:
       var  λ = { x:Int -> x+3 }

如果 lambda 沒有輸入變數時,也就無需宣告資料型態,因此可以再省略箭頭符號,只剩區塊敘述 { },而最後一條敘述就是回傳值:
       var λ = { println("Hello λ") ; 3}
       println( λ( ) )

當函式的輸入參數包含有另外一個函式時,該函式稱為高階函式(high order function),Kotlin 的高階函數有個特異功能是:當最後一個參數如果是 lambda 函式時,可以將 lambda 函式區塊與參數分離,把它放在高階函式的小括號( )後面,而且當參數是以 lambda 函式為唯一參數時,甚至可以將小括號( )省略不寫,另外當傳進 lambda 函式前面的參數不使用的話,可以用底線 _ 將以上的參數加以省略,例如:

      typealias   λType =  (String, Int, Int) -> Int  // 自定的 function type 別名
      fun main(args: Array) {
          fun λp(λ: λType){            // 定義高階區域函式
                println( λ("hello",3,5) )  // 先呼叫
函式,再列出函數值
          }
         λp { z,x,y -> println(z+":$x+$y="); x+y }  //  λp(x,y) = x+y
         λp { _,x,y -> println("Hi:$x*$y="); x*y }   //  λp(x,y) = x*y
      }


當輸入兩個函式後組合成另一個函式回傳的高階函式稱之為複合函式(compose function)
當 lambda 輸入參數只有一個時, 可以利用  it 當成輸入參數,無需用箭頭符號宣告參數, 並且呼叫成員函數時也可以省略小括號 ( ), lambda 就只留下區塊敘述 { }

       typealias    int2bool = (Int) -> Boolean
       typealias    str2int     = (String) -> Int
       typealias    str2bool = (String) -> Boolean
       fun composeBy( f: int2bool, g: str2int ): str2bool {  // high order compose function
              return { x -> f(g(x)) }    // return the lambda
              // only one parameter , so we can skip -> and use it
             //  return {  f(g(it))  }
       }

       fun main(args: Array <String>) {
              var f: int2bool = { x -> (x%2)==1 }     // fiter if  odd number
              var g: str2int    = { s -> s.length  }      // get the string length
              val strings = listOf("1", "22", "333","4444","5555")

              // strings.filter (  composeBy(f,g)  )   // call  the compose function
               strings.filter { f(g(it)) }    // use  lambda function block              
              .also { println(it) }                     
        }




2018年8月19日 星期日

玩玩 kotlin

透過 sdkman 來安裝 kotlin compiler:
   cd $HOME
    wget -O sdk.install.sh "https://get.sdkman.io"
    .   sdk.install.sh
    .  $HOME/.sdkman/bin/ sdkman-init.sh
    sdk   install   kotlin

安裝 openjdk8:   
    sudo apt-get update &&  sudo apt-get install openjdk-8-jre-headless

建立工作目錄:
    mkdir    $HOME/kotlin
    cd    $HOME/kotlin

編寫 bash 腳本,儲存設定檔 $HOME/kotlin/senv, 簡化使用方式:
#!/bin/bash
export tmpjar=/tmp/jarout.jar
function kt() {
    java   -jar    $tmpjar    "$@"
}
function ktc() {
    kotlinc    $1    -include-runtime    -d    $tmpjar   &&   shift;
    java    -jar    $tmpjar    "$@"
}

將 senv 設定成可執行檔 :
    chmod   +x   $HOME/kotlin/senv
    .   $HOME/kotlin/senv
 
編寫 kotlin 程式, 儲存成 my.kt
//  my.kt:  kotlin example file
fun main(args: Array <String> ) {
     for ( s   in  args )  {

          println("Hello, parameters: " + s)        
     }
}
// end of my.kt

編譯並執行:
    ktc   my.kt   arg1    arg2   arg3   ...

單純執行剛剛編譯好的執行檔 ( java   -jar   /tmp/jarout.jar    args    ... ):
    kt    arg1    arg2   arg3   ...


使用安裝好的 kotlinc 進入交談模式(執行 kotlinc 不加任何參數,就會進入 REPL) ,速度非常慢,若要練習 kotlin語法, 可以直接連上網站, 速度可能會比較快:
https://try.kotlinlang.org/#/Examples/Hello,%20world!/Simplest%20version/Simplest%20version.kt