問題は、修正されたFunctor
が意味するものが明確ではないということです。たとえば、ByteString
と考えてください。 ByteString
は、Word8
の各要素を同じタイプののに置き換えることによってのみマップすることができます。しかし、Functor
はのパラメトリックのためのものです。実際にここにマッピングするという2つの相反する概念があります。
- リジッドマッピング
- パラメトリックマッピング(すなわち任意の型に構造体の各要素を変換)
だから)その型を変更することなく、構造体の各要素を変換する、この場合には、あなたはどのような種類のシステムに説明することはできませんあなたが意味するのは、あまり意味がないからです。ただし、
リジッドマッピングはタイプの家族と表現するのは簡単です:)あなたは何を意味するか変更することができます。
class RigidMap f where
type Element f :: *
rigidMap :: (Element f -> Element f) -> f -> f
限りパラメトリックマッピングとして、それを行うには複数の方法があります。最も簡単な方法は、現在のFunctor
をそのまま使用することです。一緒に、これらのクラスはByteString
、[]
、Seq
のような構造をカバーします。ただし、値がOrd
であるため、すべてがSet
およびMap
になります。幸いなことに、GHC 7.4に来てconstraint kinds拡張子は私たちがこの問題を解決することができます:ここで
class RFunctor f where
type Element f a :: Constraint
type Element f a =() -- default empty constraint
fmap :: (Element f a, Element f b) => (a -> b) -> f a -> f b
、我々はすべてのインスタンスが関連付けられている型クラス制約を持つべきであると言っています。たとえば、インスタンスがタイプに使用可能な場合にのみ、を構築できることを示すために、SetインスタンスにはElement Set a = Ord a
が含まれます。 =>
の左手に表示されるものはすべて許可されます。私たちは、彼らがいたとおりに、当社の以前のインスタンスを定義することができますが、我々はまた、Set
とMap
を行うことができます。
instance RFunctor Set where
type Element Set a = Ord a
fmap = Set.map
instance RFunctor Map where
type Element Map a = Ord a
fmap = Map.map
しかし、それは剛性のマッピングおよび制限付きパラメトリックマッピングするための2つの別々のインタフェースを使用するために持っているかなり迷惑なんです。実際、後者は前者の一般化ではないのですか? Ord
とByteString
のインスタンスのみを含むことができるSet
の違いを考えてみましょう。Word8
を含むことができます。確かにそれをもう一つの制約として表現できますか?
class Mappable f where
type Element f :: *
type Result f a r :: Constraint
map :: (Result f a r) => (Element f -> a) -> f -> r
ここでの考え方:私たちは、同じトリック(すなわち全体の構造のためのインスタンスを与え、要素の型を公開するタイプのファミリを使用)HasFirst
に行われ、新しい関連する制約の家族を紹介適用
Result f a r
は値タイプ(たとえばOrd a
)に必要な制約を表し、でもは必要なコンテナタイプを制約します。おそらく、それがa
の同じ種類のコンテナのタイプを有することを保証するためである。例えば、Result [a] b r
は、おそらくr
が[b]
であり、Result ByteString b r
がb
であり、Word8
であり、r
がByteString
であることを必要とすると考えられます。
タイプファミリーは、ここでは「タイプ」と表現する必要があるタイプを提供しています。 (a ~ b) => ...
と言うと、a
とb
は同じタイプです。もちろん、制約ファミリ定義でこれを使用することもできます。だから、私たちは必要なものすべてを持っています。インスタンス:
instance Mappable [a] where
type Element [a] = a
type Result [a] b r = r ~ [b]
-- The type in this case works out as:
-- map :: (r ~ [b]) => (a -> b) -> [a] -> r
-- which simplifies to:
-- map :: (a -> b) -> [a] -> [b]
map = Prelude.map
instance Mappable ByteString where
type Element ByteString = Word8
type Result ByteString a r = (a ~ Word8, r ~ ByteString)
-- The type is:
-- map :: (b ~ Word8, r ~ ByteString) => (Word8 -> b) -> ByteString -> r
-- which simplifies to:
-- map :: (Word8 -> Word8) -> ByteString -> ByteString
map = ByteString.map
instance (Ord a) => Mappable (Set a) where
type Element (Set a) = a
type Result (Set a) b r = (Ord b, r ~ Set b)
-- The type is:
-- map :: (Ord a, Ord b, r ~ Set b) => (a -> b) -> Set a -> r
-- (note the (Ord a) constraint from the instance head)
-- which simplifies to:
-- map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
map = Set.map
パーフェクト!我々は、剛性、パラメトリック、またはパラメトリックだが制限された任意のタイプのコンテナのインスタンスを定義することができ、タイプは完全に機能する。
免責事項:私はまだGHC 7.4を試していないので、これが実際にコンパイルされているかどうかわかりませんが、基本的な考え方は健全だと思います。
ここで何が起こっているのかを正確に把握するのにまだ苦労しています...論理的には単純です。私たちは、どんな正当な入力型も受け入れ、法的な出力型を生成するマップ関数が必要です。トリッキーな部分は、特定のコンテナに対してどの型が「合法」であるかを定義することです。 (私はこの完全な「剛性」対「パラメトリック」の区別にはなっていません)誰もすべての「〜」文字が何を意味するのか説明できますか?または「制約」とは何ですか? – MathematicalOrchid
私は、うまくいけば良いことを説明するために私の答えを広げました。また、私がリンクしているブログ記事を読んで、 'Constraint'のより詳しい説明をすることをお勧めします。 – ehird
なので、 "*"は通常の型です。kind "* - > *"は任意の型コンストラクタです。kind "Constraint"は型ではありません。型制約ですか?そうですか? – MathematicalOrchid