[kotlinfp]エンベロープ(Functor)


코틀린으로 배우는 함수형 프로그래밍を読んでまとめた内容이번 포스트부터는 난이도가 좀 상승한다.

ポントラン


関数型プログラミングはカテゴリ論の数学原理に基づいて構築され,カテゴリ論には様々な数学概念が存在する.また,関数型言語では,証明された概念の実装体も提供される.
各インプリメンテーションは、ある動作を定義したタイプクラスのインスタンスからなり、その用途と使用方法が異なる.ここで、タイプクラスはcortlinでインタフェースとして実装される.

だからポントは?


パント(Functor)とは、マッピング可能(マッピング可能)な動作を宣言するタイプのクラスである.ここで、매핑할 수 있는 것という動作は、リスト内の地図と同じである.
朋特について、写真で説明した文章があり、先に共有してから投稿を続けた.
コトリンリストのmap関数のタイプは以下の通りです.
fun <T, R> Iterable<T>.map(f: (T) -> R): List<R>
map関数は、オブジェクトが持つTの値をf関数に適用してRタイプの値を求め、その値をListオブジェクトに再配置するList<R>を返す.
ここで、IterableおよびListは、それぞれTタイプおよびRタイプの박스を含むと考えられる.mapの行為を箱の中のものを取り出して変換する過程にたとえて、以下のようにします.△画像は以前共有した文章から切り取ったものです.
「朋特」とは、컨테이너型の値をリストから取り出し、入力した関数を適用した後、関数の結果値を「container型」型で返される행위型のクラスに入れることを意味する.
タイプクラスYes
A typeclass is a sort of interface that defines some behavior.
ポンド自体は抽象的なタイプクラスであるため、List<Int>のようなコンテナタイプを含む特定のタイプは定義されない.パントはList<T>と同様に一般化されたタイプを有する.
したがって、タイプパラメータを受け入れるタイプコンストラクション関数(type constructor).타입 매개변수타입 생성자などの用語に慣れていない場合は、次の位置付けを参照してください.
ダンテ関数型システム

朋特を宣言する


朋特を自分で作って使います.ポントは地図演算を抽象化した.
interface Functor<out A> {
  fun <B> fmap(f: (A) -> B): Functor<B>
}
fmap関数では、Functor<B>タイプを返すとoverrideクラスからコンテナタイプに変換されます.
fmap関数は、入力されたf関数を使用してA値をBに変換し、エンベロープにロードしてFunctor<B>に戻る.

Mavi朋特を作る


Maybeは、値があってもなくてもよいコンテナタイプです.
関数の戻り値をmabyとして宣言すると、関数が失敗する可能性があり、不要なifelseによる空処理や例外を回避できます.
コートリンであなたを扱うことができるので、メビのようなタイプはサポートされていないので、直接以下のように実現しました.
sealed class Maybe<out A>: Functor<A> {
  abstract override fun toString(): String
  
  abstract override fun <B> fmap(f: (A) -> B): Maybe<B>
}
Maybeを朋特にするためにFunctorタイプのクラスインスタンスとして宣言します.ここで、インスタンスとして宣言することは、オブジェクト向けプログラミングの継承を意味し、関数式プログラミングでタイプクラスを処理するために使用される用語である.
fmap関数は、朋特に定義されたfmap関数と同じですが、戻り値のタイプのみをMaybeに変換します.
Maybeの値付き実装クラスと値なし実装クラスを作成します.

Just

data class Just<out A>(val value: A): Maybe<A>() {
  override fun toString(): String = "Just($value)"
  override fun <B> fmap(f: (A) -> B): Maybe<B> = Just(f(value))
}
Justには値が含まれている必要があります.したがって、ジェネレータから含まれる値を入力します.fmap関数は、Justが持つ値を関数fに適用し、コンテナタイプJustに戻す.

Nothing

object Nothing: Maybe<kotlin.Nothing>() {
  override fun toString(): String = "Nothing"
  
  override fun <B> fmap(f: (kotlin.Nothing) -> B): Maybe<B> = Nothing
}
Nothingは無価な状態です.値は含まれません.したがって、作成者からの入力は受け入れられません.fmapを呼び出しても値は存在しないので、そのままNothingに戻ります.
MaviとListはいずれもある値または空のコンテナタイプを含む.朋特はタイプ作成者にコンテナタイプを採用することを要求している.したがって、値のタイプを[エンベロープ](Envelope)に設定することも考えられます.

ツリーの作成


バイナリツリーは、任意の値を含むコンテナ型ツリーです.そのため、蓬頭を作ることも考えられます.
ツリーの要件は次のとおりです.
  • ツリーは空です.または、値と2つのサブツリーがあります.
  • の新しいツリーを作成できます.(treeOf)
  • ツリーは画面に出力できます.(toString)
  • ツリー内のすべてのノードの値を変換関数に適したツリーに作成できます.(fmap)
  • sealed class Tree<out A>: Functor<A> {
      abstract override fun toString(): String
      
      abstract override fun fmap(f: (A) -> B): Tree<B>
    }
    1つ目の要件に従って、ツリーはNullまたは値と2つのサブツリーを持つ必要があります.そのため、Nullツリーの値ジェネレータを作成します.
    object Empty Tree: Tree<kotlin.Nothing>() {
      override fun toString(0: String = "E"
      
      override fun <B> fmap(f: (Nothing) -> B): Tree<B> = EmptyTree 
    }
    次に、ある値と2つのサブツリーを持つノード値ジェネレータを実装します.
    data class Node,out A>(val value: A, val leftTree: Tree<A>, val rightTree: Tree<A>): Tree<A>() {
      override fun toString(0: String = "(N $value $leftTree $rightTree)"
      
      override fun <B> fmap(f: (A) -> B): Tree<B> = Node(f(value), leftTree.fmap(f), rightTree.fmap(f))
    }

    Ethernetインタフェースの作成


    MaviとTreeにはタイプジェネレータパラメータが1つしかありません.
    タイプパラメータが2つより大きいタイプをブレークスルーのインスタンスにするには、どうすればいいですか?
    イーサはRAFTとLightタイプのみを許可する代数タイプです.エーテルパスは、関数の戻り値としてよく使用されます.関数呼び出しに成功した場合は、正しい結果が光源に書き込まれ、失敗の原因の情報がドラフトとしてマークされます.
    したがって、ラスタと光源に含まれる値のタイプが異なる場合があります.イーサの光源値を変更し、値を変更したイーサを取得できます.
    MaviまたはTreeとは異なり、イーサには2つのタイプのパラメータがあります.이더는 일반적으로 라이트의 값만 변경할 수 있다.ドラフトの値は、生成時に固定されます.
    sealed class Either<out L, out R>: Functor<R> {
      abstract override fun <R2> fmap(f: (R) -> R2): Either<L, R2>
    }
    ドラフトと光源値の作成者を実装します.
    data class Left<out L>(val value: L): Either<L, Nothing>() {
      override fun <R2> fmap(f: (Nothing) -> R2): Either<L, R2> = this
    }
    
    data class Right<out R>(val value: R): Either<Nothing, R>() {
      override fun <R2> fmap(f: (R) -> R2): Either<Nothing, R2> = Right(f(value))
    }

    単項関数ブレークポイントの作成


    関数言語では、関数は1レベルのオブジェクトとして扱われるため、関数ブレークポイントを作成しようとします.
    Funtorタイプクラスのタイプジェネレータには1つのパラメータしかありません.ただし、関数に複数のパラメータがある場合、関数のタイプには1つ以上のタイプのパラメータがあります.そのため、変更可能なタイプを除いて、他のタイプは変わらない必要があります.
    ここで、制限パラメータは、단항 함수のヘッダを作成します.
    data class UnaryFunction<in T, out R>(val g: (T) -> R) {
      override fun <R2> fmap(f: (R) -> R2): UnaryFunction<T, R2> {
        return UnaryFunction { x: T -> f(g(x)) }
      }
      
      fun invoke(input: T): R = g(input)
    }
    様々なタイプを持つ必要はないので、sealed classとして作成する必要はありません.そのため、dataclassとして作成します.関数のタイプパラメータは、入力Tと出力Rである.関数の入力を固定するために、出力のみをマッピングし、朋特タイプをFuntor<R>と宣言します.
    fun main() {
      val f = { a: Int -> a + 1 }
      val g = { b: Int -> b * 2 }
      
      val fg = UnaryFunction(g).fmap(f)
      println(fg.invoke(5))
    }
    fを関数gに適用するマッピングは、関数原理と同様であり、関数合成を用いて入力パラメータが複数の関数を作成することもできる.UnaryFunctionをフィルタリングするため、最終的には複数の入力関数と同じになります.
    ((T)->R)->R 2パラメータを1つの関数のフィルタ(T)->R(R)->R 2に変更できます.これはミリングの原理と同じで、これらの原理を使用して値を囲むエンベロープ(コンテキスト)を変更できます.
    fun main() {
      val g = { a: Int -> a * 2 }
      val k = { b: Int -> Just(b) }
      val kg = UnaryFunction(g).fmap(k)
      println(kg.invoke(5)) // "Just(10)"
    }
    UnaryFunctionは1つの数字を受け取ってMaybeに戻り,関数kに従ってTreeまたはEtherに戻ることができる.これらの関数は一般的に昇格関数と呼ばれます.

    パントの法則


    ポンテの実例になるには,二つの法則を満たさなければならない.これを朋特の法則と呼ぶ.

    パントの第一の法則

    fmap(identity()) === identity()
    Shedoコードで書いてみましょう.
    fmap id  ==  id
    fmapを呼び出す場合、一定関数idを入力とする結果は、一定関数を呼び出す結果と同じでなければならない.ここで、恒等関数とは、{ x -> x }のように、入力に加工がなく直接戻る関数を指す.
    The first functor law states that if we map the id function over a functor, the functor that we get back should be the same as the original functor.
  • テキスト
  • まず、恒等関数は次のとおりです.
    fun <T> identity(x: T): T = x
    Maybe,Ether,テスト朋特1の法則は以下の通りである.
    fun main() {
      println(Nothing.fmap { identity(it) } == identity(Nothing)) // true
      println(Just.fmap { identity(it) } == identity(it)) // true
    }
    各朋特インスタンスのfmap関数にidentityを入力呼び出しとして追加した結果は、identity関数に朋特インスタンス呼び出しを追加した結果と同じである.

    パントの第二の法則


    関数f,gは時
    fmap(f compose g) == fmap(f) compose fmap(g)
    関数fとgは、まず含まなければならず、fmap関数の入力として、得られた結果値は、関数fがfmapに加算された関数とgがfmapに加算された関数と合成された結果と同じでなければならない.
    infix fun <F, G, R> ((F) -> R).compose(g: (G) -> F): (G) -> R {
      return { gInput: G -> this(g(gInput)) }
    }
    fun main() {
      val f = { a: Int -> a + 1 }
      val g = { b: Int -> b * 2 }
      
      val nothingLeft = Nothing.fmap(f compose g)
      val nothingRight = Nothing.fmap(g).fmap(f)
      
      val justLeft = Just(5).fmap(f compose g)
      val jusetRight = Just(5).fmap(g).fmap(f)
    }

    の最後の部分


    今日は彭特に聞いてみた.次の記事では、アプリケーションのクラッシュについて説明します.