4

通常の関数を取り、それらを抽象値の計算に変換するアロートランスフォーマーを作成しようとしています。「ソース」矢印がある場合、

f :: Int -> Int
f x = x + 1

次に、この例では、リフトされた [sic?] 抽象値型でfを機能させることが目標になります。

f' :: AV Int -> AV Int
f' (Const x) = Const (f x)
-- pass along errors, since AV computation isn't always defined
-- or computable in the case of errors
f' (Error s) = Error s
-- avRep = "abstract representation". Think of symbolic math manipulation or ASTs.
f' (Abstract avRep) = AVRepPlus avRep (AVRepConst 1)

ただし、この矢印をうまく実装するには、いくつかの型を持ち上げて、任意の深さでいくつかの具体的な値といくつかの抽象値を持つ異種データ構造を持つようにする必要があります。私がやったことは、通常のhaskellコンストラクターに特別なタイプを追加することです。

g = uncurry (+) -- i.e. g (x, y) = x + y

次に、タプル コンストラクターである (,) の抽象表現を追加します。

AVTuple :: AV a -> AV b -> AV (a, b)

gのコードは[少し展開] に持ち上げられ、

g' (AVTuple (AVConst a) (AVConst b)) = (AVConst (g (a, b)))
g' (AVTuple (AVError e) _) = (AVError e)
-- symmetric case here, i.e. AVTuple _ (AVError e)
g' (AVTuple a@(AVTuple _ _) b) = -- recursive code here

同じことを AVEither で行う必要があります。これは多くのケースで終わるでしょう。これを回避する良い方法はありますか?

私は Haskell の初心者なので、参考文献または半詳細な説明を送ってください。おそらく私が読んだ中で最も近いのは、SYBR の論文 (ボイラープレートの革命を破棄する) のセクション 1 ~ 3 です。

本当にありがとう!

4

2 に答える 2

1

あなたがここで何を求めているのか、私が理解しているかどうか見てみましょう。AV atypeの何かを生成する計算を記述する型aがあります。その計算の構造は、検査を許可する方法で保存できます。AVすべての操作に特別なケースを作成することなく、構造を維持しながら、任意の関数を 上の操作に持ち上げる方法が必要です。

通常、関数を何らかの構造に持ち上げるには、 and を使用FunctorApplicativeます。ただし、これを行う簡単な方法は、構造の一部として関数の適用を保持するのではなく、構造を変換してリフトされた関数を直接適用することです。

あなたが望むのはもっとぎこちないことです。その理由は次のとおりです。

持ち上げたい関数があり、それを適用する適切な型の 2 つの抽象値があるとします。

x :: AV A
x = ...

y :: AV B
y = ...

f :: A -> B -> C
f = ...

あなたが望むことをする関数が存在するliftAV2とします。の型はforlift2 fAV A -> AV B -> AV C同じようにであると予想されます。liftAApplicative

後で、 、、およびlift2 fの値を復元することにより、を使用して生成された計算を検査したいと考えています。今のところ、最初の引数を抽出したいだけだとしましょう。=のように、これを行う関数が存在するとします。の型は何ですか? ここで、contextでは、 type を持つ必要があることがわかっています。しかし、それは一般的にどのようなタイプを持っているでしょうか?みたいな?結果は単なるany typeではなく、値を構築するために使用された型であるため、それは間違っています。操作している値が の結果を使用して構築されたと仮定すると、問題の型が存在することがわかります。fxyextractArg1extractArg1 (liftAV2 f x y)xextractArg1AV C -> AV AAV c -> AV aaAV cliftAV2 f、しかし、一般的にそれを見つける方法はありません。

これは、適切に、実存的なタイプの土地に入る場所です。正直にそれらを使用してください。よくあるケースのように型クラスで誤用するだけではありません。

多少の努力で目的を達成できるかもしれませんが、これはかなり高度な領域に入っています。初心者にはGADTを使用したいと思うでしょうが、すでにそうしているかもしれません。また、限られたコンテキストでそれらが何であるかを知ることしかできないため、存在型を扱うのは非常に不器用になる傾向があります。

AV 特定のケースでは、 2 つの型パラメーターを指定する方が簡単な場合があります。1 つは計算の最終型を表し、もう 1 つは計算の構造を表します。

data f :$ x = ...

data AV structure result where
    ...
    AVApply :: AV f (a -> b) -> AV x a -> AV (f :$ x) b

次に、計算を検査するために、最初のタイプを見て、何があるかを知ることができます。計算を構築するために、2番目を見て、型が一致することを確認できます。評価関数は のような型にAV t a -> aなり、構造を捨てます。また、構造型を使用して計算を「アンパック」し、結果の型を破棄することもできます。たとえば、きれいに印刷するために構造を分解する必要がある場合です。

于 2011-05-25T18:30:53.087 に答える
0

私がそれについて考えるのが好きな方法は、Functorいくつかの「少し余分なデータ」について話したいときにインスタンスを使用することです (「少し余分な」が何であるかによって、実際にはまたはについて話している可能性がありますApplicative) Monad

一方、私はArrowインスタンスを使用して「少し少ない関数」について説明します。矢印を使用すると、関数と同じ方法で一緒に構成できるものを定義できますが、特定の構成を禁止するために余分な構造または制限を追加することができます (たとえば、ArrowChoiceまたはのない矢印ArrowLoop)。

達成したいことは完全には明らかではありませんが、実際にはデータをAV型コンストラクターでラップしているように思えます。その場合、おそらくAVのインスタンスを作成し、 のインスタンスFunctorを追加し、同様にラップアラウンドのFunctorインスタンスを追加することをお勧めします。(AV a, AV b) => AV (a, b)AVEither

于 2011-05-25T16:42:51.350 に答える