Scalaの壁をぶち壊せ! "壁パン1発目" ~ 導入編
はじめに
バカ向け言語のエントリでscalaは簡単にコーディング出来ると書きました。それは事実なのですが、そこに行き着くために超えなくてはならない壁が、いくつか立ちはだかっています。
ぼくが分かり難かった部分を中心に、Scalaの壁をぶち壊して先に進むためのサポート的な何かを書いていこうと思います。ただ、scala入門では無さそうな予感がするので、初めての方は先にどこかでscala入門を読んでおいたほうが良いかもしれません。
実行環境
scalaの実行環境を構築するにはplay frameworkを突っ込むのが一番簡単です。
1. 落とせ!
Play Framework - Build Modern & Scalable Web Apps with Java and Scala
playframeworkをダウンロードしてください。
2. 通せ!
ダウンロードしたzipファイルを展開して、パスを通してください。
3. New!
"play new sb" を実行します。 アプリ名と言語を聞かれるので、scalaを選んでください。
使い方
便利なコマンド
意外と知らないscala consoleの便利コマンドを紹介します。
:paste
consoleでは1行単位でしか入力出来ませんが、":paste"を使えば複数行をまとめて入力出来ます。では、試してみましょう。
1. 以下のコードをコピー
object MinamikeDSL { implicit class i2Chiaki(i:Int) { def を2倍にする = s"それは${i * 2}だバカやろぅ" } }
2. consoleで":paste"コマンドを実行*1
3. コピーしたコードをペースト
4. "Ctl + D"でpasteモードを終了
5. 実行
scala> import MinamikeDSL._ scala> (1 + 10) を2倍にする
はい、気持よく罵られましたね。これであなたは新たな道への第一歩を踏み出したのです。
:type
":type"コマンドは値の型(クラス)情報を参照出来ます。例えば"s"の場合Stringと表示されます。
scala> :type "s" String
ここまでだと大した事ありませんが、これに"-v"オプションをくっつけると化けます。
scala> :type -v "s" //一部省略 // Type signature String // Internal Type structure final class String extends Serializable with Comparable[String] with CharSequence
何という事でしょう、継承関係も表示されるようになりました。これで型が大体どんな機能を持っているのかを把握出来ますね。
:warnings
":paste"コマンドでコードをpasteした時になにやらwarningが表示されていました。
warning: there were 1 feature warnings; re-run with -feature for details
"-feature"オプションを付けてもう一回実行しろと言う事ですが面倒ですよね。そんな時に":warnings"です。
scala> "abc" tail warning: there were 1 feature warnings; re-run with -feature for details res6: String = bc scala> :warnings //一部略 <console>:12: warning: postfix operator tail should be enabled by making the implicit value language.postfixOps visible. This can be achieved by adding the import clause 'import scala.language.postfixOps' "abc" tail
再実行する事無く、warningが表示されました。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (40件) を見る
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (45件) を見る
*1:過去の行いを無かったことにしたい場合は":reset"で記憶を消去
バカ向け言語 Scala
なぜScalaがバカ向けなのか。
ぼくの経験を元に、バカ向け言語と非バカ向け言語を比較しながら見て行きましょう
非バカ向け言語 C
プログラマーとして最初に携わったのがC言語です。
それは以下のようなものでした。
- 何十ものファイルにまたがるグローバル変数
- 緻密な制御が必要であるにも関わらず、無秩序に取得/開放が行われているメモリー管理
このような複雑な構造を、ぼくのようなポケコン並の処理能力しか持たないバカに把握可能でしょうか。もちろん不可能です
そこで、次のようなコーディングを心がけました。
- グローバル変数を使わず、関数に引数を定義して渡す
- メモリーの取得/解放を同一ファイル内に限定する
これで、メモリーや変数参照の影響範囲を限定し、ぼくのようなバカでも理解できるようになります。
ですが、わざわざそんな事をやらなくても全てを理解できるエリート様達は、
「なぜ引数で渡すんだ。グローバル変数で渡せば簡単に出来るだろう。無駄な事はするな!バカ」と罵ってきます。
「すみません。こうしないとプログラムを把握出来ないんです」とひたすら謝りながら辛い日々を過ごしてきました。
しかし、そこに希望の光が差し込みました。そう、Javaです。
元祖バカ向け言語 Java
今は亡き光の帝国が生み出した仮想機械用言語
javaは、オブジェクト指向という考え方をベースに設計されています。
オブジェクト指向を利用する事によって、変数の影響範囲をクラス内に限定する事が可能です。
つまり、今まで罵られながら行なっていた変数の影響範囲を制限するコーティングを、オブジェクト指向を使うことによって堂々と行うことが出来るようになったのです。
しかも、ガベージコレクションという機能によって、メモリー管理からも開放されます。
ただ、javaはそう簡単には広まることはありませんでした。グローバル変数や複雑なメモリー管理を使いこなすエリート様達にとって、オブジェクト指向やガベージコレクションなど不要な物なのですから。
オブジェクト指向のようなバカ向けの考え方など、受け入れられるはずがありません。
それでも世間のバカには需要があったようで、少しずつではありますが普及し始めます。それに伴い理解あるエリート様もバカのレベルに合わせ、オブジェクト指向を使うようになって来たのです。
ただ、コボラーと呼ばれる上級エリート様達は「そんなバカ向けの考え方など、理解する必要も無い」と全く理解しようともせずに一切を否定していました。確かに仰る通りなので返す言葉もありませんでした。
非バカ向け言語 Ruby
Javaが出てしばらくした後、rubyと呼ばれるオブジェクト指向の動的言語が発表されました。
rubyはオブジェクト指向を導入する事でバカに優しく、変数に型を持たない事で、プログラミング能力が低い人達も簡単にコードを書く事が出来る、素晴らしい言語でした。
バカでプログラミング能力も低いぼくはすぐに飛びつきました。確かに簡単にプログラミングが出来ます。しかし、規模が大きくなるにつれてバカ向けでは無い事が分かって来たのです。
まず、型が無いと言う事は、変数がどんな型なのかを全て記憶し把握しておく必要があります。
さらに、rubyでは動的にメソッドが追加出来ます。ライブラリを追加するといつの間にか利用しているクラスの挙動が変わってしまう可能性があるのです。
全ての型を把握し、ライブラリまで細かく理解する事がバカに可能でしょうか。
そうです。rubyはバカ向けを装っていますが、実際はエリート様向け言語だったのです。
もちろん、このようなエリート様向け言語では、バカが生き残る道はありません。仕方なくjavaへと戻って行きました。
第二次デザパタ戦争
javaでの平穏な日々がこのままずっと続くかのように思われていた頃、デザパタ厨と呼ばれ「デザパタ入門」というSpell Bookを装備したエリート様達が現れました。
彼らは「どんな変更にも耐えられる」と呟きながら「リファクタリング」という呪文によって、バカでも理解出来るようシンプルに実装されたコードを複雑な物にどんどん書き換えて行きました。
ぼくは「シンプルに実装して、必要な時にすぐに書き換えるようにすれば良い」と戦いましたが、「プロパー」という強大な権力の前では、その声が届く事はありませんでした。
結果、彼らが通った後には大量のinterfaceと抽象classの荒野だけが残ったのです。
これが後に「第二次デザパタ戦争」と呼ばれるようになった戦いです。
さらに悪夢は続きます。オブジェクト指向によって葬り去られたと思われていたグローバル変数が、「シングルトン」の名の下に蘇ってしまったのです。
また、あの暗黒の時代が訪れるのか…と落胆していた時、Scalaが現れました。
「そのもの赤きオブジェクト指向の衣をまといて、金色に輝く関数型の野に降り立ち、バカ供を平穏の地に導かん」
と言う伝承は本当だったのです。
そして、Scalaへ…
おかしな方向に突き進みそうになっているので、ここで一旦軌道修正してScalaのバカ向け機能について説明しましょう。
オブジェクト指向
scalaは関数型言語とよく言われていますが、紛うことないオブジェクト指向言語です。javaよりもオブジェクト指向の機能が強化されているので、より扱いやすくなっています。
また、後述の関数型機能や型ライブラリと組み合わせることによって柔軟なコーディングが可能となり、オブジェクト指向の「デザインパターン」は不要になります。
関数型
scalaは関数型機能も持っています。
関数型プログラミングの手法を使うことで変数を使わずに処理を記述出来ます。 scalaでは、val宣言を利用し値を全て定数として扱い再代入を抑制する事で、変数の状態変化を覚えておく必要が無くなると言うことです。
また、参照透過性によって関数に渡された引数が同じであれば必ず結果も同じ事を保証します。
これらの特性によって、オブジェクト指向だけの場合よりさらに考える範囲を限定出来るのです。
//再代入出来ない val x = 3 x = x + 10 //コンパイルエラーになる
型ライブラリ
scalaには豊富な型ライブラリがあります。これらを利用する事によって非常に簡単にコーディングが可能です。
以下の例ではsplitで文字列型を配列型に変換し、配列型の関数を利用しています。javaで同じ処理を書いて比較してみてください。
//CSV文字列の数字を全て合計する "33,44,55".split(",").map(_.toInt).sum
型推論
型推論によって、動的言語のように型を書かずにコーディングする事も、型を明記する事も出来ます。
わざわざ無駄な記述をしなくても全てを把握出来るエリート様に余計な負担をかけないようにしつつ、型を書かなければ理解出来ないバカにも優しい素晴らしい機能です。
//エリート様向け Some(140000) map (_ + 5000) map (_ - 80000) //バカ向け val rentSupport:Int => Int = _ + 5000 val dormFee:Int => Int = _ - 80000 val salaryCalc = rentSupport andThen dormFee Some(140000) map salaryCalc
implicit(暗黙の型変換)
scalaも既存クラスへのメソッド追加が可能です。
暗黙の型変換機能を利用する事によってメソッド追加を行う際に、メソッド追加の影響を限られた範囲に狭める事が出来ます。
また、暗黙変数(implicit)として宣言された引数を暗黙変数のスコープ内で呼び出した場合、省略する事が出来るので、コードがよりシンプルになります。
def f(x:Int)(implicit y:Int) = x + y implicit val x = 10 f(10) //-> 20 implicit val x = 100 f(10) //-> 110
パターンマッチ
パターンマッチは柔軟性が高くいろいろなパターンを簡単に記述する事が出来ます。
例えば以下のように記述すると、配列の先頭が"2"の場合は先頭を除いた合計を計算。それ以外の場合は全ての合計を計算する事が出来ます。
def f(lst:List[Int]) = lst match { case 2 :: xs => xs.sum case x => x.sum } f(List(1,2,3,4,5)) //-> 15 f(List(2,3,4,5)) //-> 12 f(List(2,3,4)) //-> 7 f(List(3,4)) //-> 7
代数的データ型
代数的データ型を利用して列挙型を表現する事によって、パターンマッチ時に足りないパターンがあればコンパイラが警告してくれます。忘れっぽいバカにぴったりですね。
まとめ
このように、scalaにはコーディングをシンプルかつ簡単に行う為の機能が沢山搭載されています。
まさにバカ向け言語と言うに相応しいのではないでしょうか。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (40件) を見る
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (44件) を見る
「淡路島の電車の運行状況を取得する方法」のinterface問題をscalaの型クラスで書いてみた
元ネタはひしだまさんのエントリです。
淡路島の電車の運行状況を取得する方法 - ひしだまの変更履歴
その元ネタはこちらです。
「淡路島の電車の運行状況を聞いた話」をシステム開発に置き換えてみる - 日々常々
interfaceを個別に増やすなら型クラスかな〜と思い試しに書いてみました。シンプルにする為に、いろいろ手を抜いていますのでご了承ください。
ついでなので、元の元記事のnull問題も解決して、DSLを使って日本語で会話している風にしてあります。実際に動くので、sbt等で試してみてください。
簡単に説明
型クラス
型クラスを使う事によって、型ごと個別に振る舞い(interface)の定義ができます。今回は鉄道路線だけですが、後から飛行機やバス等を個別に増やすこともできます。*1
null問題
なんという事でしょう。scalaだとnull問題はEither型で解決できてしまいます。Eitherは2つの型を別々に保持する為のコンテナ(箱)で、今回は、運行状況の取得に成功した場合の値(Right)と失敗した場合のメッセージ(Left)を格納しています。Maybeを使えというコメントが多かったようですが、失敗した原因も分かった方が良いですしね。
日本語DSL
日本語DSLはおまけです。scalaって簡単にこんな事が出来るんですよ。丸括弧は…見なかったことにしておいてください…(ノД`)
要望があれば、コードの詳しい説明も書きます。
尚、このエントリを読んだことによりjavaを投げ捨てたくなっても、当方は一切責任を持ちません(´・ω・`)
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (44件) を見る
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (40件) を見る
*1:型クラスは後ほど別エントリで説明する予定です
なんでこんなブログ書いてるの?
本当はブログを書く時間があるなら、だらだらしながらゲームしたり本を読んだりしたいのです。
なのになぜ、ブログを書こうかと思ったかを簡単に書いておこうと思います。
もっと分かりやすく書いてよ
関数型界隈では謎記号や専門用語を使った学術的な小難しい説明が多く、ぼくのような数学の専門的な知識が無い人には理解するのがとても大変です。さらに、定義が理解出来ても「で、それ仕事でどう使うの?」という所まで言及していないので、すっごくモヤっとしたままになる事が良くありました。
少しでも不毛な時間を過ごす人が少なくなるように
そこで、ぼくがscalaを仕事で使ってきた経験を生かして、専門用語を出来るだけ少なく、お仕事で使うような具体例を挙げて説明する事で、関数型の考え方で悩む人が少なくなればいいなと思ってます。*1
まったりと
筆が遅いのでたまにしか記事は書かないと思いますが、気長に付き合ってもらえると嬉しいです。
*1:AnimalやCarは出てこないので安心してください
scalaをお仕事で使う時の3つの心得
・型を書け!
・名前を付けろ!
・合成しろ!
型を書け!
「は?型推論あるのにわざわざ型なんて書かなくていいし!」と思いましたか?
では保守を任されて緊急でソースを理解したい時にこのようなコードだった場合はどうでしょう。
//XXとYYはどこか別の場所で定義されている class AAA[A <: XX, B <: YY] { def f(a:A,b:B) = a.xx + b.yy }
「戻りの型くらい書いとけ、ばかやろぅ」と言いたくなりましたね。それが普通の反応です。
class AAA[A <: XX, B <: YY] { def f(a:A,b:B):String = a.xx + b.yy }
戻り値の型がある場合と比べて見てください。コードの読みやすさが全く違います。このようにコーディングする際にほんのひと手間掛けるだけで、保守時のコストが大幅に削減出来ます。
保守のコストを削減する事によるメリットは開発全体で見ると計り知れません。開発時はせいぜい一人か二人しかコードを見ませんが、保守は長期に渡って続き、複数の人がコードを読むのですから。
名前を付けろ!
scalaだとclosureが使えるのでついつい無名関数を多様しがちです。でも、コーディングしている時はどういう処理か分かっていたとしても、半年後に見た時に覚えていられますか? ぼくは無理です。
無名関数では無く、きちんと名前を付ける事によって、いつ、誰が見ても処理が分かりやすくなります。
//× saraly map (_ + 5000) //○ val rentSupport:Int => Int = _ + 5000 saraly map rentSupport
合成しろ!
scalaに慣れてくると、mapをチェーンしたりしてどんどんコードが横長になってきます。ワンライナーで書けるのが楽しいんですよね。気持ちは良く分かります。
だがしかし、mapをチェーンするという事はループが沢山実行され、しかも型も名前も書きません。ダメ過ぎです。mapがチェーンし始めたら、関数合成でmapを最小限に減らしましょう。
//× Some(140000) map (_ + 5000) map (_ - 80000) //○ val rentSupport:Int => Int = _ + 5000 val dormFee:Int => Int = _ - 80000 val salaryCalc = rentSupport andThen dormFee Some(140000) map salaryCalc
まとめ
この心得は、ぼくがscalaをお仕事で使ってきて悟った事を書いています。scalaのコードが分かり難いと言われるのは、型も名前も無いコーディングスタイルが起因しているのではないでしょうか。
またこの記事は、ある程度の人数で開発し保守を行うプロジェクトを前提にしています。プロトタイプのような使い捨てコードで、保守よりも開発スピードを優先する場合は、型も名前もぶっ飛ばしてコーディングしてください。
だって、scalaの語源であるスケーラブルはどのような開発(スタイル)でも柔軟に対応出来ると言う意味でもあるのですからね。
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (43件) を見る
圏論とかモナドなんて簡単だからscalaを使って説明してみた
はじめに
関数型といえばモナド、モナドといえば難しいという事が巷で言われていますが、いきなりモナドを理解しようとするから難しく思えるだけで、圏論から順序を追って理解していけば全然難しく無いんだよって事を分かって貰えればいいなぁと思い書いて見ることにしました。
ただ、圏論といっても適用範囲がとっても広く、応用編になると分けわかんなくなってくるので、ここではプログラミング分野に特化したFP(functional programing)圏論*1について書きます。
また、説明を簡単にする為に細かい部分をいろいろ省略しています。学術的な定義としては正確ではないので、このエントリの説明は大体合ってる位の気持ちで読んでくださいね。
尚、ぼくは圏論の詳しい事はさっぱり分からないので、学問的な話を振られても回答できませんキリッ
圏ってなんなの?
圏論と言えば、圏です。
圏って何なのかというと、対象(object)と射(arrow)で構成され、対象どうしが射で接続されているものが集まったグループです。対象は実体が無いラベルのようなものと理解しておいてください。射はラベル同士を接続します。対象Aと対象Bが射fで結合されているとすると、f:A -> Bと書くことができます。scalaで書くと以下のようにそのまま書けます。
圏
また、射同士も結合則にしたがって合成する事ができます。例えば、対象Aと対象B、対象Cが存在し、AとBが射f、BとCが射gで接続されているとすると、 gとfが合成できます。scalaだとこうなります。
結合
射のお尻と別の射の頭が同じラベル(対象)であれば合成可能です。
で、それが何の役に立つの?
それじゃ、お仕事で使うような例で書いてみます。住所から郵便番号を取得する仕様を圏を使って定義してみましょう。住所圏ですね。
住所圏
住所と郵便番号を対象として定義し、case classとStringを割り当て、射(def)でつなげました。簡単なので説明はいりませんね。
では、使ってみましょう。住所をzip射に適用すると郵便番号に変換されます。
それが関手(functor)なんだよ
ここで、住所があるか無いか分からない場合について考えてみましょう。scalaだとOptionを使いますよね。でも、Option型をさっきの住所圏に適用するには型が合いません。じゃどうするか。少しでもscalaをやったことがあれば分かりますね。mapを使います。それでは書いてみましょう。
これが関手といわれるものです。住所圏からOption圏への関数合成みたいなものですね。このコードを少し変形して合成されている事が分かるコードを書いてみます。
住所 => 郵便番号を渡すとOption[住所] => Option[郵便番号]が返ってきてますね。
また住所が複数あった場合でも、同じzip射が使えます。OptionをListに変えてみましょう。
モナドさんだけ日本語ついてなくて可哀想
さてここで、住所圏のzip射が複数の値を返す場合を考えてみましょう。住所が県や市までだった場合、郵便番号が沢山返ってきますから。scalaで書いてみます。
郵便番号が沢山返ってくるパターンを定義して、関手(map)に適用してみました。でもこれだと、List[郵便番号]が欲しいのにList[List[郵便番号]]が返ってますよね。これを意図通りに返す為にモナドを使います。モナドをscalaで扱う時はflatMapです。
はい、これで欲しいものが返ってきました。scalaの立場からみると、モナドは関手(map) + flattenと同じ事です。
まとめ
- 関手は射の変換先がコンテキスト(ListやOption)にくるまれていない時に使う。
- モナドは射の変換先がコンテキスト(ListやOption)にくるまれている時に使う。
射っていうのはscalaだと単なる関数だし、関手はmap、モナドはflatMapなだけです。これのどこが難しいというのでしょう。
まぁ確かにモナドの応用編になってくると多少難しくなって来ますが、ここまで理解できてればそんなに難しくありません。
モナドが難しいっていうのは思い込みと、教える人が悪いだけです。いきなりモナドではなく、圏から順を追っていけばすんなり理解出来ると思います。
自分でモナドを作る場合はモナド則とか考えないといけませんが、使う分には気にしなくて大丈夫です。
あと、そもそもscalaが分かんないって人は以下の本を読んでみてください。
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (43件) を見る
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (39件) を見る
*1:勝手にそう呼んでます