攻める!ラムダ式禁止おじさん #kotlin_kansai

攻める!ラムダ式禁止おじさん #kotlin_kansai

Kotlin 1.0リリース記念勉強会 in 京都 ( http://kanjava.connpass.com/event/27758/ ) で発表したスライドです。
少し補足を書きました→ http://taro.hatenablog.jp/entry/2016/04/03/173831

14c9795d267f5b85abb98ca5e8780646?s=128

Taro Nagasawa

April 02, 2016
Tweet

Transcript

  1. 攻める! ラムダ式禁止おじさん 2016-04-02 Kotlin 1.0リリース記念勉強会 in 京都 長澤 太郎 @ngsw_taro

  2. Kotlinってラムダ式使えて快適〜(^q^) list .filter { it % 3 == 0 }

    .fold(0) { a, e -> a + e } .let { println(it) }
  3. おじさん曰く

  4. ラムダ式は 難しい! おじさん曰く

  5. 複数の文が入り込む余地があるのが... list .filter { hoge(); fuga(); piyo(); it % 3

    == 0 } .fold(0) { a, e -> a + e } .let { println(it) }
  6. ラムダ式なんか禁止だ〜〜〜〜〜!!! list .filter(myCondition) .fold(0, Int::plus) .let(::println)

  7. トリにして

  8. トリにして ネタ枠 です

  9. もくじ 1. 関数参照・メソッド参照 2. カリー化、関数の部分適用 3. Java的なメソッド参照 4. nullableどうするの問題 5.

    まとめと注意事項
  10. 自己紹介 • 長澤 太郎 たろーって呼んでね • @ngsw_taro • プログラマー@エムスリー株式会社 ◦

    Android, Kotlin, Java, Scala, Rubyなど • Kotlinエバンジェリスト(JetBrains黙認) ◦ 日本Kotlinユーザグループ代表 ◦ Kotlin入門書 目下執筆中! • やすべえとディズニーが好き
  11. • 日本・世界の医療改善を目指し医療関連のサービスを開発・ 運営 • 今回のゲストスピーカーをスポンサー • Androidアプリ開発でKotlinを利用している • 開発エンジニア、常に募集中! エムスリーの紹介

  12. 1. 関数参照・メソッド参照

  13. 引数を1つだけ取る関数 123.let { println(it) }

  14. 引数を1つだけ取る関数 123.let { println(it) } ここのラムダ式をなくしたい!

  15. 関数参照 val println: (Int) -> Unit = ::println 123.let(println) 123.let(::println)

  16. 関数参照 val println: (Int) -> Unit = ::println 123.let(println) 123.let(::println)

    関数オブジェクトへの 参照を取得できる
  17. 関数参照 val println: (Int) -> Unit = ::println 123.let(println) 123.let(::println)

    関数の型
  18. 関数参照 val println: (Int) -> Unit = ::println 123.let(println) 123.let(::println)

    普通に引数として渡すだけ
  19. 関数参照 val println: (Int) -> Unit = ::println 123.let(println) 123.let(::println)

    もちろん直接もOK!
  20. 引数を取らないメソッド "hoge".let { it.toUpperCase() }

  21. 引数を取らないメソッド "hoge".let { it.toUpperCase() } ここのラムダ式をなくしたい!

  22. メソッド参照 val toUpperCase: String.()->String = String::toUpperCase "hoge".let(toUpperCase)

  23. メソッド参照 val toUpperCase: String.()->String = String::toUpperCase "hoge".let(toUpperCase) 関数オブジェクトへの 参照を取得できる

  24. メソッド参照 val toUpperCase: String.()->String = String::toUpperCase "hoge".let(toUpperCase) 関数の型

  25. メソッド参照 val toUpperCase: String.()->String = String::toUpperCase "hoge".let(toUpperCase) レシーバの型

  26. メソッド参照 val toUpperCase: String.()->String = String::toUpperCase "hoge".let(toUpperCase) 引数リスト->戻り型

  27. メソッド参照 val toUpperCase: String.()->String = String::toUpperCase "hoge".let(toUpperCase) 普通に引数として渡すだけ

  28. できたこと 123.let { println(it) } 123.let(::println) "hoge".let { it.toUpperCase() }

    "hoge".let(String::toUpperCase)
  29. 2. カリー化、関数の部分適用

  30. 引数を2つ取る関数 fun minus(a: Int, b: Int) = a - b

    3.let { minus(5, it) } //=> 2 ここのラムダ式をなくしたい!
  31. デフォルトでカリー化してるっぽく見せる operator fun <A, B, R> ((A, B) -> R).invoke(a:

    A): (B) -> R = { this(a, it) }
  32. デフォルトでカリー化してるっぽく見せる operator fun <A, B, R> ((A, B) -> R).invoke(a:

    A): (B) -> R = { this(a, it) } 「AとBを引数に取り、Rを返す関数」に 拡張関数invokeを生やす
  33. デフォルトでカリー化してるっぽく見せる operator fun <A, B, R> ((A, B) -> R).invoke(a:

    A): (B) -> R = { this(a, it) } Aを引数に取り、 「Bを引数に取り、Rを返す関数」を返す
  34. デフォルトでカリー化してるっぽく見せる operator fun <A, B, R> ((A, B) -> R).invoke(a:

    A): (B) -> R = { this(a, it) } 演算子オーバロードにより function2.invoke(foo) ↓ function2(foo)
  35. デフォルトでカリー化してるっぽく見せる operator fun <A, B, R> ((A, B) -> R).invoke(a:

    A): (B) -> R = { this(a, it) } つまり、invoke関数の存在によって (A, B)->R を (A)->((B)->R) とも見なせる
  36. minusを1引数関数として使える! fun minus(a: Int, b: Int) = a - b

    (::minus)(5, 2) //=> 3 val x = (::minus)(5) x(2) //=> 3 (::minus)(5)(2) //=> 3
  37. ラムダ式を消せる! 3.let { minus(5, it) } //=> 2 3.let((::minus)(5)) //=>

    2
  38. 引数の位置を変えたい問題 5.let { minus(it, 3) } //=> 2 5.let((::minus)(3)) //=>

    -2
  39. 関数の部分適用 5.let { minus(it, 3) } //=> 2 5.let((::minus)(3)) //=>

    -2 5.let((::minus)(_, 3)) //=> 2 プレースホルダーを置いて、 引数の位置をコントロールしたい!
  40. プレースホルダー sealed class PlaceHolder { object アレ : PlaceHolder() }

  41. プレースホルダー sealed class PlaceHolder { object アレ : PlaceHolder() }

    型と、その唯一のオブジェクト的な ちなみに、Kotlinでは アンスコのみで構成される名前は使えない
  42. プレースホルダー sealed class PlaceHolder { object アレ : PlaceHolder() }

    クラスの継承を制限する
  43. プレースホルダー sealed class PlaceHolder { object アレ : PlaceHolder() }

    シングルトン・オブジェクトをつくる
  44. プレースホルダー sealed class PlaceHolder { object アレ : PlaceHolder() }

    object PlaceHolder + import PlaceHolder as アレ でもOK!
  45. プレースホルダー sealed class PlaceHolder { object アレ : PlaceHolder() }

    enum class PlaceHolder { アレ } でもOK!
  46. プレースホルダーを取る拡張関数invoke operator fun <A, B, R> ((A, B) -> R).invoke(p:

    PlaceHolder, b: B): (A) -> R = { this(it, b) }
  47. プレースホルダーを取る拡張関数invoke operator fun <A, B, R> ((A, B) -> R).invoke(p:

    PlaceHolder, b: B): (A) -> R = { this(it, b) } 「AとBを引数に取り、Rを返す関数」に 拡張関数invokeを生やす
  48. プレースホルダーを取る拡張関数invoke operator fun <A, B, R> ((A, B) -> R).invoke(p:

    PlaceHolder, b: B): (A) -> R = { this(it, b) } プレースホルダーとBを引数に取り、 「Aを取ってRを返す関数」を返す
  49. 1引数バージョンのminusを自在に使える (::minus)(5, 3) //=> 2 (::minus)(アレ, 3)(5) //=> 2

  50. ラムダ式を消せる! 5.let { minus(it, 3) } //=> 2 5.let((::minus)(アレ, 3))

    //=> 2
  51. 3. Java的なメソッド参照

  52. レシーバが指定されたメソッド参照 // Javaコード static class Calculator { int succ(int x)

    { return x + 1; } } public static void main(final String... args) { final Calculator c = new Calculator(); Optional.of(5).map(c::succ); }
  53. レシーバが指定されたメソッド参照 // Javaコード static class Calculator { int succ(int x)

    { return x + 1; } } public static void main(final String... args) { final Calculator c = new Calculator(); Optional.of(5).map(c::succ); } 「c」のsuccメソッド
  54. 一方、Kotlinは... class Calculator { fun succ(x: Int): Int = x

    + 1 } val calc = Calculator() 2.let(calc::succ) // NG 2.let { calc.succ(it) } // こう書くしか…?
  55. T.(A)->Rは、(T, A)->Rでもある! class Calculator { fun succ(x: Int): Int =

    x + 1 } val a: Calculator.(Int)->Int = Calculator::succ val b: (Calculator, Int)-> Int = a
  56. T.(A)->Rは、(T, A)->Rでもある! class Calculator { fun succ(x: Int): Int =

    x + 1 } val a: Calculator.(Int)->Int = Calculator::succ val b: (Calculator, Int)-> Int = a
  57. つまり val calc = Calculator() 2.let(calc::succ) // NG 2.let {

    calc.succ(it) } // OK 2.let { (Calculator::succ)(calc, it) } // OK
  58. さらに val calc = Calculator() 2.let(calc::succ) // NG 2.let {

    calc.succ(it) } // OK 2.let { (Calculator::succ)(calc, it) } // OK 2.let((Calculator::succ)(calc)) // OK
  59. さらに val calc = Calculator() 2.let(calc::succ) // NG 2.let {

    calc.succ(it) } // OK 2.let { (Calculator::succ)(calc, it) } // OK 2.let((Calculator::succ)(calc)) // OK 既にカリー化する方法を知っているので、 it を省略して、ラムダ式を消せる!
  60. 4. nullableどうするの問題

  61. 安全呼び出しするためには、ラムダ式が... setOf<Int?>(2).map { it?.let(::succ) } setOf<Int?>(2).map { it?.inc() }

  62. 安全呼び出しするためには、ラムダ式が... setOf<Int?>(2).map { it?.let(::succ) } setOf<Int?>(2).map { it?.inc() }

  63. 関数のnullable対応 fun <A, R> ((A) -> R).nullOk(): (A?) -> R?

    = { it?.let(this@nullOk) }
  64. 関数のnullable対応 fun <A, R> ((A) -> R).nullOk(): (A?) -> R?

    = { it?.let(this@nullOk) } 普通の関数を取って
  65. 関数のnullable対応 fun <A, R> ((A) -> R).nullOk(): (A?) -> R?

    = { it?.let(this@nullOk) } 「nullableを取って、 nullableを返す関数」を返す
  66. ラムダ式を消せる setOf<Int?>(2).map { it?.let(::succ) } setOf<Int?>(2).map { it?.inc() } setOf<Int?>(2).map(::succ.nullOk())

    setOf<Int?>(2).map(Int::inc.nullOk())
  67. 5. まとめと注意事項

  68. まとめ • 関数参照、メソッド参照により、スッキリしたコードが書ける • カリー化や関数の部分適用により、無理やり1引数関数の形 にして、ラムダ式を排除できる • Java的なメソッド参照のような記法は使えない。しかし、無理 やり1引数関数の形にして、ラムダ式を排除できる •

    notNullを取る関数を持ち上げて、無理やりnullableに対応さ せ、ラムダ式を排除できる
  69. 注意事項 • ラムダ式を使いましょう • 「単純な関数参照」はOK • ちょっと複雑な関数参照は、インライン展開されない場合が多 く、パフォーマンスに影響あり • わざわざトリッキーなコード書くな(怒)

  70. Thank you Enjoy Kotlin!