全てのプログラマに捧げるScala入門 コレクション
はじめに
この記事ではScalaのコレクションについて解説する。
しかし、コレクションをすべて隅から隅まで紹介するにはこの一つの記事では難しいため
特によく使うものに厳選して解説する
コレクション
コレクションにはmutable(可変)とimmutable(不変)のものが存在するため
まずそれらについて解説し、その後にそれらを踏まえた性能の特性について解説する。
特に、immutableであるコレクションを使う際にはそれぞれの性能特性を理解しておくことが
パフォーマンス的に重要になってくる。
不変と可変
不変と可変はこれ以前の記事で解説した、valとvarに似たとようなものである。
しかし、コレクションとこれらを併用することで微妙な差が出てくる。
ここでは特にそれを紹介する。
コレクションにおける不変と可変
次のようなimmutableなリストを例として挙げる。
val list: List[Int] = List(1,2,3,4,5)
このリストに対して、インデックスを使用して値を参照する場合は
applyメソッドか()でインデックスを指定する。
list(0) //return -> 1 list.apply(1) //return -> 2
インデックスを取得できるなら値の更新ができるはずだが、immutableリストではそうならない。
scala> list(0) = 10 <console>:9: error: value update is not a member of List[Int] list(0) = 10 ^
エラーにも出ているがimmutableなコレクション、今回の場合で言えば特にイミュータブルリストは
updateという値の更新をするメソッドが存在しないために更新できない。
immutableとは内部の値も更新できない、つまりは内部状態が不変なコレクションと言える。
それでは値を更新したい場合はどうするのか。
解決策は至って簡単でListを作りなおすことである。
先頭に要素を追加する場合と末尾に要素を追加する場合の2つを紹介する。
scala> 0 :: list res4: List[Int] = List(0, 1, 2, 3, 4, 5) scala> list :+ 6 res5: List[Int] = List(1, 2, 3, 4, 5, 6)
このように::と:+を使うことでそれぞれ先頭追加、末尾追加が行える。
そして、値を追加した後、新しいリストが返ってきている。
しかし、これを次のように再代入はできない。
scala> 0 :: list res4: List[Int] = List(0, 1, 2, 3, 4, 5) scala> list :+ 6 res5: List[Int] = List(1, 2, 3, 4, 5, 6)
というのも、listという変数は今valで宣言されており
値を追加したimmutableなリストは新しいListオブジェクトとなっているので
valにおいては参照するオブジェクトが変更されることに等しいので再代入はできない。
なので、実際に上記のコードのように再代入したいのなら変数はvarで宣言する必要がある。
それでは次にmutableリストについて解説する。
immutableリストを解説したあとだとこちらはわかりやすいかもしれない。
まずmutableリストを使うにはcollection.mutableパッケージをインポートする必要がある。
import scala.collection.mutable.ListBuffer
mutableなリストはMutableListが存在するが実際にはListBufferを使うことが多いと思うので
今回はそちらをインポートする。
そして先ほどと同様にリストを宣言する。
scala> val list: ListBuffer[Int] = ListBuffer(1,2,3,4,5) list: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4, 5)
valで宣言していことを覚えておきながら値の更新を行う。
//index = 0の値を2に更新 scala> list.update(0, 2) scala> list.foreach(println _) 2 2 3 4 5
更新が正常に終了している。
valで宣言したmutableコレクションの値を更新することが出来たのは
コレクション、今回ならListBufferのオブジェクト自体に変更はなく
変更されたのはコレクションの内部状態であるからvalでも変更ができた。
これと同様に、先頭や末尾への追加がおこなえる。
性能特性
性能特性に関しては、いちからまとめるより公式のドキュメントがよくまとめているのでそちらを紹介する。
特に気をつけたいのは線形と定数となっている部分である。
例えばimmutableなListでは末尾への追加が線形になっていて先頭への追加が定数となっている。
これが意味することは末尾への追加はその要素数分だけコストがかかり、先頭への追加は一定のコストしかかからないということである。
もし要素数が多いリストに値を追加するなら最初から先頭追加にして、
追加が完了した段階でreverseなどをつかって逆順にすることの方が賢い方法と言える。
シーケンス
ここでは特によく使うシーケンスのコレクションについてのみ解説する。
シーケンスとは要素が順序をもっておりインデックス等で要素を指定できるコレクションのことである。
先ほど紹介したListもシーケンス型のコレクションに含まれている。
リスト
Scalaにおけるリストは次のような構造になっている
List(1,2,3, ..., n)
これをもっと掘り下げると次のような構造になっている。
1 :: 2 :: 3 :: ... :: n :: Nil
先頭追加のメソッドが連続した状態で、末尾がかならずNilになっている。
データ型については前章を見てもらうとわかるが型推論でIntになっている。
これが例えばIntとDoubleが混在したリストだとどうなるだろうか?
List(1,1.0) res4: List[Double] = List(1.0, 1.0)
IntはDoubleにキャストできるためDoubleのリストとして解釈され、Intの要素もDoubleに変換されている。
では次にIntとStringの混在リストはどうなるだろうかやってみよう。
scala> List(1, "hello") res5: List[Any] = List(1, hello)
すべてのクラスの親クラスであるAny型に推論されている。
推論はされているが、実際はこのようにいくつかの型をまとめるようなデータ構造は避けるべきである。
次にリストのなかでよく使うメソッドを一気にコードとして紹介する。
//Listでは無いものをListに変換する scala> val list = (1 to 10).toList list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) //先頭要素を求める scala> list.head res0: Int = 1 //先頭以外を求める scala> list.tail res1: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10) //サイズを求める、sizeでもいい scala> list.length res2: Int = 10 //空かどうかを求める scala> list.isEmpty res3: Boolean = false //末尾要素を求める scala> list.last res4: Int = 10 //末尾以外を求める scala> list.init res5: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9) //先頭からn分だけリストにする scala> list.take(5) res6: List[Int] = List(1, 2, 3, 4, 5) //末尾からn分だけリストにする scala> list.takeRight(5) res7: List[Int] = List(6, 7, 8, 9, 10) //先頭の要素n個を除いたものを求める scala> list.drop(5) res8: List[Int] = List(6, 7, 8, 9, 10) //末尾の要素n個を除いたものを求める scala> list.dropRight(5) res9: List[Int] = List(1, 2, 3, 4, 5) //引数が含まれているか求める scala> list.contains(5) res11: Boolean = true //逆順にする scala> list.reverse res12: List[Int] = List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) //最大値 scala> list.max res13: Int = 10 //最小値 scala> list.min res14: Int = 1
配列
リストだけでなく当然配列も存在する。
配列は要素のシーケンスを保持することができて、任意の要素に対して一定のコストでアクセスできる。
一般的な配列はScalaだと次のようにかける。
//要素数5で作成 val array = new Array[Int](5)
もちろんListのようにパラメータでも初期化できる。
メソッドに関してもほとんどListと同じで大きな違いといえば性能面になる
バッファ
最初に紹介したListBufferと同様に、mutableな配列やシーケンスは大抵****Bufferの名前え存在する。
これは内部状態がミュータブルであるが、利用できるメソッドとして大きな違いはない。
コレクションと関数
ここではリストの要素に関数を適用して新しいリストを求める場合について行う。
前の記事で紹介したラムダ式を多用することになる。
map
これは新しいリストを返す制御構造のなかで特に使うものであり次のように使用する。
scala> val list = 1 to 10 toList list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> list.map(num => num * 2) res0: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
関数にたいして要素を一つずつ適用し、返ってきた値を新しいリストとして生成しているのがわかる。
この程度であればわざわざ明示的にラムダを書く必要がなくワイルドカードを使用して簡潔にかくことができる。
scala> val list = 1 to 10 toList warning: there was one feature warning; re-run with -feature for details list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> list.map(_ * 2) res0: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
filter
これは評価式をもつ関数を渡し、それに対して要素が真であるmのをリストとして返すものである。
使い方自体はmapと同じになる。
scala> list.filter(num => num % 2 != 0) res1: List[Int] = List(1, 3, 5, 7, 9)
foreach
これはリストの要素に対して、繰り返し処理をする時に使う。
よく使うのは要素の表示など
scala> list.foreach(println _) 1 2 3 4 5 6 7 8 9 10
ただし、新たなコレクションが返ってくるわけではない。
おわりに
ここではまだ紹介していないMapやSet、Tuppleなどのコレクションや
畳み込みなどのメソッドがあるので、Scalaをもっと使いこなすにはそれらへの理解も必要になるだろう。
次はパターンマッチについての紹介になる。
全てのプログラマに捧げるScala入門 トレイト
はじめに
Scalaで規模の大きいプログラムを作成する場合にモジュール化するのは非常に重要なことである。
そこでモジュール化のための機能をココでは紹介する。
JavaにおけるInterfaceと同等の機能を持つtraitである。
トレイト
トレイトとは次の様に宣言する。
trait Name{
}
宣言自体はtraitというキーワードを使うだけなので宣言は比較的簡単である。
それでこのトレイトで何が出来るかというとメソッドとフィールドの定義を持つことができる。
ScalaのトレイトはほとんどJavaのInterfaceと同じであるが、決定的に違う点がある。
それはinterfaceの様な機能を持ちながら、メソッドが宣言のみで処理を持たないものだけでなく
実装をもったメソッドも含めることが出来るという点である。
実装をもつメソッドも含めるということはそれだけ機能を抽象化するだけでなく
カプセル化できるという利点がある。
では実際に自身がTwitter4SというScalaで書いたTwitterAPIのラッパーライブラリ内で使用しているトレイトを例に挙げてみよう。
trait StatusBase { /** * @param text * @return response json as Status */ def updateStatus(text: String): Status }
実際に処理がない宣言のみのメソッドを持っているがこれはエラーにならずトレイトの要素として認められる。
上記のメソッドには実装がないため、このトレイトをミックスインしたクラスでオーバーライドしなければならない。
それではクラスとほとんど変わらないのでは?と思うかも知れないが
クラスともまた違う点がある。それは次の点である。
最初の2つはともかくとして、ミックスインはモジュール化やトレイトを使う上で重要なので説明していく
ミックスイン
クラスにトレイトを実装する場合やトレイト自身に別のトレイトを継承(厳密にはこれもミックスインというが)することをミックスイン合成という。
どのように使うかというと次のように使う。
trait TraitA class Sample extends TraitA //これはミックスイン class SubSample extends Sample with TraitA // withキーワードでもミックスインとなる //traitにtraitをミックスインする場合 trait TraitB extends TraitA trait TraitC extends TraitA with TraitB
1つめでミックスインするならextends、ふたつ目以降はwithキーワードを使う。
ただしクラスを継承する場合は優先的にextendsを使わないとエラーとなる。
ここまで聞くと、インスタンス生成やコンストラクタを持てないクラスが多重継承できると思うが(クラスは多重継承できない)
実際に多重継承でなくても、実質的に多重継承の様な機能を持っていることになる。
おわりに
ここまでで、大規模なコードを書く上で重要なモジュール化の役割を果たすトレイトについて解説した
次は、ラムダや高階関数、カリー化についてまとめる。
全てのプログラマに捧げるScala入門 クラスとオブジェクト、ケースクラス
はじめに
前回の続きで、今回はクラスとオブジェクトについてまとめていく。
その前に、メソッドについて触れていなかったのでそこもまとめる。
メソッド
Scalaにおけるメソッドとは次の形をとる。
(private or protected [this or package_name]) def methodName(arg0: DataType, arg1: DataType, ... argN: DataType): ResultDataType = ???
これだけではわかりにくいのでざっくりと解説すると
アクセス修飾子やthis, package_name等は必要に応じて任意でつける。
defがメソッドを宣言するキーワードとなり、0個以上の引数を持つ。
最後に戻り値のデータ型、つまり結果型を記述し=の後に式を書いていく。
式ということなので{}式を用いて複数の式を入れることも可能。
privateやprotectedをつけない場合はpublicとみなされる点はJavaと異なるので注意が必要である。
[this]とすればそのクラス内だけから、[package_name]とすると同一パッケージ内のみからのアクセスが可能となる。
よく使うのは次のような形となる。
private [this] def getXXX(): Int = 0
クラス
Scalaのもつクラスはコンストラクタの記法を除いてほとんどJavaと同じである。
というのもコンストラクタを特に使用しない場合は次の様に記述するからです。
//これはScalaのクラス class ClassName{ }
これでコンストラクタを使う場合は次のようになる。
class ClassName(param1: DataType, ... ,paramN: DataType){
}
()で囲んだパラメータリストをクラス名の横に付与するとそれがコンストラクタになる。
例として、コンストラクタの値をフィールドに代入するコードを上げると次のようになる。
class SampleClass(num1: Int, num2: Int){ val number1: Int = num1 val number2: Int = num2 }
このようなクラスはJavaでインスタンスを生成するようにnewでインスタンスを生成することができる。
コンストラクタに対しての引数の渡し方も同様に次のようにできる。
val inst: SampleClass = new SampleClass(1, 2)
継承
ScalaのクラスはほとんどJavaと同じわけなので継承に関しても次のようになる。
class SubClass extends SuperClass{ }
見かけは同じなのだが少しだけ違いがある。
クラスにおいて継承した既存のメソッドをオーバーライドする場合、Javaでは@Overrideというアノテーションを付与したが
Scalaにおいてはoverrideキーワードになる。
なので、仮にtoStringメソッドをオーバーライドするときは次のようになる。
overrideキーワードをつけるのは継承したメソッドだけでなくオーバーライドする全ての場合に適用される。
override def toString(): String = ???
補助コンストラクタ
ここまでの内容を把握してみて、ひとつ疑問に思うことはないだろうか。
それはコンストラクタのオーバロードをどうするかという問題である。
それに関しては解決策があり、補助コンストラクタを用いるものである
これはJavaでいうコンストラクタのオーバロードと似ているが決定的にことなる部分がある。
まずは補助コンストラクタの使い方から見てみよう。
例はコップ本にある分数を扱うクラスを参考に一部改変し、簡略化したものを使う。
object Sample{ def main(args: Array[String]):Unit = { val sample: Rational = new Rational(1,2)// 1/2 val ratinal: Rational = new Rational(2) } } class Rational(val n:Int, val d:Int){ //コンストラクタから値を受け取り初期化 val numer: Int = n val denom: Int = d def this(n:Int) = { this(n,1) //ここに処理があってもいい } //toStringメソッドをオーバーライド override def toString(): String = numer + "/" + denom }
このコードにおいて、補助コンストラクタは次の部分である
def this(n:Int) = { this(n,1) //ここに処理があってもいい }
このようにdef thisというメソッドが補助コンストラクタとなる。
しかしこの補助コンストラクタというものを使うときには
thisを使い必ず先頭でそれより前に定義されたコンストラクタを呼び出す必要がある。
オブジェクト
Scalaにおいて全てはクラスであり、値はオブジェクトとして存在しメソッドは何らかのオブジェクトとなっている。
その状態を考えるとクラスに属するstaticなものを生成することができない。
しかし、その状態は不便なのでobjectキーワードがそれを解決する。
objectがあると何が嬉しいのかというと、objectで指定したシングルトンオブジェクトを生成することが出来る点である。
シングルトンオブジェクトであるが故に、そのオブジェクト固有のメソッドやフィールドを扱うことができる。
シングルトンオブジェクトを作ることができるなら、それによる恩恵を受けることができる。
そうなると、ユーティリティメソッドやオブジェクトのファクトリメソッドなども扱うことができる様になる。
それでは早速使ってみる。
といっても宣言から継承などほとんどクラスと変わらない上に、実はもう一度登場している。
それはHelloWorldを出力した時である。
object Sample{ def main(args: Array[String]): Unit = { println("hello,world") } }
この時にすでに使っている。
これによりmainメソッドがJavaでいうところのstaticメソッドになっている。
唯一といってもいい大きな違いはコンストラクタとしてパラメータを受け取れない点にある。
ケースクラス
最後に、クラスでもオブジェクトでもない特別なクラスを紹介する。
それはケースクラスというものでこの後に登場するパターンマッチという機能にも関連するものである。
ケースクラスとは次の様に宣言する。
case class User(name: String, age: Int)
これを見ると早いのだが、classの宣言の前にcaseキーワードが付いただけである。
一見なんら変化が無いように思えるがケースクラスには次のような利点がある
- コンストラクタ引数にvalが付いた状態で自動的にそのクラスのメンバとして宣言される
- パターンマッチに使える
- 便利なメソッドが自動生成される
今回はこれの特に自動生成されるメソッドについて、とくにapplyについて触れる。
applyメソッド
実際、自動生成されるメソッド群のなかでこれが一番使われていると思う。
applyメソッドはそのケースクラスのインスタンスを生成する。
ということはこれを使うことでnewをする必要がなくなる。
上記のUserクラスをそのまま例で使う。
caseクラスのインスタンス生成の仕方は以下の通りである
val user :User = new User("scala", 20) //これでも生成できる val user :User = User.apply("scala", 20) //こっちでもok
おわりに
ここまでで、一通りクラス、オブジェクト、ケースクラスについては解説した。
次はJavaでいうinterfaceに類似した機能をもつtrait(トレイト)について解説する。
全てのプログラマに捧げるScala入門 Scalaの基本編
Scalaでコードを書く
Javaでコードを書く時の最小単位は次の様なものを書くだろう。
public class Hello{ public static void main(String... args){ System.out.println("Hello,World!!"); } }
クラス名とファイル名が一致し、mainメソッドを持つお馴染みの形である。
これがScalaだとどうなるかと言えば、次のようになる。
object Hello{ def main(args: Array[String]): Unit = { println("Hello,World!!") } }
classではなくobjectというキーワードを使い(これは後述する)、
mainメソッドをdefで定義し標準出力はprintln()という様に短くなる。
そして文末のセミコロンもいらない。しかし任意につけることは出来る。
Unitなどは後の、データ型で詳しく説明する。
ScalaにはREPLがあるので、それを使うともっとコードは短くなる。
これがREPLを使わない時のScalaプログラムの基本形である。
変数
Javaで変数の宣言をするときは次のような形式を取る
int i = 0; String str = "Hello";
つまりは
(アクセス修飾子)[データ型] [変数名]; or (アクセス修飾子)[データ型] [変数名] = [初期値];
と言った形である。
これに対してScalaは次のような形式を取る。
val str: String = "Hello" var i: Int = 0 var number = 1L //val number:Long = 1L
つまりは
(アクセス修飾子) [val or var] [変数名] : (データ型) = [初期値]
となっている。
ここで重要なのがval,varとデータ型である。
変数宣言の先頭にある、valとvarで何が違うのかというと再代入が可能かどうかで
Javaでいうところのfinalが付与される動作に似ているのがvalで、非finalな変数がvarとなる。
実際に次のようにvalに値を再代入するとエラーが発生し、再代入が出来ないことを確認できる。
scala> val index: Int = 0 index: Int = 0 scala> index = 1 <console>:8: error: reassignment to val index = 1 ^
これに対してvarは再代入ができることを確認するのが次のREPLになる
scala> var index: Int = 0 index: Int = 0 scala> index = 1 index: Int = 1
var,valでどちらを使うのが良いかと思うかも知れないが、Scalaでは基本的にvalを使用することが多い。
というのも関数型的な側面を活かすには変数等はイミュータブルの方が望ましいからである。
ネットでは何がなんでもvalを使えという過激派な意見も見受けられるが、実際にScalaの標準ライブラリの実装では
ミュータブルな変数やコレクションなんかが多用されているので一概にどちらかだけを使えとは言えない。
データ型
ScalaにはJavaの様なプリミティブ型が存在しない。
ではなにが存在するかといえば、全て参照型でありつまりはクラスの値なのだ。
Scalaは拡張性が高くスケーラブルであるという最大の要因であると言える。
つまりはJavaからプリミティブが消えて、それに対応しているラッパークラスのみが残った状態と言える。
ここまでで、そのように説明されてもいまいちしっくりこないだろう。
そこで次の例を用いてInt型について考えてみる。ここでのIntは前述の通りクラスである。
まだその点において疑問を持つ人は次のリンクを参照してみるといい。
github.com
val a: Int = 1 val b: Int = 2 println(a + b) //out -> 3
気になるのが
a + b
という構文である。
Int型がクラスであるというなら、通常このように書くはずであると考えるし
そもそも+という名前のメソッドを書けるのかと疑問に思うだろう。
a.+(b)
Scalaをさわったことが無い人なら誰でもそのように感じるだろう。
その考えは全て正しいが、Scalaにおいては全てがクラスとそのメソッドなので+という演算子そのものが
言語レベルでサポートされているわけではなく、あくまでもそのクラスのひとつのメソッドにすぎない。
今までの言語と似た感じで使用できるのは、メソッドの呼び出しで.や()を省略できる場合があるからである。
ここまで話して、Scalaにおいてのデータ型とは参照型を指すということがわかるだろう。
また演算等についても自身で作成することができるのでデータ型を自作できるという面で非常に拡張性が高くなっている。
Scalaのデータ型とはそういうものなのだ。
制御構文
ここまでで変数とデータ型について触れたので、基本的なifやwhile、forといった制御構文についてここでは触れようと思う。
{}式
これについてはあまり触れられていないが、if文やwhile文ではなくif式やwhile式であるScalaにおいて複数の式を
記述する場合にこれは非常に重要な役割を持つ。{}式は次のようにいくつかの式を含む。
{ exp1; exp2; .... expn;}
これは順番に1~nの順で評価される。
if式
これはほとんどjavaのif文のように使えるが、それに馴染みすぎた人にはすごく奇妙な性質を持つ。
というのを、次のかなり有名な例を基に考えてみる。
Javaでは、年齢を数値として考えるとき次の様にすることが多い。
int age = 21; String res; if(age < 20){ res = "未成年"; }else{ res = "成人"; } System.out.println(res); //out -> "成人"
if~else文で評価式に応じて変数に値を代入しているがScalaでは次のように書くことが多い。
val res:String = if(age < 20) "未成年" else "成人" println(res) //out -> 成人
この処理を見てもらうとわかるのだがif式が式であるがゆえに値が返っている。
また{}式も併用も可能であり、else if,elseも使うことができる。
while式
while式もif式同様に値を返すが適切な値がない(これは書いてみるとわかると思う)ためUnit型の()が返る
基本的にはjavaのwhile文と同じなので省略。
for式
ここまでのif,whileは基本的にJavaの制御構文と同様に使えるがforはかなり奇妙な使い方をする。
一般的にJava等の言語でfor文と言えば次を想像するだろう。
for(int i = 0; i < 10; i++){ System.out.println(i); }
しかしこれをScalaで書くと次のようになる。
for(i <- 0 until 10) println(i)
どちらかというとPythonに近い感じで書ける。
untilでなくtoも存在するが、これに共通しているのはループ変数の増減ではなくコレクションを対象に行うことである。
もちろんListやMap等でも同じようにループを回せる
Scalaのfor文は次のようにも書ける。
これは多重ループのように機能する。
for( x <- 1 to 5; y <- 1 to 5; if x != y) println(s"x : y = ${x} : ${y}") x : y = 1 : 2 x : y = 1 : 3 x : y = 1 : 4 x : y = 1 : 5 x : y = 2 : 1 x : y = 2 : 3 x : y = 2 : 4 x : y = 2 : 5 x : y = 3 : 1 x : y = 3 : 2 x : y = 3 : 4 x : y = 3 : 5 x : y = 4 : 1 x : y = 4 : 2 x : y = 4 : 3 x : y = 4 : 5 x : y = 5 : 1 x : y = 5 : 2 x : y = 5 : 3 x : y = 5 : 4
この時点でも、かなり奇妙だがScalaのfor式はyieldというキーワードを使うことで
コレクションの要素を加工し新たなコレクションとして返すことができる。
for(i <- List(1, 2, 3, 4, 5)) yield { i + 1 } //return List(2, 3, 4, 5, 6)
最後に
ここまでがScalaの基本となるあれこれである。
次回はクラスとオブジェクトについてまとめようと思う
全てのプログラマに捧げるScala入門 ラムダと高階関数、カリー化
はじめに
Scalaをああだこうだ使っているけど、いまいちココらへんが理解できていないので
なんとなくまとめて整理してみる。
ラムダ式
上の参考サイトを元に値を引数で一つ受け取って、それに1加算して返す関数を考える。
通常の書き方で書くと次のようになる。
def inc(x: Int): Int = x + 1 inc(1) // return => 2
これをラムダで書き換えてみる。
すると次のようになる。
val inc: Int => Int = x => x + 1 inc(1) // return => 2
ただこちらは関数型でない言語を使ったことがない人には直感で理解がしにくいと思う。
上記のコードを次の用にするとわかりやすいかもしれない。
val inc: (Int => Int) = (x :Int) => x + 1
(Int => Int)が変数incの型であり、Int型の引数をとりInt型の戻り値を返すという意味になっている
(Int => Double)ならInt型の引数をとり、Double型の戻り値を返すという意味合いになる。
引数を2つとり、それらを加算して返す関数は次のように書ける。
val plus: ((Int,Int) => Int) = (x: Int, y: Int) => x + y
変数の型(今回ならInt => Int)を省略することができて、その場合は次のようになる。
val inc = (x: Int) => x + 1
この場合だと引数を二個以上もつ関数もラムダで書きやすい。
上記と同様に引数を2つ受け取り、それを加算する関数を書くと次のようになる。
val plus = (x: Int, y: Int) => x + y plus(5,6) // return => 11
ここまで書いた右辺のことをラムダ式という。
無名関数
ラムダ式は名前のない関数、つまり無名関数として定義でき
無名関数は変数に束縛されていると言える。
高階関数
一言で言うなら、関数を引数にとる関数のことで次のように書ける。
ラムダ式のことがわかっていればさほど難しい話ではない。
def calc(plus: (Int,Int) => Int, minus: Int) = plus(2,3) - minus val plus_func: (Int, Int) => Int = (x, y) => x + y calc(plus_func, 5) //return => 0
引数だけでなく、戻り値として関数を返すこともできるのが高階関数である。
def add(x: Int): (Int => Int) = (y : Int) => x + y add(2)(3) // return => 5
この様な高階関数はあまり意識しなくても使っていることが多く
よく使う場面ではScalaのコレクションにおいて、mapやforeach,filterなどのメソッドを使うときである
(1 to 100).filter(x => x % 2 == 0)
カリー化
カリー化とは(Int, Int) => Intのように複数の引数をとるラムダにおいて Int => Int => Intのように引数を分割し、
ひとつ引数を取ったあとに、その引数をメソッドチェインのようにチェインさせて書く方法のことを指す。
関数型言語ではよく使われており、Haskellでは複数の引数をとると自動でカリー化されるらしい。
さっきのplus関数で例を挙げてみる。
先に書いたカリー化していない場合は
val plus: (Int, Int) => Int = (x: Int, y: Int) => x + y
となるが
これをカリー化すると次のようになる
val plus: Int => Int => Int = (x: Int) => (y: Int) => x + y plus(2)(3) // return => 5
これが使えるとなにが嬉しいかというと、filterなどの高階関数を扱うあれこれにおいて
片方の値を定数にして、もう片方を動的に処理した場合などに非常に便利になる。
val myFilter: Int => Int => Boolean = (x: Int) => (y: Int) => x < y (1 to 100).filter(myFilter(50)) res10: scala.collection.immutable.IndexedSeq[Int] = Vector(51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)
最速のEchoサーバーを目指して、LinuxKernelモジュールを作っていく part2
はじめに
前回はカーネルモジュールで出力をやるところまでやった。
rabbitfoot141.hatenablog.com
今回は、気合でカーネルスレッドを扱えるようにする。
実装
EchoサーバではTCPを使用する、それをスレッドでああだこうだして複数接続を可能にしたい。
その目的のために今回は頑張ってカーネルスレッドを実装する。
kernel moduleでカーネルスレッド
参考にしたのはまつもとりーさんの次の記事。
hb.matsumoto-r.jp
linux/kthread.hとlinux/sched.hいうヘッダーファイルにある関数を使う。
kthread.hがカーネルスレッドに関するあれこれで、sched.hがカーネルで扱うプロセスやそれを扱うtask_structという構造体を使うためにある。
それでは早速コードを晒す。
#include <linux/kthread.h> #include <linux/sched.h> struct task_struct *task; static int kthread_cb(void *arg){ printk("[%s] running as kthread\n", task->comm); while(!kthread_should_stop()){ schedule(); } return 0; }
グローバル変数としてtask_structを宣言している。
これにプロセスとして起動したいあれこれを代入して使う。
kthread_cb関数ではカーネルスレッドとしての動かしたい処理を書いている。
それを次のモジュール本体のコードで次のように使う。
static int fastecho_init_module(void){ printk("Fastest Echo Server Start!!"); //make kernel thread task = kthread_create(kthread_cb, NULL, "lrf141:fastecho"); printk("[%s] wake up as kthread\n", task->comm); //launch task wake_up_process(task); return 0; } static void fastecho_cleanup_module(void){ printk("Fastest Echo Server is unloaded!"); printk("[%s] stop kthread\n", task->comm); kthread_stop(task); }
まず前のコードで宣言したtask_structにkthread_create関数の戻り値を代入する
これでスレッドが使える様になるので、printkの次でwake_up_process関数を呼び出し、プロセスとして起動する
rmmod時にはそれを止める処理を書いている。
これを前回のモジュール本体に組み込んで次のようにする。
$ make $ sudo insmod fastecho.ko $ ps auwx | grep "\[lrf141:faste cho\]" root 30557 98.3 0.0 0 0 ? R 16:49 0:05 [lrf141:fast echo] $ dmesg [176532.823327] Fastest Echo Server Start!! [176532.826178] [lrf141:fastecho] wake up as kthread [176532.827453] [lrf141:fastecho] running as kthread [176586.304842] Fastest Echo Server is unloaded![lrf141:fastecho] stop kthrea d $ sudo rmmod fastecho.ko
おわりに
まつもとりーさんの記事のおかげでなんとかカーネルスレッドを動かすことはできたので
次はネットワークの部分を実装していきたい。
最速のEchoサーバーを目指して、LinuxKernelモジュールを作っていく part1
はじめに
これから諸事情でLinux Kernel moduleとして動作する最速(?)のEchoサーバを作ることになったので
Echoサーバを作る過程をまとめていく。
今回はとりあえず、環境構築からKernel moduleで「Hello,World」的なことをするまでをやる
開発環境構築
参考にしたのは次の2つ
前者では主に、開発環境の面を、後者でコード周りを参考にした。
カーネルモジュール作成によるlinuxカーネル開発入門 - 第一回 hello world - Qiita
Linux kernel module の作成 - Qiita
VM的なのを使いたいなら特に前者を、そうでなくてよくてバグを踏んだ瞬間OSが死んでいいなら後者で十分だと思う。
これがメインではないので関連記事を参照して欲しい。
カーネルモジュールの作成
とりあえずCで開発するのでMakefileを作る
今までこういったものまともに書いたことないので今のところはふたつ目の参考記事のものをパクる。
KERNEL_DIR = /lib/modules/$(shell uname -r)/build BUILD_DIR := $(shell pwd) VERBOSE := 0 obj-m := fastecho.o fastecho-objs := fastecho_module.o all: make -C $(KERNEL_DIR) SUBDIRS=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules clean: rm -f *.o *.ko *.mod.c *.symvers *.order .fastecho* rm -fr .tmp_versions
次に、カーネルモジュール本体のコードを書く
#include<linux/module.h> #include<linux/kernel.h> MODULE_DESCRIPTION("The fastest echo server in kernel module"); MODULE_AUTHOR("lrf141"); MODULE_LICENSE("MIT"); static int fastecho_init_module(void){ printk("Fastest Echo Server!!"); return 0; } static void fastecho_cleanup_module(void){ printk("Fastest Echo Server is unloaded!"); } module_init(fastecho_init_module); module_exit(fastecho_cleanup_module);
カーネルモジュールを書く上で、一行目のlinux/module.hは必ず必要となる。
include文の次に来る、大文字のあれこれはそれぞれモジュールの情報で
上から、そのモジュールの説明文、作者、ライセンスとなっている。
そのあとにはモジュールとして呼び出す関数が2つあるが、その両方で使用しているprintkはstdio.hを使った時のprintfのような機能がある。
末尾二行にあるmodule_init()関数ではカーネルモジュールがロードされた際に引数で渡した関数を呼び出す。
引数として渡す関数はintを返り値としている。
module_exit()関数では、カーネルモジュールがアンロードされた時に引数の関数を実行する。
ソースコードがかけたらとりあえずmakeする。
そのあとこれらモジュールの情報は次のようにすると見れる。
$ modinfo fastecho.ko filename: /home/lrf141/cProject/fastest_echo/fastecho.ko license: MIT author: lrf141 description: The fastest echo server in kernel module srcversion: B5F9EFB5EC209D9B991796D depends: vermagic: 4.4.0-98-generic SMP mod_unload modversions
モジュールをロードするときは次のようにする
$ sudo insmod fastecho.ko
モジュールをアンロードするときはこれ。
$ sudo rmmod fastecho.ko
ただこれではprintkで出力された文字列が見れない。
ということでここまで終わった段階でdmesgコマンドでカーネルのバッファ出力内容をみる
ただ見るだけでは見づらいのでgrepも組み合わせる。
$ dmesg | grep "Fast" [46074.578777] Fastest Echo Server!! [46079.485027] Fastest Echo Server is unloaded!
するとprintkした内容が見えている。
最後に
今回は簡単なモジュールの制作をやったので次回はこれにネットワークの機能を突っ込みたい