19.0504

関数の 省略記法/ラムダ式にして変数に代入/高級関数

Kotlin の、関数 は、省略して書く方法から、また、関数も、ただの、
  「何かの処理のために利用するための、おおざっぱに言えば 演算子のようなもの」 というだけでなく、
  「例えば、インスタンス のような、オブジェクト」 でも、あると言えそうです。
実際に、関数 を、変数に代入できますし、
  関数が代入された変数 は、関数同様に使えます
これの何がいいのか、、私ごときが思いつく例だと、
例えば、
  val x = ::longLongLongLongNameFun
とすれば、たったx で、x(10, 20) のように書けてしまいます。

ただ、私などは、なにより理解することが、第1なので、省略記法とか、しゃらくせえやみたいにも、
思っていましたが、
きっと、省略記法 に慣れたプログラマの方たちは、Kotlin の話題に触れる時、
それを使って、説明するだろうと思われます。

また、私自身も、同じく簡単に書けるという点で、when式/文 を何回か書いていると、もう、if/else は、
だんだん書きたくなってきてしまうようです。

これらをマスターするために、最初理屈を理解するのは、大変だと思いますが、
何とか理解して、たくさん書けば慣れると思います。

この理解するという事に関しての、最難関は、「クロージャ/closure」 ではないでしょうか。
よく、クロージャの説明として、「関数を返す関数」 とも、言われているようです。
それは、間違いではないのですが、
  それだけではなく、
    例えば、
    「関数を返す と、同時に、[例えばインスタンスの持つフィールドのようなもの]も、含めて、
  return される
と、言えると思います。
私も、クロージャを使ったコード を、書いてみましたが、
  何のために、これを使うのだ?
という感じです。
なんとか、理屈は分かってもらえるとは、思いますが、
本当に使えるようになるためには、今はまだ思いつきませんが、
  クロージャを、うまく使っている コード/例
を、何回も体に染みつくまで、ペンとかでもいいと思うけど、書いて覚えるのが、早道かな~と思います。


1 .関数 の 省略記法
ここでは、普通に定義した関数を、2段階 で、省略するのを見ていきます。

ステップ0
これを、ベースとなる関数 にします
(streamlined:スリム化された)
これは、普通の/ベースの 関数定義です。
答えは 「3.3333333333333335」 です。
(お金の計算には、使えません!が、ここでは触れません、詳しく知りませんし。)

ステップ1
では、streamlinedFun0()関数 を、省略して、短くします
これは、
  =以下の値 が return される
つまり、
  = とは、「{ } と return が、変身した」 と、考える
    / ただし、return しない、関数もあります
とすれば、OKでしょう。

ステップ2
さらに、streamlinedFun1()関数 を、省略して、短くします
ここで、省略しているのは、
  戻り値/返し値
  : Double
です。
これは、
  Double/Double -> Double
というのは、
  ルール
だと考えてください。
それで、
  型推量 を、使って、省略できます。


2 . ラムダ式 にして 変数 に 代入
ラムダ式(lambda expression)とは、関数 のことです。
それを、
  { }-> を、使って表現したもの
と、言えます。
そして、ここも大事なところですが、名前がありません(無名関数)。
名前がないのですが、
  短く書けるので、関数の引数 などで使うと、便利
    / 関数を定義する手間を省ける.
です(ここでは、触れません)。
たとえば、先ほど紹介した、
  streamlinedFun2
    / fun streamlined2(x: Double, y:Double) = x / y
を、
  ラムダ式 で書くと、
  {x: Double, y: Double -> x / y}
と書けます。
こうやって見ると、関数の省略記法 と、よく似ています(ラムダ式には名前がありませんが)。
  = が、->
に、なっただけです。
ここでは、先ほどやったことを、ラムダ式を変数に代入して、実行してみましょう。

fun main() { val lambdaExpression = {x: Double, y: Double -> x / y} println(lambdaExpression(10.0, 3.0)) }
これで、先ほどと同じ、「3.3333333333333335」 が、出力されます。

ラムダ式の構文:
  { 引数: 型 -> 本体 }
となります。
(ラムダ式の書き方には、「ゆれ」がありますが、基本が分かれば、対応できると思います)
戻り値は、型推量 です。
もし、型推量が出来ない場合は、ラムダ式 は、使えません
その場合は、普通の無名関数 を、使います。
この場合は、ラムダ式が使えますが、無名関数を使うと、

fun main() { val fn = fun(x: Double, y: Double): Double = x / y println(fn(10.0, 3.0)) }
こうなります。
  fun の あとに、関数名 が、ありません、、それで、無名の関数 なのです。

ラムダ式の理屈は、こんなところです。

これを、どんな場面で使うかは、まだ私には、書けませんが、
今後、トライするつもりでいます。


3 .関数 の細かいルール の補足
ここで、関数に関する、今まで、取りこぼしたと思われる基本ルールなど を、まとめてみます。

第1級オブジェクト、 高級関数
ここで、見てきたように、
  関数は、変数 に代入できます
そして、この柔軟性は、(Javaが、JavaScriptのやり方を取り入れたように)素晴らしいです。
このことから、関数は、第1級オブジェクト(first-class object)と、呼ばれます。

これで、Kotlin の、オブジェクト は、細かいことを言えば、 3種類 あると思われます。   1. value-type/値型: ある時は、インスタンス としてふるまい(Integerのように)     ある時は、プリミティブ型(primitive-type)としてふるまう。   2. value-type とは、逆の意味で、純粋な、インスタンス   3. 関数オブジェクト(しかし、Anyクラスの、3つの関数は、正確には、メソッドでしょ。)
また、
  ・ 関数の引数 としての 変数 として、使える
  ・ 戻り値 と、して使える(こうなると、クロージャ で、単に変数に代入するのとは、違う部分がある)
そして、そんな使い方をしている、関数 のことを、高級関数(higher-order function) と言います。


戻り値 のない関数
関数には、戻り値/返し値 のない、関数もあります。
  単に、println("なになに") だけなら、returnキーワード は、必要ありません。
ただその時に、コードとして、書く方法があります。
普通は、省略(すべき)ですが(IntelliJ先生がぶつぶつ言います)、
IntelliJ では、透かし文字 として、Unit と、一応入れてくれます。
なので、そのことは、何だ、Unit とは?、と思わないように知っておくべきでしょう。
しかし、確認のために、今コードを書いたら、透かし文字 は、入ってなかった。
まあ、とにかく知っておいた方がいいでしょう。

fun unitFun(): Unit { println("I'm unitFun()") } fun main() { unitFun() }

ここで、再確認も含めてみていきましょう。

関数を、変数に代入する、2通り

1. 関数名で代入fun unitFun(): Unit { println("I'm unitFun()") } fun main() { val fn = ::unitFun // () などは、つけない  ->  :: と、関数名 だけ fn() }
2. 無名関数 を、そのまま代入する(先にやったコード)fun main() { val fn = fun(x: Double, y: Double): Double = x / y println(fn(10.0, 3.0)) }

関数 の 型:表し方

構文: ( 引数 ) -> 戻り値

(Int, Int) -> Int:こんな風に表します。fun main() { val fn: (Int, Int) -> Int = fun(x: Int, y: Int): Int = x * y println(fn(56, 78)) } ポイント:引数の方は、() でくくります!


4 .クロージャ(Closure)/ 関数の引数 として使う
まずは、簡単な方の、関数の引数 として、関数 を使う方法を見ていきます。

関数の引数 として、関数 を使う
こんな例を書きました
今までの知識を総動員すれば、分かると思われます。
考えてみてください。


クロージャ
クロージャ って何に使うの?
な感じですが、
とりあえず、使い方だけ理解したいと思います。

例1:
このコードで考えて見ましょう
関数useClosure1() は、
その中に、useFun() を、作り、そして、returnしています。
また、main()関数の中で、
  val z = useClosure1(100)
で、
  useClosure1()に、引数に、100 を与えて、返し値useFun を、変数z に、返しています。
  ここで、です!
  勘違いしやすいのは、変数zは、useFun()である!と、考えることです。
  でも、もしそうであるとしたら、最初に、useClosure1()に、与えた、「引数、100」 は、
  何だったのでしょうという事にならないでしょうか?
ですから、
   変数z とは、useClosure1()に、「引数、100」 を、与えた、環境/閉じられたエリア
と、考えるべきではないでしょうか
クロージャ/closure という単語に、適切な訳はないかと思って調べたら、こんな日本語訳がありました。
  closure : 密封装置 - weblioより -
とにかくこう考えれば、何に使うのかはともかく、理解だけは出来ると思います。

例2(おまけ):
これは、まったく意味のないコードだと思われますが
クロージャを理解するために、「検証のために」、書きました。
多分、今述べた考え方でいいと思います。



まとめ
・ コード省略化:3ステップ
・ ラムダ式:
   ラムダ式(lambda expression)とは、関数 のことです。
     それを、 { }-> を、使って表現したもの。
・ ラムダ式の構文:
      { 引数: 型 -> 本体 }
   となります。
   (ラムダ式の書き方には、「ゆれ」がありますが、基本が分かれば、対応できると思います)
   戻り値は、型推量 です。
   もし、型推量が出来ない場合は、ラムダ式 は、使えません
第1級オブジェクト、 高級関数 の意味。
・ 戻り値/返し値 の ないことは、Unit で表せます、が、普通は、省略します。
・ 関数 を 変数 に代入する:
   1. :: を、関数名のみに付けます
     例: val fn = ::unitFun
   2. 無名関数 にして、代入する。
     例: val fn = fun(x: Double, y: Double): Double = x / y
・ 関数の型 の、表し方
   構文: ( 引数 ) -> 戻り値
     例: val fn: (Int, Int) -> Int = fun(x: Int, y: Int): Int = x * y
・ 関数の引数 として、関数 を使う: 
・ クロージャ:本文4参照