410 likes | 651 Views
株式会社 Phantom Type 設立記念. Scala の すゝめ. 2012 . 9 . 25 前田康行 (@ maeda _). 自己紹介. 前田康行 (@ maeda _) 勤務先:某社(こくぼさんの元同僚) 好きな言語 Scala Smalltalk DyNagoya Dy namic Language + Nagoya http:// dynagoya.info /. アジェンダ. 静的型付け関数型プログラミングのすばらしさ Scala のすばらしさ Scala を始める. 静的 型付け 関数型プログラミングの すばらしさ. 関数型.
E N D
株式会社Phantom Type設立記念 Scalaのすゝめ 2012.9.25 前田康行 (@maeda_)
自己紹介 • 前田康行 (@maeda_) • 勤務先:某社(こくぼさんの元同僚) • 好きな言語 • Scala • Smalltalk • DyNagoya • Dynamic Language + Nagoya • http://dynagoya.info/
アジェンダ • 静的型付け関数型プログラミングのすばらしさ • Scalaのすばらしさ • Scalaを始める
静的型付け関数型プログラミングのすばらしさ静的型付け関数型プログラミングのすばらしさ
関数型 と言っても、何かよく分かりませんが、 一番根底にあるのは「式指向」 と考えています。
式指向(Expression Oriented) • 式とは • 値は式 • 関数の引数に式を渡して評価すると値になる → 式 • 式指向とは • 式を組み合わせてプログラムを作る • 手続き型は文を順次実行する // 式指向 funcA( funcB( funcC())) // 手続き型 processA() processB() processC()
式指向(具体例) • 手続き型の場合 • 式指向の場合 for(i = 1; i<= 10; i ++){ if ( i % 3 == 0 ){ println(i + 1) } } (1 to 10) filter { _ % 3 == 0 } map { _ + 1 } foreachprintln
式指向(具体例) • 式指向の場合 (1 to 10) filter { _ % 3 == 0 } map { _ + 1 } foreachprintln 式 式 式 式 式 式 式
もうひとつ具体例 • 式指向でWeb Serverを考える • Web Server は HTTPリクエストをHTTPレスポンスに変換する関数 • 関数の組み合わせ(値の変換)で、プログラムを記述するリクエスト → タスク → 結果 → レスポンス def server(req: HttpRequest): HttpResponse = Format.toXml(Process.do(Task.dispatch(req))) // 変数束縛を使って書いた場合 defserver(req: HttpRequest): HttpResponse = { val task = Task.dispatch(req) val result = Process.do(task) Format.toXml(result) }
「レガシーコード改善ガイド」から抜粋 接合部 接合部 (seam)とは、その場所を直接編集しなくても、プログラムの振る舞いを変えることのできる場所である 。 P.36 より • 例えば、無駄に長い手続き型のメソッドにはseamがない • 式指向ならば、あらゆる場所がseamになる! • 自然にテスタビリティ・再利用性・拡張性の優れたプログラムになる
さらに型をつけると・・・ • 型チェックされるのは、関数の引数と戻り値に対してのみ • 手続き型における文の羅列は型チェックがない • 式指向なら、何がそこに書けるかは型によって決まる • あらゆる場所で整合性のチェックがされる • コンパイルが通った時の安心感が違う // 式指向 funcA( funcB( funcC())) // 手続き型 processA() processB() processC()
型を作る • タスクは以下のものがあるとする • ユーザーの登録(パラメータ:ユーザー名・年齢) • ユーザーの更新(パラメータ:ユーザー名・年齢) • ユーザー一覧の取得(パラメータなし) • 代数データ型(Scalaならcase class)で簡単に型が作れる • 残念すぎるコードの例 • flagの値の範囲や使ってはいけない変数など、規約ができている • 規約を守るようにプログラマが気をつける sealed abstactclass Task case class CreateUser(name: String, age: Int) extends Task case class UpdateUser(name: String, age: Int) extends Task case object ShowAllUsersextends Task /* flag = 1 : ユーザー登録 flag = 2 : ユーザー更新 flag = 3 : ユーザー一覧取得(name, ageは使用しない)*/ case class Task(flag: Int, name: String, age: Int)
型を分解する • 型はパターンマッチで分解 • 型によって、取り出せるものが限定される • ケースの網羅性のチェックも可能 • 例:case ShowAllUsers => ...の行がないとワーニングが出る object Process{ def do: Task => Result = { case CreateUser(name, age) => Users.create(name, age) case UpdateUser(name, age) => Users.update(name, age) case ShowAllUsers=> Result(Users.list) } } // 呼び出し側 Process.do(CreateUser(“yamada”, 32))
もっと型を! • できるかぎり情報は型に持たせたい • 例:変数の値があるか、ないか 値レベルで → 値がない場合は変数にnullを代入 型レベルで → Option[T]型を使う val x = 3 val y = null // 簡略化したOptionの定義 sealed abstract class Option[T] case class Some[T](x: T) extends Option[T] case object None extends Option[Nothing] val x = Some(3) val y = None
Option[T]型を使う • nullを返す場合(Option型を使わない場合) • nullが返ることがあるという規約ができてしまっている • nullチェックを忘れたら、実行時にヌルポ // 渡されたidからUserを返す。見つからなかった場合はnull defgetFromId(id : Id): User= ... deffunc(id : Id):Result = { val user = getFromId(id) if (user == null) null else doSomthing(user) }
Option[T]型を使う • Option[T]を使う場合 • 値がないかもしれないことは型をみれば分かる • ヌルポはありえない!(型が合わずにコンパイルエラーになる) // 渡されたidからUserを返す。 defgetFromId(id : Id): Option[User] = ... deffunc(id : Id): Option[Result] = getFromId(id) match { case Some(user) => doSomthing(user) case None => None } }
Option[T]型を使う • Option[T]を使う場合(パターンマッチを使わない) • Option型には便利なメソッドがいろいろある // 渡されたidからUserを返す。 defgetFromId(id: Id): Option[User] = ... // ユーザーがいなかった場合は結果はなし deffunc(id: Id): Option[Result] = getFromId(id) map doSomthing // ユーザーがいなかった場合はAnonymousで実行 deffunc(id: Id): Result = doSomething(getFromId(id).getOrElse(User.Anonymous))
型はドキュメント • 自然言語では抽象的な概念を表すことが難しい • 抽象的な概念は人間にはあいまいになりがち • コンピュータはあいまいでは動かない • 型は形式的な記述。矛盾はコンパイルエラー • 型があれば、安心して抽象的な概念を扱える
型はドキュメント(具体例) • お題 キーとそれに対応する値に変換する関数とキーのコレクションを渡して、渡されたキーに対応する値のコレクションを返す関数を書け。ただし、キーに対応する値は高々1つとする。 • 型を書かないで、変数名でがんばってみる • 型があれば、変数名は適当でも分かる deffunc(keys, converterFromKeyToValueOrNullIfMissed) = ... // converterFromKeyToValueはKeyに対応する値がない場合nullを返す deffunc(keys, converterFromKeyToValue) = ... deffunc[A, B](keys: Seq[A], f: A => Option[B]) = keys flatMap f
静的型付け関数型言語まとめ • ここまでは静的型付け関数型の共通の話(もちろん、文法は異なります。) • 基本パーツは、これだけ • 式 • 型 • パターンマッチ • 柔軟 + 明瞭 + 型安全
Scala • 静的型付け関数型 • 代数データ型/パターンマッチ • 型推論 • オブジェクト指向 • 前衛的な機能 • 暗黙のパラメータ / 暗黙の型変換など • JVM上で動く • コンパイルするとjarファイルができる • ScalaからJavaのjarを使うことも、その逆も可能
充実のコレクション - immutableとmutable - • immutable(不変) • List • Map • Set • mutable(変更可能) • ArrayBuffer • ListBuffer • Map • Set
充実のコレクション - 便利メソッドもいっぱい - • お題 • コレクションxsの中の2要素の組み合わせ全て列挙せよ • 前のページのどのコレクションでもcombinationsメソッドが実装されている • コレクションの種別ごとに実装していたら、メンテナンスが大変 xs.combinations(2)
充実のコレクション - そこで継承ですよ - • combinationsメソッドの実装は一カ所のみ。他のメソッドも重複する実装は最小限にとどまっている • コレクションの親クラスで実装されているため、子クラスごとに個別に実装する必要はない • 特定コレクションについて、適切なアルゴリズムがある場合は、オーバーライドして、個別に最適な実装をしている • Traitにより、モジュールの分割や組み合わせが容易にできるため、きめ細やかな階層構造が作れる trait Map[A, +B] extendsIterable[(A, B)] withGenMap[A, B] with MapLike[A, B, Map[A, B]] traitIterable[+A] extends Traversable[A] withGenIterable[A] withGenericTraversableTemplate[A, Iterable] withIterableLike[A, Iterable[A]]
充実のコレクション - (いい意味で)やばい多相性 - Set(1,2,3) ++ List(2, 4, 6) // → Set(1, 6, 2, 3, 4) List(2, 4, 6) ++ Set(1,2,3) // → List(2, 4, 6, 1, 2, 3) Map(1 -> "a", 2 -> "b") ++ List(3 -> "c", 4 -> "d") // → Map(1 -> a, 2 -> b, 3 -> c, 4 -> d) ListBuffer(1, 2, 3) ++ List(4, 5) // → ListBuffer(1, 2, 3, 4, 5) • 異なる種類のコレクションの結合(++メソッド)が可能 • しかも、型が維持される(アップキャストされない) • Option型も要素数が0か1のコレクションとして扱える。 deffunc(x: String): Option[Int] = ... List(1,2,3) ++ func(“try something”)
充実のコレクション - この多相性はOOだけでは無理 - • コレクションまわりの多相性は、暗黙のパラメータ(implicit parameter)も活用している • Haskellの型クラスに対応する • 見えないところで、型によってそれっぽい組み立て方を選ぶ // 次のコードは List(1 -> "a", 2 -> "b") ++ Map(3 -> "c") // 裏では++メソッドの2つめの暗黙のパラメータ使われる // 暗黙のパラメータは型をヒントにして決まる // 型はコンパイル時に決定するので、パラメータもコンパイル時に決まる List(1 -> "a", 2 -> "b”).++(Map(3 -> "c")) (List.canBuildFrom[(Int, String)])
省略について- Scalaの省略っぷりはすごい - • Scalaは省略のテクニックがいっぱいある • 暗黙のパラメータ/暗黙の型変換 • 型推論 • import • その他、シンタックスシュガーなど // このコードは実は... List(1 -> "a", 2 -> "b") ++ Map(3 -> "c") // こんなに省略してるんです immutable.this.List.apply[(Int, String)](scala.this.Predef.any2ArrowAssoc[Int](1).->[String]("a"), scala.this.Predef.any2ArrowAssoc[Int](2).->[String]("b")).++[(Int, String), List[(Int, String)]](scala.this.Predef.Map.apply[Int, String](scala.this.Predef.any2ArrowAssoc[Int](3).->[String]("c")))(immutable.this.List.canBuildFrom[(Int, String)])
省略について- やりすぎ注意 - • 省略しすぎると、逆に分からなくなる • でも、コンテキストによって「言わずもがな」は省略したい • 省略するには、 • コンテキストが明確であることが大事 • そのコンテキストが有効なスコープが分かりやすいことが大事 • スコープのコントロールしやすいことが大事 • 暗黙のパラメータ/暗黙の型変換はスコープの中から使えるものを探す。これらの機能は特に強力なので、よりスコープの扱いが重要となる
省略について - 言わずもがなの例 - val x: Node • Nodeクラスってなんだ? • このクラスでは「言わずもがな」 • importしたから「言わずもがな」 class SomeTranslator extends TreeTranslator{ val x: Node ... } import scala.xml._ deffunc(x : Node) = ...
importがすばらしい • importで名前空間の省略が可能 • Scalaのimportはどこにでも書ける • importがスコープを持っている(ファイル全体/クラス内/関数内/ブロック内) // Something.Specialはここでしか使用しないので // ファイル全体でimportしたくない valy = Something.Special.foo( Something.Special.bar, Something.Special.baz(x) ) // importを局所化できるので、こう書ける valy = { import Something.Special._ foo(bar, baz(x)) }
さらにimportがすばらしい • オブジェクトをimportできる • コンテキストを外部から注入する例 • モジュールとしてのオブジェクトを使う • お手軽 & 強力 • さらに、ScalaにはTraitがあって・・・ • もうそろそろお腹いっぱい? class SomeClass(x : AnotherContext) = { import x._ ... }
Scalaについてまとめ • 関数型 + (クラスベースの)オブジェクト指向の世界 • 複雑。。。 • 頑張るのはライブラリ実装者 • 使う側は簡単(なことが多い) • 話してないけど、ScalaのTraitはすごい
Scalaの学びやすさ= プログラミングの始めやすさ • 書いてみないと、使えるようにはならない • Javaっぽくも書けるし、とりあえず始めれる • JVMが動けば、どこでも動く • Linux / Mac / Windows • Google App Engine / CloudBees/ Heroku • Android
ライブラリも充実 • 標準ライブラリ • 豊富なコレクション • XML • Actor • Parser Combinator • ツール • sbt • 各種IDEのプラグイン • オープンソースライブラリ • Dispatch • Scalaz • shapeless • Webフレームワーク • Lift • Play! • Unfiltered • Scalatra • spray • blueeyes • finagle • 分散フレームワーク • akka • Scalaのライブラリがなくても、Javaのライブラリも使える
Scalaの企業での導入実績 • Scalaは企業での導入実績も多数あります • Twitter • FourSquare • LinkedIn • Tumblr • Électricité de France Trading • Novell • Sony • などなど... http://www.scala-lang.org/node/1658 より抜粋
初心者がScalaを学ぶ上での問題 - Scalaの開発が活発すぎる - • もちろん、いいことだし、これからもそうあってほしい • 下記の点は注意 • 言語仕様も発展途上(すでに実用レベルだけど、進化は続く) • 標準ライブラリも新しいものがどんどん出てくる • その他のライブラリ・フレームワークもどんどん変わる • 標準のビルドツールsbtもどんどん変わる • マイナーバージョン変わったら、ビルドできなくなったとか当たり前(最近、落ち着いてきた気がする) • プラグインが最新バージョンに追従してないとか当たり前 • ドキュメントはない • ドキュメントがあっても、チュートリアル/ファーストステップ程度 • ドキュメントはあっという間に陳腐化 • ソースコードを読め • テストコードを読め
初心者がScalaを学ぶ上での問題 - 機能が多すぎる - • 関数型プログラミングを学ぶ上では、理解の足枷になるかも • HaskellやOcamlなどで概念を学んで、Scalaで書くのがオススメ • 機能が多ければ、不具合の可能性も高くなる
勉強会に行こう • 分かっていれば、簡単なことでも、自力で調べることが難しいことは、いっぱいある • いわゆる関数脳の感覚的なところは本などからは習得しにくい • 分かっている人に聞くのが一番の早道 • 名古屋の関数型勉強会 • なごやかScala毎月第1金曜日 • SML#読書会毎月第2土曜日 • TAPL-nagoya毎月第3土曜日 • ProofCafe毎月第4土曜日 • その他、随時
結び • Scalaはすばらしい言語です。 • そのすばらしさを完全に享受するのは大変ですが、その苦労以上の価値があります • とりあえず、できるところから始めてみましょう。