型ファミリを使用して、関数と定数値のnタプルとして表される型とその型の基礎となる代数の関数折り畳みを定義できます。これにより、Foldable型クラスで定義される一般化されたfoldr関数の定義が可能になります。
import Data.Set (Set)
import Data.Map (Map)
import qualified Data.Set as S
import qualified Data.Map as M
class Foldable m where
type Algebra m b :: *
fold :: Algebra m b -> m -> b
instance (Ord a) => Foldable (Set a) where
type Algebra (Set a) b = (b, a -> b -> b)
fold = uncurry $ flip S.fold
instance (Ord k) => Foldable (Map k a) where
type Algebra (Map k a) b = (b, k -> a -> b -> b)
fold = uncurry $ flip M.foldWithKey
同様に、制約の種類によって、一般化されたマップ関数を定義できます。map関数は、代数データ型の各値フィールドを考慮する点でfmapとは異なります。
class Mappable m where
type Contains m :: *
type Mapped m r b :: Constraint
map :: (Mapped m r b) => (Contains m -> b) -> m -> r
instance (Ord a) => Mappable (Set a) where
type Contains (Set a) = a
type Mapped (Set a) r b = (Ord b, r ~ Set b)
map = S.map
instance (Ord k) => Mappable (Map k a) where
type Contains (Map k a) = (k, a)
type Mapped (Map k a) r b = (Ord k, r ~ Map k b)
map = M.mapWithKey . curry
ユーザーの観点からは、どちらの機能も特に使いやすいものではありません。特に、どちらの手法もカリー化された関数の定義を許可しません。これは、ユーザーがフォールドまたはマップされた関数を部分的に簡単に適用できないことを意味します。私が望むのは、上記のカリー化されたバージョンを生成するために、関数と値のタプルをカリー化する型レベルの関数です。したがって、次の型関数に近いものを書きたいと思います。
Curry :: Product -> Type -> Type
Curry () m = m
Curry (a × as) m = a -> (Curry as m b)
もしそうなら、基になる代数からカリー化された折り畳み関数を生成できます。例えば:
fold :: Curry (Algebra [a] b) ([a] -> b)
≡ fold :: Curry (b, a -> b -> b) ([a] -> b)
≡ fold :: b -> (Curry (a -> b -> b)) ([a] -> b)
≡ fold :: b -> (a -> b -> b -> (Curry () ([a] -> b))
≡ fold :: b -> ((a -> b -> b) -> ([a] -> b))
map :: (Mapped (Map k a) r b) => (Curry (Contains (Map k a)) b) -> Map k a -> r
≡ map :: (Mapped (Map k a) r b) => (Curry (k, a) b) -> Map k a -> r
≡ map :: (Mapped (Map k a) r b) => (k -> (Curry (a) b) -> Map k a -> r
≡ map :: (Mapped (Map k a) r b) => (k -> (a -> Curry () b)) -> Map k a -> r
≡ map :: (Mapped (Map k a) r b) => (k -> (a -> b)) -> Map k a -> r
Haskell には型関数がないことを私は知っています。nタプルの適切な表現は、おそらく型レベルの長さインデックス付きの型のリストのようなものでしょう。これは可能ですか?
編集:完全を期すために、解決策に対する私の現在の試みを以下に添付します。上記の型の積を表すために空のデータ型を使用し、関数Curryを表すために型ファミリを使用しています。このソリューションはmap関数では機能するように見えますが、fold関数では機能しません。型チェック時にCurryが適切に削減されていないことは確かではありませんが、私は信じています。
data Unit
data Times a b
type family Curry a m :: *
type instance Curry Unit m = m
type instance Curry (Times a l) m = a -> Curry l m
class Foldable m where
type Algebra m b :: *
fold :: Curry (Algebra m b) (m -> b)
instance (Ord a) => Foldable (Set a) where
type Algebra (Set a) b = Times (a -> b -> b) (Times b Unit)
fold = S.fold
instance (Ord k) => Foldable (Map k a) where
type Algebra (Map k a) b = Times (k -> a -> b -> b) (Times b Unit)
fold = M.foldWithKey
class Mappable m where
type Contains m :: *
type Mapped m r b :: Constraint
map :: (Mapped m r b) => Curry (Contains m) b -> m -> r
instance (Ord a) => Mappable (Set a) where
type Contains (Set a) = Times a Unit
type Mapped (Set a) r b = (Ord b, r ~ Set b)
map = S.map
instance (Ord k) => Mappable (Map k a) where
type Contains (Map k a) = Times k (Times a Unit)
type Mapped (Map k a) r b = (Ord k, r ~ Map k b)
map = M.mapWithKey