記事は考慮します
- Invariant Functorなどのカテゴリー理論の抽象化は、指数関数的ファンクターとも呼ばれ、Scalaで表現されます。
- すべての不変ファンクターが従わなければならない2つのルール( アイデンティティ法 、 構成法 )。
- 状態を持つ不変ファンクターの例(Value Holder)
- セット(半群)の要素間の不変のファンクター関係の例
公開は
Scalaでの
FPの拡張
です。ファンクターとは何ですか? 次の問題に対処しました
- カテゴリー理論 、 HaskellとScalaの関係は何ですか?
- 共変ファンクターとは何ですか。
- 反変ファンクターとは何ですか。
内容
Scala、数学、関数型プログラミングの世界をさらに詳しく知りたい場合は、
Scala for Java Developersオンラインコース
をお試しください(ビデオ+テスト、価格のわずか25%!リンククーポンの数は限られています!)。
はじめに
前回の記事の要点を思い出させて
ください 。
- 関数型プログラミングのフレームワーク内でカテゴリーの抽象化を理解するために、カテゴリーの理論を知る必要はありません。 さらに、カテゴリー理論は良い例を提供していません。
- Scalaでのカテゴリー抽象化の実装はHaskellから来ました。 若いScalaの例を入力できるように、より成熟したHaskellのソースコードを読み取れると便利です。
- 主なカテゴリライブラリ( Scalaz 、 Cats )は、より高い種類のジェネリックを使用して、カテゴリの抽象化を表現します。 ただし、この言語メカニズム(JavaでもC#でもない)は、再利用可能な抽象化を構築するために使用されます。 「ピース」イディオムは最小限の手段で実装できます。
- 共変ファンクターはデータソースです。
- 反変ファンクターはデータレシーバーです。
。
それでも、著者は
前の記事 を読む
ことを強くお勧めします 。
不変ファンクターとは
不変ファンクターは、共変および反変の「1つのボトル」ファンクターです。
署名の形式では、これは次のように表現できます。
trait Invariant[T] { def xmap[R](f: T => R, g: R => T): Invariant[R] }
つまり、新しい不変ファンクタを取得するには、共変ファンクタ(
f:T => R )と反変ファンクタ(
g:R => T )の両方に必要なマップを提供する必要があります。
またはグラフィカルに
+ --------------------------------------- +
R | T + ------ + T | R
-----> f:R => T -----> C [T] -----> g:T => R ----->
| + ------ + |
+ --------------------------------------- +
つまり、「変換された」
不変式[R]は、 「入力」と「出力」でデータを「待機」する相互に逆の変換
fと
gのペアを使用して、元の
不変式[T]に 「縮小」されます。
不変ファンクター-アイデンティティ法
Invariant Functor(Covariant FunctorおよびContravariant Functorとともに)も、同様のいくつかの規則に従う必要があります。 そして、最初のルール(Identity Law)には、不変ファンクター
fun [T]について 、IdentityLaw.case0(fun)はIdentityLaw.case1(fun)と同一でなければなりません。
object IdentityLaw { def case0[T](fun: Invariant[T]): Invariant[T] = fun def case1[T](fun: Invariant[T]): Invariant[T] = fun.xmap(x => x, x => x) }
共変関数の アイデンティティ法と反 変関数のアイデンティティ法の意味に目を向けると、ルールの意味が明確になります。
不変ファンクター-合成法
2番目のルール(構成法)の状態:不変ファンクター
fun [T]および関数
f1:T => R 、
g1:R => T 、
f2:R => Q 、
g2:Q => R 、CompositionLawを実行する必要があります。 case0(fun、f1、g1、f2、g2)はCompositionLaw.case1(fun、f1、g1、f2、g2)と同じです。
object CompositionLaw { def case0[T, R, Q](fun: Invariant[T], f1: T => R, g1: R => T, f2: R => Q, g2: Q => R): Invariant[Q] = fun.xmap(f1, g1).xmap(f2, g2) def case1[T, R, Q](fun: Invariant[T], f1: T => R, g1: R => T, f2: R => Q, g2: Q => R): Invariant[Q] = fun.xmap(f2 compose f1, g1 compose g2) }
共変ファンクターの 合成法と反 変ファンクターの合成法の意味に目を向けると、ルールの意味が明確になります。
例#1:バリューホルダー
前の記事から、共変と反変のファンクターは2つの「コンテナーの半分」であることがわかります(これは比phorです。より正確には署名+ルールを考慮してください)。 したがって、不変(指数)ファンクターが両方である場合、例として、最も単純なコンテナーであるバリューホルダーを使用できます。
trait Holder[T] { self => def put(arg: T): Unit def get: T def xmap[R](f: T => R, g: R => T): Holder[R] = new Holder[R] { override def put(arg: R): Unit = self.put(g(arg)) override def get: R = f(self.get) } }
たとえば、文字列の値ホルダー。
class StrHolder(var value: String = null) extends Holder[String] { override def put(arg: String): Unit = {value = arg} override def get: String = value }
デモンストレーション
object Demo extends App { val f: String => Int = Integer.parseInt(_, 16) val g: Int => String = Integer.toHexString val s: Holder[String] = new StrHolder val k: Holder[Int] = s xmap (f, g) k put 100500 println(k get) } >> 100500
文字列を16進形式で保存するIntコンテナがあります。
さて、なぜ最も単純なバリューホルダーが必要なのですか? いくつかの方向で機能を増やすことができます
- データを添付できます(バージョン、ログの書き込み/取得、取得など)。
- 呼び出しをインターセプトできます(同期、put / get呼び出しのロギング、リモートソースへのプロキシなど)。
注:不変ファンクターのルールは、xmap操作の「カウント」を禁止しますが、その他(put、get)は禁止します。 内部でput / get / ...呼び出しを記録することはできますが、xmapは記録できません。
タスク:このロジックを実装します。
例#2:セミグループ
共変および反変ファンクターは、特定のルール(同一性法、合成法)に従って動作するデータのソースおよびレシーバーであり、「コンテナーの半分」ではないことに注意することが重要です。 データを(状態の一部にするために)
保存する必要はありません。引数とメソッドの戻り値を持っている必要があります。 しかし、それらを保存しない場合、put操作とget操作は「同時に実行」する必要があります。 型パラメーターTが引数と戻り値の両方に存在する構造を探しています。
例は
状態ではなく、プロセスです! プロセス(メソッド)とは、要素間の関係を表現できることに注意してください)。
基本的な代数用語を思い出させてください(不変ファンクターを与えるセットの要素間の最も単純な関係が必要です。もちろん、最も単純なものは等価関係と順序関係ですが
、前の記事によると、それらは反変ファンクターです)。
groupoidは、1つの二項演算(T#T => T、Tはgroupoid要素のタイプ、#は演算記号)を備えたセットであり、セットを超えません。
セミグループは、操作が連想的なグループイドです((a#b)#c == a#(b#c)すべてのa、b、c)。
モノイドは中立要素を持つセミグループです(任意のaに対してa#e == e#a == aとなる要素 'e'が存在します)。
操作で設定 | Groupoid | セミグループ | モノイド |
---|
Intおよび '+' | + | + | + |
Intおよび '-' | + | - | - |
Intおよび '*' | + | + | + |
Intおよび '/' | - | - | - |
文字列と「+」 | + | + | + |
文字列(「」なし)および「+」 | + | + | - |
備考
- Intでは、0、ArithmeticExceptionで除算できません。結果はIntではありません。
- 加算と乗算は連想的ですが、減算と除算はそうではありません。
- 追加セミグループIntの場合、ニュートラル要素は「0」です。
- 乗算半群Intの場合、中立要素は「1」です。
- 連結文字列では、中立要素は空の文字列( "")です。
トレイトの形式でセミグループ(またはScalaではグループイド、アソシエティビティチェックをまだコンパイル段階に渡すことはできません)を想像してください。これは、要素タイプによってパラメーター化され、メソッドとしての操作を含みます
trait Semi[T] { def |+|(x: T, y: T): T }
セミグループは不変(指数)ファンクターです。
trait Semi[T] { self => def |+|(x: T, y: T): T def xmap[R](f: T => R, g: R => T): Semi[R] = new Semi[R] { override def |+|(x: R, y: R): R = f(self |+| (g(x), g(y))) } }
連結によって文字列のセミグループを表現する
class SemiStr extends Semi[String]{ override def |+|(x: String, y: String): String = x + y }
連結による文字列の半グループから
「連結による整数の半グループ」への移行を検討し
ます object SemiDemo10 extends App { val f: String => Int = _.toInt val g: Int => String = _.toString val x: Semi[String] = new SemiStr val y: Semi[Int] = x xmap (f, g) println(y.|+|(100, 500)) } >> 100500
整数のセミグループ(Semi [Int])を作成しました。この場合、バイナリ演算は、10進数システム(100 | + | 500 => "100" | + | "500" => "100500" =>の数値の文字列表現を連結します100500)。
前の例があまりにもありふれたものである場合、次を見て
object SemiDemo16 extends App { val f: String => Int = Integer.parseInt(_, 16) val g: Int => String = Integer.toHexString val x: Semi[String] = new SemiStr val y: Semi[Int] = x xmap (f, g) println(y.|+|(10, 10)) } >> 170
整数のセミグループ(Semi [Int])を作成しました。この場合、バイナリ演算
は16進表記(10 | + | 10 => "A" | + | "A" => "AA" =>の数字の文字列表現の連結になります10 * 16 + 10 = 170)。 Intでのそのような操作がまだ結合的であることを確認できます(Intのオーバーフローと負の数のケースは無視します、申し訳ありません)。
Scalazライブラリーは、セミグループとモノイドが不変ファンクターであるとも考えています。
package scalaz object InvariantFunctor { implicit val semigroupInvariantFunctor: InvariantFunctor[Semigroup] = new InvariantFunctor[Semigroup] {...} implicit val monoidInvariantFunctor: InvariantFunctor[Monoid] = new InvariantFunctor[Monoid] {...} ... }
ライブラリーの不変ファンクター
ScalaとFPを学ぶ最良の方法は、ScalaとFPの専門家のソースコードを読むことです。
Scalazは、カテゴリ理論からの抽象化を実装するライブラリの中で最も人気があり成熟しています。 多くの点で、設計はHaskell =
scalaz.InvariantFunctorライブラリから取得され
ますCatsは、カテゴリアブストラクション(
Scalazが気に入らなかったもの-わかりません)=
cats.functor.Invariantを再実装する試み
ですPlay JSONライブラリには
不変ファンクターが含ま
れてい
ます 。これについては、
ここと
ここで説明し
ます 。