ScalaのFP:Invariant Functor

記事は考慮します



公開はScalaでのFPの拡張です。ファンクターとは何ですか? 次の問題に対処しました



内容





Scala、数学、関数型プログラミングの世界をさらに詳しく知りたい場合は、 Scala for Java Developersオンラインコースお試しください(ビデオ+テスト、価格のわずか25%!リンククーポンの数は限られています!)。





はじめに



前回の記事の要点を思い出させてください



それでも、著者は前の記事 読むことを強くお勧めします





不変ファンクターとは



不変ファンクターは、共変および反変の「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]は、 「入力」と「出力」でデータを「待機」する相互に逆の変換fgのペアを使用して、元の不変式[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 => Rg1:R => Tf2:R => Qg2: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コンテナがあります。



さて、なぜ最も単純なバリューホルダーが必要なのですか? いくつかの方向で機能を増やすことができます



注:不変ファンクターのルールは、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および '/' - - -
文字列と「+」 + + +
文字列(「」なし)および「+」 + + -


備考

  1. Intでは、0、ArithmeticExceptionで除算できません。結果はIntではありません。
  2. 加算と乗算は連想的ですが、減算と除算はそうで​​はありません。
  3. 追加セミグループIntの場合、ニュートラル要素は「0」です。
  4. 乗算半群Intの場合、中立要素は「1」です。
  5. 連結文字列では、中立要素は空の文字列( "")です。




トレイトの形式でセミグループ(または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 { /** Semigroup is an invariant functor. */ implicit val semigroupInvariantFunctor: InvariantFunctor[Semigroup] = new InvariantFunctor[Semigroup] {...} /** Monoid is an invariant functor. */ implicit val monoidInvariantFunctor: InvariantFunctor[Monoid] = new InvariantFunctor[Monoid] {...} ... }
      
      









ライブラリーの不変ファンクター



ScalaとFPを学ぶ最良の方法は、ScalaとFPの専門家のソースコードを読むことです。



Scalazは、カテゴリ理論からの抽象化を実装するライブラリの中で最も人気があり成熟しています。 多くの点で、設計はHaskell = scalaz.InvariantFunctorライブラリから取得されます



Catsは、カテゴリアブストラクション( Scalazが気に入らなかったもの-わかりません)= cats.functor.Invariantを再実装する試みです



Play JSONライブラリには不変ファンクターが含まています 。これについては、 ここここで説明します



All Articles