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参照、例