Используя семейства типов, мы можем определить функцию fold над типом и лежащую в основе алгебру для этого типа, представленную как n-набор функций и постоянных значений. Это позволяет определить обобщенную функцию foldr, определенную в классе Foldable type:
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
Аналогично, виды ограничений допускают определение обобщенной функции отображения. Функция карты отличается от 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
С точки зрения пользователя ни одна из функций не является особенно дружественной. В частности, ни один из методов не позволяет определять карри-функции. Это означает, что пользователь не может легко применить либо fold, либо отображаемую функцию частично. То, что я хотел бы, это функция уровня типа, которая представляет собой кортежи функций и значений, для того, чтобы генерировать версии с карьерами выше. Таким образом, я хотел бы написать что-то, приближающееся к следующей функции типа:
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-кортежа, вероятно, будет чем-то вроде типа типов с индексом типа. Возможно ли это?
EDIT: Для полноты моей текущей попытки решения прилагается ниже. Я использую пустые типы данных для представления продуктов типов и типа семейств, чтобы представить функцию Curry выше. Это решение, похоже, работает для функции отображения, но не для функции сгиба. Я верю, но не уверен, что 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