EdKmettのrecursion-scheme
パッケージには、次の3つの宣言があります。
newtype Fix f = Fix (f (Fix f))
newtype Mu f = Mu (forall a. (f a -> a) -> a)
data Nu f where
Nu :: (a -> f a) -> a -> Nu f
これらの3つのデータ型の違いは何ですか?
Mu
は再帰型をその折り畳みとして表し、Nu
はそれを展開として表します。 Haskellでは、これらは同型であり、同じタイプを表すさまざまな方法です。 Haskellに任意の再帰がないふりをすると、これらのタイプの違いがより興味深いものになります。_Mu f
_はf
の最小(初期)不動点であり、_Nu f
_はその不動点です。最大(終端)固定小数点。
f
の不動点は、タイプT
T
と_f T
_の間の同型、つまり、逆関数_in :: f T -> T
_、_out :: T -> f T
_。型Fix
は、Haskellの組み込み型再帰を使用して、同型を直接宣言します。ただし、Mu
とNu
の両方にin/outを実装できます。
具体的な例として、再帰的な値を記述できないふりをします。 _Mu Maybe
_の住民、つまり値:: forall r. (Maybe r -> r) -> r
は、自然、{0、1、2、...}です。 _Nu Maybe
_の住民、つまり値:: exists x. (x, x -> Maybe x)
は、conaturals {0、1、2、...、∞}です。これらのタイプの可能な値について考えて、_Nu Maybe
_に余分な住民がいる理由を確認してください。
これらのタイプの直感を知りたい場合は、再帰せずに(おおまかに難易度の高い順に)以下を実装するのは楽しい演習になる可能性があります。
zeroMu :: Mu Maybe
_、_succMu :: Mu Maybe -> Mu Maybe
_zeroNu :: Nu Maybe
_、_succNu :: Nu Maybe -> Nu Maybe
_、_inftyNu :: Nu Maybe
_muTofix :: Mu f -> Fix f
_、_fixToNu :: Fix f -> Nu f
_inMu :: f (Mu f) -> Mu f
、outMu :: Mu f -> f (Mu f)
inNu :: f (Nu f) -> Nu f
、outNu :: Nu f -> f (Nu f)
これらの実装を試みることもできますが、再帰が必要です。
nuToFix :: Nu f -> Fix f
_、_fixToMu :: Fix f -> Mu f
__Mu f
_は最小不動点であり、_Nu f
_は最大であるため、関数_:: Mu f -> Nu f
_の記述は非常に簡単ですが、関数_:: Nu f -> Mu f
_の記述は困難です。それは流れに逆らって泳ぐようなものです。
(ある時点で、これらのタイプのより詳細な説明を書くつもりでしたが、この形式には少し長すぎるかもしれません。)