軽い気持ちでScalazを使う
Aizu Advent Calendar 4日目です!
前はhimaaaattiさんのGoogle Summer of Code 応募について
はじめに
こわいひとはみないで
Scalaz
ScalazはScalaで関数型プログラミングをするときの補助となるようなライブラリです。
Scalaz is a Scala library for functional programming. It provides purely functional data structures to complement those from the Scala standard library. It defines a set of foundational type classes (e.g. Functor, Monad) and corresponding instances for a large number of data structures.
FunctorとかMonadとか出てきて若干怖めですが、ガリガリに使わなければ難しい概念を覚えなくても便利に使うことができます。
型合わない問題
Scalaのfor式は特定の条件を満たす型に対して使える便利構文です。
// Future[A]は成功したらAを保持するような非同期処理を扱う型 def findTodos(): Future[Seq[Todo]] = ??? // 終了したTodoを取得する val finishedTodosF: Future[Seq[Todo]] = for ( todos <- findTodos(); finishedTodos <- todos.filter(_.isFinished) ) yield finishedTodos // これと同じ findTodos().map(_.filter(_.isFinished))
単純な処理だと問題ありませんが、ちょっと複雑になると困ることがあります。
case class Todo(id: TodoId, body: TodoBody, assigneeId: MemberId) case class Member(id: MemberId, name: MemberName) def findTodoById(id: TodoId): Future[Option[Todo]] = ??? def findMemberById(id: MemberId): Future[Option[Member]] = ??? // TodoIdから、そのTodoにアサインされたMemberを取得する。 def findAssigneeMember(id: TodoId) = for ( todo <- findTodoById(id); assigneeMember <- todo.map(t => findMemberById(t.assigneeId)) ) yield assigneeMember
このfor式はコンパイルが通りません。この場合、for式内の右辺値の型はFuture[A]
でないといけませんが、assigneeMember <- todo.map(t => findMemberById(t.assigneeId))
の型はOption[Future[Option[Member]]]
になっています。
todoがNoneだったらFuture.failureを返すようにすると型は合うようになります。
def findAssigneeMember_(todoId: TodoId): Future[Option[Member]] = for ( todo <- findTodoById(todoId); assignedMember <- todo.map(t => findMemberById(t.assigneeId)).getOrElse(Future.failed(new RuntimeException(s"Todo ${todoId.value} not found!"))) ) yield assignedMember
しかしよく見ると、本来findTodoById()
がNone
を返した時はFuture
は成功してその上でNone
を返して欲しいのに、これだとFuture
自体が失敗してしまうので意味が変わってしまいます。
意味は変えずに型を合わせたい...
Option[Future[Option[Member]]]
をFuture[Option[Member]]
にしたい...
これを解決するsequence
という関数がScalazには定義されています。
sequenceってなに
sequence
は特定の条件を満たすA[_]
とB[_]
について、A[B[C]]
をB[A[C]]
に変換します。
どんな仕組みやねん。
このsequence
はtraverse
という関数の用途を限定したバージョンなので、traverse
の定義を先に見てみます。
trait Traverse[F[_]] extends Functor[F] with Foldable[F] { self => // ... def traverse[G[_]:Applicative,A,B](fa: F[A])(f: A => G[B]): G[F[B]] = ... // ... }
このFunctor[F]
、Foldable[F]
、G[_]: Applicative
が特定の条件です。雑に説明すると、
Functor[F]
: Fはmapができる。Foldable[F]
: Fはfold(reduce)ができる。G[_]: Applicative
: GはGに包まれた関数にGに包まれた値を適用できる。
Applicative
だけ補足します。さっきの例でも使ったOption
は実はApplicative
な型でもあります。なぜかと言うと、
// (1: Int).someはSome(1): Option[Int]と同じ 5.some <*> (4.some <*> {(a: Int, b: Int) => a + b}.curried.some) // => Some(9)
こんな感じで、Option
な値をOption
な関数に適用できるからです。こういう性質の実装を持つ(持てる)型をApplicative
1型と言います。
なのでtraverse
は、A
がFunctor
かつFoldable
でB
がApplicative
の時、A[X]
とX => B[Y]
な関数を受け取って、B[A[X]]
を返す関数でした。
例としてList
のtreverse
の実装を見てみます。
def traverseImpl[F[_], A, B](l: List[A])(f: A => F[B])(implicit F: Applicative[F]) = {
l.reverse.foldLeft(F.point(Nil: List[B])) { (flb: F[List[B]], a: A) =>
F.apply2(f(a), flb)(_ :: _)
}
}
また雑に説明すると、
- リスト
l
と、リストの要素をApplicative
な値に変換する関数f
を引数にとる l
を反転する。l
の要素を先頭から(本来の末尾から)f
でApplicative
な値に変換し、Applicative
な関数を使ってApplicative
なリストの先頭に繋げる。
...シミュレーションしてみます。
// Some(List(6, 7, 8))を期待 List(1, 2, 3).traverse(i => Some(i + 5): Option[Int]) // 1. リストを反転 List(3, 2, 1) // 2. foldの初期値はSome(List()) Some(List()) // 3. 先頭の要素 3 にfを適用してApplicativeな値に変換 f(3) == Some(8) // 4. Applicativeな関数を使ってApplicativeなリストの先頭に繋げる {_ :: _} ---Applicativeに---> Some({_ :: _}) Some({_ :: _}) ---適用---> (Some(8), Some(List())) => Some(List(8)) // 5. 3と4を全ての要素分回す Some(List(6, 7, 8))
こうして見ると、traverse
はmap
に似てる気もします。
さて、traverse
がなんとなく分かったところでsequence
の定義を見てみます。
/** Traverse with the identity function. */ def sequence[G[_]:Applicative,A](fga: F[G[A]]): G[F[A]] = traverse(fga)(ga => ga)
コメントにあるように、sequence
はtraverse
の引数f
をidentity function、つまり何もせずにそのまま返す関数にしたものです。
sequence
の呼び出しもシミュレーションしてみます。
// Some(List(1, 2, 3))を期待 List(Some(1), Some(2), Some(3)).sequence // 1. リストを反転 List(Some(3), Some(2), Some(1)) // 2. foldの初期値はSome(List()) Some(List()) // 3. 先頭の要素 Some(3) にfを適用してApplicativeな値に変換 f(Some(3)) == Some(3) // 4. Applicativeな関数を使ってApplicativeなリストの先頭に繋げる {_ :: _} ---Applicativeに---> Some({_ :: _}) Some({_ :: _}) ---適用---> (Some(3), Some(List())) => Some(List(3)) // 5. 3と4を全ての要素分回す Some(List(1, 2, 3))
List[Option[Int]]
をOption[List[Int]]
に変換できました!
簡単か?
冒頭で、「ガリガリに使わなければ難しい概念を覚えなくても便利に使うことができます。」と言いました。
正直、sequence
の実装を理解した上で使うのは簡単ではないと思います。
そこでsequence
の振る舞いだけ見てみましょう。sequence
はA[B[C]]
をB[A[C]]
に変換できる不思議な関数です。
これを使ってさっきの問題を解決してみます。
def findAssigneeMember(id: TodoId) = for ( todo <- findTodoById(id); assigneeMember <- todo.map(t => findMemberById(t.assigneeId)).sequence.map(_.flatten) ) yield assigneeMember
ただ使うだけならFunctor
とかApplicative
とかどうでもいいですね!
しかし、注意しないといけないのはA
はFunctor
かつFoldable
、B
はApplicative
でないといけないということです。適当に作った型ではsequence
は使えません。
なので、使うだけなら難しい概念は考えなくても良いものの、何に使えて何に使えないかを知るには頑張りが必要です。
おわりに
Scalazには関数型プログラミングの特性を使った便利な関数がたくさん入っています。ゴリゴリに使いこなすまでいかなくても今回のsequence
のように問題の解決策として使うと幸せになれるのではないでしょうか。
-
本当はこれはApplyの説明です。Applicativeの場合はもう一つ性質が必要です。↩
囲碁入門の敷居を下げる7つのこと
Aizu Advent Calendar(ADVENTER版)19日目です。
前は@kyuriageさん、次は@taroooyanさんです。
タイトルは適当につけたので特に意味はありません。
はじめに
僕は今年(2016年)6月くらいから囲碁にハマり始めたのですが、誰かに「囲碁にハマってます」と言うとだいたい
- 難しそう
- いつ終わるのかわからない
- ヒカルの碁面白かった
という返答を頂きます。
そうじゃないんだよ!!!!囲碁は楽しいの!!!!!やってみてよ!!!!!
ということで周りに囲碁スキーを増やして対局したい、という願望によってこの記事は生成されています。
どういうの
囲碁は一言でいうと陣取りゲームです。
イメージするなら、無人島で交互に柵を置いていって、より広い範囲を囲んだ方が勝ちという感じです。
なので、当然効率の良い陣取りが求められます。初めから大きい陣地を狙うのも良いですが、大きく陣地を取るにはたくさんの柵が必要になるので、「柵を置いてる間に相手が割り込んできて潰れる」ということがあります。なので、それなりに広くて相手に割り込ませにくい位置に柵を置くことが重要になります。
では、まっさらな無人島に柵を置く時、あなたならまず初めにどこに置きますか?(線の交差した部分に置くことができます)
欲張りな人ならこんな感じで置きたくなると思います。
右上を大きく取ろうという作戦ですね。ただ、相手に入られたらどうなるでしょうか?
初めに置いた柵は島の真ん中辺りで浮いてしまいました。
実は、この相手が置いた場所が安定して陣地を確保できる位置です。この端から縦横4つ目の位置を星といって、最序盤に打たれやすい位置の一つです。
囲碁はこのように「自分の陣地に相手を割り込ませないように固めつつ陣地を広げていく、その効率を競う」ゲームです。
だいじなきまり
囲碁では相手の柵を自分の柵で囲うと、囲った柵を取り払うことができます。
(ここからは無人島のことを碁盤、柵のことを石と呼んでいきます)
次の盤面を見てください。
白がなんだかいい感じに陣地が作れてそうですが、このルールがあるので、黒が中心に入ると大変です。
一気に黒の陣地に変わってしまいました。
このように、陣地を作ったと思っても取られてしまう場合があります。では次の例はどうでしょうか?
さっきの例を見てからだと白が追い込まれているようにも見えます。ですが、黒は絶対にこの白を取ることができません!
この白を取るためには、黒は開いた二つの穴に同時に石を置くしかありませんが、囲碁では自ら相手に囲まれに行く手は禁止されているので*1、白が自ら穴を埋めない限り黒は白を取ることができないのです。
このように、石で囲った陣地が二つ繋がることで相手に取られなくなる状態の事を二眼といいます。自分の陣地を守ったり相手の陣地に攻めたりするときに、この二眼の考えはとっても大切です。
いろんなかたち
囲碁の攻防を少し覗いたところで、その攻防に関係した形を見ていきたいと思います。
囲碁はとても自由度の高いゲームですが、「こうなったら取られる」「こうなったら取られない」という形がいくつも存在します。
例えばこの時、次が黒番なら白を確実に取ることができます。
このように追いかけていくと最終的に碁盤の端に行き着き、黒は白を囲むことができます。
このような形をシチョウといいます。
では次はどうでしょうか?
似たような感じですが、白石が一つ増えました。同じようにシチョウにしてみます。
増えた白石に当たって白を取ることができなくなってしまいました。シチョウで囲もうとした石が別の石に当たって逃げられることをシチョウアタリといいます。シチョウでは取れないみたいです。
でも黒は白を取る方法があります!
直接白に付けずにこのように置きます。すると、白は右から逃げようとしても、上から逃げようとしても最後には囲まれてしまいます。
このように、ちょっと離して取る形をゲタといいます。
他にも、ウッテガエシ、オイオトシなど色々な形があります。二眼と同様にとても大切なテクニックです。
いつおわるの
囲碁の終局はお互いに、もういいかなと思ったタイミングです。
いつだよ!!!!!
例えばこんな時です。
まだまだお互い攻められるようにも見えますが、実はもう得するような打ち方はどちらにもありません。
石が碁盤の上に生き残るためには二眼が必要です。その考え方からいうと、白黒共に大きく繋がってる部分は二眼を作るのは簡単そうです。ただ、ここから相手の陣地に打ち込んだ場合にそこから二眼を作ることはできるでしょうか?ちょっと狭すぎる気がしますね。
このような感じで、お互いどこに打っても得しない時、囲碁は終局します。
ただ、プロの対局だと自分の陣地と相手の陣地の広さを目算して、逆転できないと思ったらその時点で投了しちゃうことが多いです。
おわりに
囲碁がどういうゲームか何となくお分かり頂けたでしょうか?色々説明してきましたが、実際の囲碁の楽しさは自分でやってみないと分かりません。スマホアプリでも囲碁ができたりするのでやってみてはいかがでしょうか?個人的には囲碁クエストというアプリがオススメです。
囲碁に興味が出た方は僕と対局してください!!!
*1:置いた時に相手の石を取ることで、結果的に囲まれない場合には置くことができます。
オレオレ字句解析器つくりました
Aizu Advent Calendar 2016(ADVENTAR版) 7日目です。
6日目がid:unamune, 8日目もid:upamune(!!!)です。
おつかれさまです。
はじめに
強い人は見ないで
もの
リポジトリはこちらです↓
はじめはScalaのDSLの記事を書こうと思ってDSLで書ける簡易的なパーサを書こうと思ったのですが、DSLの部分よりそれ以外のところが重くなったのでもうDSLの記事じゃなくていいか、と色々考えて字句解析器になりました。
Rustじゃなくてすみません。
なかみ
本体はここです↓
https://github.com/misoton665/simp/blob/master/src/main/scala/org/misoton/Lexer.scala
トークンと解析後のトークンツリーをつくって、LexerをInput => Outputな関数として関数合成で作れるようにしました。 パーサコンビネータはあるけどレクサーコンビネータと言えるのか...?
文字列と正規表現を暗黙的型変換でLexerに変換できます。
"hogehogepun" ← "hogehogepun"をトークンとして解析するLexer "([a-z]+)".r ← aからzで構成される1文字以上の文字列をトークンとして解析するLexer
演算子はこんな感じです。
"a" ~ "b" ← "ab"をそれぞれ別のトークンとして解析するLexer "a" / "b" ← "a"または"b"をトークンとして解析するLexer "a" ~ "b" <| ← "a"と"b"を持つトークンを子ノードとする親ノードとして解析するLexer
実はn文字先読みできます。
lookAhead(hoge) ← hogeで解析したものはなかったことに(先読み) hidden(hoge) ← hogeで解析した結果を消す(読み進めるだけ) $ ← 入力文字列の終端のみで成功
Main.scalaで、元の定義を遵守したPPAPと広義のPPAPの解析を行ってるので、見てみてください。
DSL
このDSLの仕組みは簡単です。Scalaは略記が色んなところでできるのでそれを利用しています。
// これは↓ a.hoge(b) // こう書ける↓ a hoge b
上で書きましたが、暗黙的型変換で文字列や正規表現をLexerに変換しているので、いちいち変換のための関数を呼ばなくてよくなります。
// 文字列からLexerの生成 implicit def string2lexer(str: String): Lexer = { lexerGen((input) => { val (inputStr, nodes) = input if (inputStr startsWith str) Right((inputStr substring str.length, nodes :+ TokenTreeNode[ConstToken](ConstToken(str), Nil))) else Left("\"" + inputStr + "\" cannot match with \"" + str + "\"") }) } // 正規表現からLexerの生成 implicit def regex2lexer(regex: Regex): Lexer = lexerGen{ (input) => val splitRegex: Regex = (regex.toString + """(.*)""").r val (inputStr, nodes) = input inputStr match { case splitRegex(str, left) => Right((left, nodes :+ TokenTreeNode[IDToken](IDToken(str), Nil))) case _ => Left("\"" + inputStr + "\" cannot match with \"" + regex.regex + "\"") } }
細々したものはここでやってるので、演算子とかは簡潔に書けます。
// Lexerの連結 def ~(that: Lexer): Lexer = lexerGen { (input) => this (input) match { case Right(output) => that(output) case err@Left(_) => err } }
ここで使ってるlexerGenもScalaの略記(というか糖衣構文?)で使ってます。元の定義は普通の関数です。
// 関数からLexerの生成 def lexerGen(f: Input => Output): Lexer = new Lexer { override def apply(input: Input): Output = f(input) }
// これは↓ a.hoge( b ) // こう書ける↓ a.hoge{ b }
おわりに
- 実用性があまりなくてつらい。字句解析と構文解析を一気にやる方法があるので、そうすればマシになりそう。
- 再帰的な定義ができなくてつらい。つらい。
- これを使いましょう: github.com
- たのしかったです(こなみ)
ラムダ計算した
Aizu Advent Calendar 2016(Qiita) 5日目です。
4日目はstringampさんのRubyを使ったWAVファイルのBPM解析 - Qiita
はじめに
ラムダ計算を調べた発端はこのブログ(5年前...)です。
記事中のfib関数はopen recursionと言って開いた再帰関数らしく、OOPの支援によって元の定義を変えずに簡単に拡張することができます。
ただこれをOOPの支援なしで元の定義を変えずに拡張するときはどうするのか、そのときに登場するのがラムダ計算のYコンビネータという不動点演算子と呼ばれるものです。
Yコンビネータは関数の再帰呼び出しを抽象化したもので、これで全ての再帰関数を表現できます。
ただ、この投稿ではラムダ式の基本とYコンビネータの紹介をするだけです。具体的な解決については上の記事内を参照してください。
ラムダ計算ってなに
こういうやつです↓
λx. x
これは「x を受け取って x を返す関数」です。このλx. x
のことをラムダ式(ラムダ項)といいます。x
という変数もラムダ式です。
次にこれ↓
λx. λf. f x
これは「x を受け取って、f を受け取って f に x を適用したものを返す関数、を返す関数」です。 ちなみにこれと同値です。
λx. λf. f x = λxf. f x
つまり、二つの引数を受け取る関数をカリー化したものが上のラムダ式です。 また、右辺は左辺のラムダ式を略記した形です。
ここで重要なのが、
- 関数には名前が付いていない。
- x や f はただの変数で、その名前は重要じゃない。
ということです。 なんのこっちゃわかりませんが、要するにメッチャ抽象的な関数をラムダ計算では扱います。
このラムダ計算を使うと、関数だけではなくプログラム上の論理式やif式、数値を表現することもできます。
ラムダ計算があって何が嬉しいかというと、
1936年にチャーチはラムダ計算を用いて一階述語論理の決定可能性問題を(否定的に)解いた。ラムダ計算は「計算可能な関数」とはなにかを定義するために用いられることもある。計算の意味論や型理論など、計算機科学のいろいろなところで使われており、特にLISP、ML、Haskellといった関数型プログラミング言語の理論的基盤として、その誕生に大きな役割を果たした。(Wikipedia)
らしいです。
論理値
ラムダ式でtrue, false, if式はこのように定義できます。
true λtf. t false λtf. f if p then t else f (p には true か false が入る) λptf. p t f
式を見るだけどつまらないので、Haskellのコードで書いてみます。
true = \t f -> t false = \t f -> f if' p t f = p t f if' true 1 0 -- 1 if' false 1 0 -- 0
この計算の導出は、α変換、β簡約、η変換という三つのルールに従って行われています。
まず、if' true 1 0
をラムダ式に直してみます。
(λptf. p t f) (λtf. t) O Z
便宜上、1をO、0をZとしています。
まず、引数をラムダ式内の変数に当てはめます。これがβ簡約です。
(λtf. (λtf. t) t f) O Z
すると、名前が同じで実体の違う変数 t と f が出てきました。ラムダ計算では上記の通り変数の名前は重要ではないのでそれぞれ名前を変えることができます。これがα変換です。
(λtf. (λxy. x) t f) O Z
次々にβ簡約をしていきます。
(λf. (λxy. x) O f) Z (λxy. x) O Z (λy. O) Z O
これで手動でラムダ計算ができました!
使わなかったη変換はこのような時に使います。
λx. f x
あるラムダ式fにxを適用するラムダ式ですが、よく考えるとこのラムダ式は f そのものと同値であることがわかります。
Z は任意のラムダ式 (λx. f x) Z f Z
この時λx. f x
をf
に変換することをη変換といいます。
数値
if式の例ではZ, Oと表記されていた数値ですが、これもラムダ式で表現することができます。 と言っても数字を直接扱うのではなく、関数を何回適用するかで表現します。
zero(Z) λsz. z one(O) λsz. s z two λsz. s (s z)
three以降は書くのが面倒ですね。数値n+1はsをnに一回適用することで得られることから、関数を一回適用するラムダ式を用意すればインクリメントの表現ができそうです。
succ λnsz. s (n s z) three λsz. succ two s z
数値は二つの引数を持つラムダ式なので、その分を埋める必要があります。
インクリメントもあればデクリメントもある。ということで、デクリメントはこちらです。
pred λnsz. n (λfg. g (f s)) (λx. z) (λx. x)
大変ですね。一つひとつ見てみましょう。 まずpredを三つの部分に分けて見てみます。
pred λnsz. n <flip> <const> <id> flip λfg. g (f s) const λx. z id λx. x
constは「何が適用されようがzを返す関数」、idは「何が適用されようがそのまま返す関数」と読めます。
キモはflipです。
flipは一体何をしているのでしょうか?
ラムダ式では数値は関数が何回適用されたかで表現するのでした。
nとn+1 s( s( s( s( s( ... s( z ) ... ) ) ) ) ) s( s( s( s( s( s( ... s( z ) ... ) ) ) ) ) )
ここで、twoにflipを渡してみます。flip中のsを一旦Sとおきます。
two λsz. s( s( z ) ) ) two flip ( λsz. s( s( z ) ) ) ( λfg. g ( f S ) ) ( λz. ( λfg. g ( f S ) ) ( ( λfg. g ( f S ) ) z ) ) --> (λz. <flip> (<flip> z)) λz. ( λfg. g ( f S ) ) ( λg. g ( z S ) ) λz. λg. g ( ( λg. g ( z S ) ) S ) λz. ( λh. h ( ( λg. g ( z S ) ) S ) ) λz. ( λh. h ( S ( z S ) ) ) λzh. h ( S ( z S ) )
これを見ると、s( s( z ) )
だったものがs ( z ( s ) )
になっていることがわかります。また、ここでzにあたるのはconstなのでさらにconstを適用してみましょう。const中のzを一旦Zとしておきます。
( λzh. h ( S ( z S ) ) ) ( λx. Z ) λh. h ( S ( λx. Z ) S ) λh. h ( S Z )
s ( s ( z ) )
がs ( z )
になりました!!
このままidも適用してみましょう。
( λh. h ( S Z ) ) (λx. x) ( λx. x ) ( S Z )
変換できるのはここまでですが、結果はこの時点でoneになることがわかります。
つまりflipはzとsを逆転させることでconstを適用した時にs ( z )
をz
に評価するようにしていた、ということになります。
再帰
ようやくYコンビネータです。
Y λf. f (λx. f ( x x ) ) (λx. f ( x x ) )
もう何を言ってるのかよくわかりません。とりあえず、Yコンビネータはこのような性質を持っています。
Y ( f ) = f ( Y ( f ) )
Yコンビネータを使って階乗を表してみると、このようになります。
<isZero> ... 引数がzeroならtrue,そうでなければfalseを返すラムダ式 <mul> ... λxy. x * yのような計算をするラムダ式 λn. <Y> (λfx. <if> (<isZero> x) <one> (<mul> ( f ( <pred> x ) ) x ) ) n
こうなると変換は大変なのでやりませんが、このように全て関数を使って再帰関数を表現することができました。
ちなみにHaskellで書いてみるとこのようになります。
-- そのまま書ける y f = f (y f) fac x = y (\f x -> if x == 0 then 1 else f (x - 1) * x) x
しかし、Yコンビネータを正格評価のプログラミング言語で実装すると無限再帰になってしまうので、それを解消するためのZコンビネータというものもあります。Zコンビネータは完全にYコンビネータの正格評価版です。
Z λf. ( λx. f ( λy. x x y ) ) ( λx. f ( λy. x x y ) )
// Scala def Z[A, B] = (f: (A => B) => A => B) => (x: A) => f(Z(f))(x)
おわりに
今回はラムダ計算そのものについて考えましたが、記事の初めに書いたように応用できると強いと思います。あと何より面白いので、本など漁ってみてはいかがでしょうか。僕はこの記事を投稿した後、漁ります。
6日目はmizuki_sonokoさんです!
Kotlin(かわいい)
この記事はAizu Advent Calendar 2015の5日目(@misoton665)のものです。www.adventar.org
前の方 @plasma_effectorさん: constexpr多倍長整数 · GitHub
次の方 @himaaaattiさん: H8マイコンのエミュレータつくったはなし - ひまわり
Kotlinとは
KotlinはIntelliJ IDEAで有名なJetBrains社が開発しているプログラミング言語です。JetBrainsはこの言語を開発する目的の一つとして、Androidで利用されることを挙げていて、さらにAndroidStudioでプラグインをインストールすればすぐにKotlinを試すことができます。詳細はこちら-> Getting started with Android and Kotlin
Kotlinのチャーミングポイント
Kotlinの良いところをAndroidアプリ開発を例に見ていきます。
拡張メソッドで気がきくKotlin
Kotlinには、既存のクラスに後からメソッドを付け加える事ができる仕組みがあります。これによって、何回も書く冗長なコードを省略して短くするようなメソッドを簡単に定義できます。
Activityを起動する処理を例にして見てみましょう。これはJavaのコードです。
Intent intent = new Intent(this, NextActivity.class).setAction(ACTION_VIEW); startActivity(intent);
このコードで重要な部分は一行目の"NextActivity.class"の部分だけで、他の部分はどのActivityからどのActivityを起動するにしてもほぼ同じコードになります。
Javaでこの記述を省略して書こうと思うと、Activityクラスのサブクラスとして自分で新しくクラスを定義して、そこに省略するためのメソッドを定義して、さらに全てのActivityのスーパークラスを自分で新しく定義したクラスに書き換えなければなりません。面倒臭い。
Kotlinでは以下のように拡張メソッドを書けば終わりです。
fun <T: Activity> Activity.startActivity(classRef: KClass<T>, bundle: Bundle? = null, finishFl: Boolean = false) { val intent = Intent(this, classRef.java).setAction(Intent.ACTION_VIEW) bundle?.let { intent.putExtra("args", bundle) } startActivity(intent) if (finishFl) { finish() } }
この拡張で、先ほどの処理はこのように書く事ができます。
startActivity(NextActivity::class)
これで無駄な記述はなくなりましたね!!かわいい!!!
さて、ここでもう一つ重要なのは、拡張メソッドを利用するのに必要なのは拡張メソッド自身の定義のみ、という点です。
Javaでは拡張済みのクラスを継承するように明記しなければなりませんでした。Kotlinは継承元のスーパークラスのメソッドを拡張するので、継承するクラスを変える必要はありません。
また、拡張メソッドはそのスコープを限定することができるので、Activityの起動のようにどこでも使うものはスコープを広く、限定的な処理を拡張したい場合にはスコープを狭くすることで"後付け神クラス"が作られるのを避けることができます。
健康的なKotlin
KotlinにはOptional型という、nullをできるだけ使わない、使うとしても安全に使おう、という仕組みがあります。
実は先ほどのコードで、もうそのOptional型が使われています。
bundle: Bundle? = null
この"?"が付いているものがOptional型で、KotlinではOptional型で定義した変数のみ、nullを代入するのが許されています。つまり、Optional型でない通常の変数を定義して利用する時には、そもそもnullを代入するのが不可能*1なのでnullについて考える必要がない、ということになります。
さらに、このOptional型を参照するときには必ずnullチェックを行う必要があるので、全てOptional型で定義してしまってもはや意味がない、という状況にはなりません。
Optional型のnullチェックは、上の例で
bundle?.let { intent.putExtra("args", bundle) }
この部分です。この場合は、bundleがnullではない時にintentのExtraにbundleを追加する、という処理になります。letはKotlinの全てのオブジェクトに自動的に追加されるメソッドです。ただ渡された処理(この場合は"intent.putExtra("args", bundle)")を実行するだけのメソッドですが、Optional型と組み合わせると効果的に使う事ができます。
曲がった事が嫌いなしっかり者のKotlin
Kotlinは再代入可能な変数をvar、再代入不可能な変数(定数)をvalで定義しますが、設計上どちらでもいい場合にはvalを使うのが一般的に良いとされています。というのも、varで宣言すると、いつどこで再代入されるのか分からないので、その変数の振る舞いを予想するのが困難になってしまい、予期せぬバグ*2の原因になることがあるからです。
Javaでもfinal修飾子をつけることである程度対策することは可能ですが、Android開発をする上で避けて通れないあるものはfinalをつける事が困難です。それはViewです。ViewはActivityがCreateされたときに生成されるので、onCreate以後にfindViewByIdによって変数に結び付ける必要があります。finalな変数はフィールドの宣言文かコンストラクタで初期化されなければならないため、Viewを保持するためのフィールドをfinalにするのは難しい*3です。
しかし、KotlinであればDelegateという仕組みを使うことで再代入不可能な変数として宣言することが可能です。
fun <T : View> Activity.bindView(resId: Int, clickListener: View.OnClickListener? = null): BindViewDelegate<T> { return BindViewDelegate(this, resId, clickListener) } class BindViewDelegate<T : View> (val activity: Activity, val resId: Int, val clickListener: View.OnClickListener? = null) { private var value: T? = null @Suppress("UNCHECKED_CAST") fun newValue(resId: Int): T? { return activity.findViewById(resId) as? T } fun initValue(): T?{ value = newValue(resId) clickListener?.let { value?.setOnClickListener(clickListener) } return value } operator fun getValue(thisRef: Any, prop: KProperty<*>): T { return value ?: initValue()!! } operator fun setValue(thisRef: Any, prop: KProperty<*>, value: T) { this.value = value } }
色々込み入ったことをしていますが、重要なのはBindViewDelegate.getValueの部分です。
return value ?: initValue()!!
何をしているかというと、valueがnullでなければその値を、nullであればinitValue()を実行してその戻り値をOptionalを外して返すということをしています*4。これによって、初めて変数にアクセスするタイミングで初期化をするようになります。
Delegateは英語で委譲という意味で、オブジェクトの操作の一部を他のオブジェクトにまかせる仕組み、もしくはその操作を担うクラスの事です。例のBindViewDelegateはオブジェクトへの代入と参照を代わりに行うようなDelegateになっています。このDelegateを利用するには以下のように記述します。
val sampleButton: Button by bindView(R.id.main_sample_button, clickListener = this)
投稿時現在、はてぶにKotlinのシンタックスハイライトが付いていないのでわかりにくいですが、byというのがDelegateを適用するキーワードになっています。このような定義をしてもsampleButtonの型はあくまでButtonのまま、代入と参照の振る舞いだけをBindViewDelegateに委譲した変数になります。これでViewも再代入不可能な変数で定義する事ができました!!かわいい!!!
おわりに
Kotlinは現在1.0betaが公開されていて、正式なバージョンは年内か年始にリリースされる(らしい)です。私はKotlinの事を半年程前に初めて知ったのですが、初回のリリースから現在までにかなりの多くの仕様変更と機能追加があったそうで、これからもどんどん進化していく可能性があります。まだ実案件でKotlinを使う例は多くないですが、これから増えていくことを期待しています。もしこの記事や公式ページで興味を持たれたのであれば、一度触れてみてはいかがですか?
RubyでPOST・GETする。
RubyでPOSTとかGETとかリクエストを送る方法ってちょっと調べてみても色々ありますよね。
Net::HTTP::POSTをnewしてやってるやつとか、request_post()を使ってるやつとか。
ここでは後者の方を説明します。
require 'uri' require 'net/http' uri = URI.parse(url) http = Net::HTTP.new(uri.host, uri.port) res = http.request_post(uri.path, params, { 'Content-Type' => 'application/x-www-form-urlencoded' })
POSTの例です。
resの前まではテンプレなので、特別なことをしないならばそのままいじらなくていいと思います。
POSTの場合urlにパラメータを含めず、代わりにrequest_postの第二引数paramsの位置に"text=hoge&id=fuga"の様な形で渡します。
当たり前ですが、ヘッダは場合によって書き換えてください。
次にGET。
require 'uri' require 'net/http' uri = URI.parse(url) http = Net::HTTP.new(uri.host, uri.port) res = http.request_get(uri.path + params, { 'Content-Type' => 'application/x-www-form-urlencoded' })
POSTと大体同じですが、GETの場合はパラメータをパスと一緒に第一引数に渡します。
URI.parseのときにurlにパラメータが含まれていてもuri.pathはパスしか返ってきませんので、あとからパラメータ文字列を足す必要があります。
uriからパラメータをもらってくる方法もあるかもしれませんが、私は知りません。
知っている方は教えてください!