トップ 差分 一覧 ソース 検索 ヘルプ RSS ログイン

関数型Scala

[Scala]

  • このページには関数型言語としてのScalaの活用方法をまとめたいと思っています
  • Scalaの関数型的設計はHaskell由来のものが多いと感じます
  • まずはこの本を読みましょう

Scalaスケーラブルプログラミング

Scala関数型デザイン&プログラミング―Scalazコントリビューターによる関数型徹底ガイド

文法

  trait

trait とは
Scala スケーラブルプログラミング 12章 トレイト に詳しい説明がある

管理人から見た所感

  • Javaで言うところのclassであるしinterfaceのようなもの
  • Javaではclassは多重継承できないけど、Scalaではtraitを使ってmixinできる
  • Javaのinterfaceが定義できるのは仮想関数だけ(Java8からメソッドも定義できるようになったとか)だが、traitはほぼクラスと同じようなことが定義できる

なんかいろいろ書いたけど、実用的なのでScala書く時は是非trait書いてドヤ顔しましょう

Diamond Problem(菱形継承問題)

traitが言語仕様として生まれた背景には、先人の言語であるJavaやC++における 多重継承問題がある、単純に書くと

  • C++は多重継承が起きた時のために、継承元の情報を各継承先でも内包するようにした。だからクッソ複雑なエラーとか出るようになってストラウストラップ先生の頭がハゲた
    • C++は多分本当に早いのだと思う。メタプログラミングの性能、ジェネリックプログラミングの方法などはもしかしたらScalaより優れているかもしれない。けどもやっぱり書くのがダルいしWEBには向かない。
  • それを見たゴスリンは複雑性をプログラマーに与えるのは可哀想だと思って、多重継承一切禁止でinterfaceimplementsのみで実装するようにJavaを設計した
    • 誰でも使えて・どこでも動く言語は成功を収めているように見える。日本では、Javaは自分自身を薄利多売する技術者を産んだ。それぐらいしか悪いところはない
    • ごめん、Javaは長ったらしいからそこは悪いわ
  • Scalaを作ったオダスキーはJavaの冗長さはクソだと思ったので、ある方法で多重継承ありにした。
    • その方法とはtraitを言語仕様に入れて、複数のtraitが継承元にある場合、一番優先されるクラスの情報は一番右に書かれたtraitにするという単純なものだった
  • 一方D言語のウォルターはC++の邪悪さは嫌いだったので、Javaの方針をまねつつmixinもつくった

  Type Class(型クラス)とは

  • 異なる型に共通のインターフェイスを持たせて、統一的に扱えるようにするもののことらしい
  • 日本のコミュニティでは大方「型クラス」と呼ばれて親しまれているようだ

後述のtraitimplicitを使用して作る

    • パッと見、C++/D言語のtemplateとさほど変わらないように見えるのですが、そこはどうなんですかね

  Implicit

  • Scala スケーラブルプログラミング 21章 暗黙の型変換とパラメーター に詳しい説明がある

implicit def でメソッドを traitで定義して、そのtraitを使いまわすことで暗黙の型変換を行いたいらしい。

 Implicitの型探索の順序を考慮した上でのクラス定義のおすすめの順序
 implicitパラメータは呼び出しのスコープ以外に次のオブジェクトからも探索します
  1. データ型のコンパニオンオブジェクト
  2. 型クラススーパークラスのコンパニオンオブジェクト
  3. 型クラスのコンパニオンオブジェクト
 この順序でインスタンスを定義するオブジェクトを検討するとよいでしょう

コンパイラに型の推定を行わせるためのテクニックがあるっぽい。C++みたいですね。

  Monoid

標準的な話ではなくて、関数型プログラミング・数学の話

要は数学の計算の結合法則みたいなものだと思う。それが、数値だけではなく、文字列やリストについても適用できる。

ついでにもう一つ, モノイド と呼ばれる代数構造を紹介します.

結合法則を満たす演算が定義されている. a ○ (b ○ c) = (a ○ b) ○ c
単位元がある. ea=ae=a
例えば、(3 * 4) * 5 も 3 * (4 * 5) も、答は 60 です。
++ についてもこの性質は成り立ちます。 … この性質を結合的 (associativity) と呼びます。
演算 * と ++ は結合的であると言います。結合的でない演算の例は - です。

scala> (3 * 2) * (8 * 5) assert_=== 3 * (2 * (8 * 5))
scala> List("la") ++ (List("di") ++ List("da")) assert_=== (List("la") ++ List("di")) ++ List("da")

ここにMonoidの使用例について書く

  Functor

あとで

  Monad

  Free Monad

  • Free モナドとは、Functor を ベースに Monad を作れる構造のこと
  • 合成可能なDSLを作りたい時に使われる

合成とは複数の関数による処理をmapとかeachみたいなやつでまとめられる処理にすること、だと思う。

 さて、まず今回話すことを理解するには、Free Monadをある程度わかってないといけません。自分なりのFree Monadの理解を(実用的にプログラムを書くのにあたって使うという視点で)適当にまとめると
 
 Functorがあれば任意のデータ型をMonadにできる
 Coyonedaを使えば Kind が * のものは、FunctorさえなくてもFree Monadにできる(それをOperational Monadと呼ぶらしい)
 ある意味、IOモナドや、Readerモナドの代替というか、それらを組み合わせたような効果を表現できる(?)
 FreeでDSLを作っておいて、それを実行するinterpreter*3を切り替えることによって、とても綺麗に副作用を切り離すことができたり、副作用*4の実行部分を超柔軟にあとから切り替えることができる(テストなどで便利)。DIフレームワークや、テスト用のモックライブラリなんていらなかったんや!

雑多な話

  Property Based Testing

元ネタ => @xuwei-k さんのProperty Based Testing

  • 証明と普通のテストの中間どころ…らしい
  • scalacheckのテストの書き方の例
forAll { l: List[String] =>
  l.reverse.reverse == l
}
  • List[String] は半自動で生成される
  • 事前に関数を定義しておけば、任意の型の値が生成可能
  • デフォルトだと100回テスト

Arbitrary

  • 日本語に訳すと "任意の" という意味
  • ランダムな値を生成するための型クラス
  • (Scalaの場合)Arbitrary自体がMonad
 → つまりはArbitraryという概念をもとに、Genなどのクラスがデータをランダムに生成する
 → テストが出来たヤッター
 → でもデータサイズがでかすぎて処理が重すぎて死んだり、データのばらつきが十分ではない場合がある
 → そこで、小さいものからテストするという概念を導入 '''SmallCheck'''

お名前: コメント: