抽象化を行うときには、通常、抽象化が表すはずのものである「意味論的ドメイン」と呼ばれるものがあります。抽象化のプロパティは、セマンティックドメインのプロパティと一致する必要があります。 (抽象化が型クラスを持つ場合、これはtype class morphismと呼ばれています)。
GMap
は明らかに何らかのマッピング操作を表現しようとしています。プロトタイプのマッピングの例は関数です。 Data.Map
のような有限の地図もありますが、特殊な種類の関数であるふりをしています。
とにかく、GMap a b
は、機能a -> b
と同様のプロパティを持つと予想されます。 GMap (a,b) v
が(GMap a v, GMap b v)
に等しいと定義されている場合、セマンティックドメインに対応する同型異性が存在することを期待する必要があります。だから、関数矢印->
にすべてのGMap
の変換、我々が得る:
f' :: ((a,b) -> v) -> (a -> v, b -> v)
g' :: (a -> v, b -> v) -> ((a,b) -> v)
g'
はです。TypeCheckするために取得するのは簡単ですが、二つの異なる実装といずれかを選択する方法があります
g' :: (a -> v, b -> v) -> ((a,b) -> v)
g' = (tl, tr) (x,y) = tl x
-- and
g' = (tl, tr) (x,y) = tr y
とf'
我々はを持って、左???
で
f' :: ((a,b) -> v) -> (a -> v, b -> v)
f' t = (\a -> ??? , \b -> ???)
実に不可能ですがあり、v
が必要ですが、t
にa
とb
の両方を指定すると、v
しか作成できません。また、b
が必要です。タプルの右のコンポーネントでも同じことが起こります。
ペアの関数(a,b) -> v
と関数のペア間を行き来する明確な方法はありません。したがって、これら2つのものをGMap
と等しいと宣言するのは間違っているようです。同じことがData.Map
のような有限マップで起こります(タイプチェックに何かを得ることができますが、それはf' . g' /= id
(またはその逆、私はどちらが覚えていないか)であるため真の同型写像にならないでしょう)。
(Either a b -> v) -> (a -> v, b -> v)
から同型はこの意味領域のものが堅実プログラマのためのビット抽象的なことができ
f :: (Either a b -> v) -> (a -> v, b -> v)
f t = (t . Left, t . Right)
g :: (a -> v, b -> v) -> (Either a b -> v)
g (l, r) (Left x) = l x
g (l, r) (Right y) = r y
を書きやすいですに対し。この同形性を書くことができればどうして重要なのでしょうか?しかし、GMap
が何かをしようとすると、実用的な方法ですぐに問題が発生することがわかります。
のは、データの家族に非常に単純な操作のカップルを束ねる始めましょう、私たちは書くことができるようにするべきである:
class MapKey k where
data family GMap k :: * -> *
empty :: GMap k v
lookup :: GMap k v -> k -> Maybe v
insert :: k -> v -> GMap k v -> GMap k v
、非常にシンプルなベースケースを我々がしようとした場合
instance MapKey Int where
data GMap Int v = GMapInt (Int -> Maybe v)
empty = GMapInt (const Nothing)
lookup (GMapInt f) x = f x
insert x v (GMapInt f) = GMapInt (\y -> if x == y then Just v else f y)
で動作するように
instance (MapKey a, MapKey b) => MapKey (a,b) where
data GMap (a,b) v = GMapTuple (GMap a v) (GMap b v)
empty = GMapTuple empty empty
lookup (GMapTuple l r) (x,y) =
-- several implementations here, but maybe we could do
lookup l x `mplus` lookup r y
insert (x,y) v (GMapTuple l r) = GMapTuple (insert x v l) (insert y v r)
妥当と思われますが、動作しません。
我々は
(1,2)
、ない
(1,3)
を挿入するので
Nothing
を与えているはずです
>>> lookup (insert (1 :: Int, 2 :: Int) "value" empty) (1,3)
Just "value"
。私の実装では単なるバグだと言えるかもしれませんが、私はあなたに挑戦してください。
タイプの変換方法については、タイプと代数の間に美しい対応があります。ここで~~
は「に類似した」を意味:
Either a b ~~ a + b
(a,b) ~~ a * b
a -> b ~~ b^a
あるので
Map ((a,b) -> v) ~~ v^(a*b)
= (v^a)^b
~~ Map b (Map a v)
、我々はタプルと、ネストされたマップからのマップ間の同型を期待するべきです。同様に:
Map (Either a b -> v) ~~ v^(a+b)
= v^a * v^b
~~ (Map v a, Map v b)
そして副産物(Either
)とマップのペアから、マップ間の素敵な同型があるはずです。
これは魅力的なほど深くなり、他のものと同型であるもので遊ぶ価値があります。
さらに読書:
"私はGMapEitherは何とか左または右のいずれかの変異体を提供するために期待" - これがあなたの誤解の基礎です。 'GMap k v'は' k'を提供しません - 'v'を提供するために' k'を消費します。あなたは 'GMap(どちらかk0 k1)v'型の' 'どちらかk0 k1'型の値を得、' v'型の値を得ます。これは 'lookup'です。 (別として、「これはもっと直感的になるかもしれないと思う」 - これはあなたの2番目の致命的な間違いです。プログラムは直感的に正しいものに基づいて書かれているのではなく、むしろ正しい正式な意味に基づいて書かれています) – user2407038