Внимание, впереди мнения!
Профукторы - это немного чрезмерная абстракция.ИМО, мы должны в первую очередь говорить о категориях ;на практике большинство профессоров являются категориями, а не наоборот.Класс profunctor может иметь правильное применение, но на самом деле он гораздо более ограничен и привязан к категории Hask .Я предпочитаю делать это явным, говоря о категориях, конструкторы стрелок которых Hask -функторы в последнем аргументе и контравариантные Hask -функторы в аргументе pænultimate.Да, это глоток, но в этом суть: на самом деле это довольно специфическая ситуация, и часто оказывается, что вам действительно нужна только менее конкретная категория.
Конкретно, Cartesian
более естественно рассматривается как класс категорий.не профункторы:
class Category k => Cartesian k where
swap :: k (a,b) (b,a)
(***) :: k a b -> k a' b' -> k (a,a') (b,b')
Что позволяет
first :: Cartesian k => k a b -> k (a,c) (b,c)
first f = f *** id
second :: Cartesian k => k a b -> k (c,a) (c,b)
second f = id *** f
, то есть категория-независимость id
.(Вы также можете определить ***
и second
в терминах first
, с second f=swap.first f.swap
и f***g=first f.second g
, но это неоправданно запутано IMO.)
Чтобы понять, почему я предпочитаю идти по этому пути,а не с профессорами, я хотел бы привести простой пример: , а не - профунктор: линейные отображения.
newtype LinearMap v w = LinearMap {
runLinearMap :: v->w -- must be linear, i.e. if v and w are finite-dimensional
-- vector spaces, the function can be written as matrix application.
}
Это не профюнктор: хотя вымог бы с этой конкретной реализацией написать dimag f g (LinearMap a) = LinearMap $ dimap f g a
, это не сохранит линейность.Это, однако, декартова категория:
instance Category LinearMap where
id = LinearMap id
LinearMap f . LinearMap g = LinearMap $ f . g
instance Cartesian LinearMap where
swap = LinearMap swap
LinearMap f *** LinearMap g = LinearMap $ f *** g
Хорошо, это выглядит довольно тривиально.Почему это интересно?Ну, линейные отображения могут быть эффективно сохранены в виде матриц, но концептуально они в первую очередь функции .Таким образом, имеет смысл обращаться с ними аналогично функциям;в этом случае .
эффективно реализует умножение матриц, а ***
собирает блочную диагональную матрицу , все в безопасном для типов виде.
Очевидно, что вы можете делать все этос неограниченными функциями, так что instance Cartesian (->)
действительно тривиально.Но я привел пример линейной карты, чтобы мотивировать, что класс Cartesian
может делать вещи, которые не являются обыденными, чтобы обходиться без него.
Star
- то, где это становится действительно интересным.
newtype Star f d c = Star{runStar :: d->f c}
instance Monad f => Category (Star f) where
id = Star pure
Star f . Star g = Star $ \x -> f =<< g x
instance Monad f => Cartesian (Star f) where
swap = Star $ pure . swap
Star f *** Star g = Star $ \(a,b) -> liftA2 (,) (f a) (g b)
Star
является предшественником категории kleisli , которая, как вы, возможно, слышали, является одним из способов использования цепочек монадических вычислений.Итак, давайте перейдем прямо к IO
примеру:
readFile' :: Star IO FilePath String
readFile' = Star readFile
writeFile' :: Star IO (FilePath,String) ()
writeFile' = Star $ uncurry writeFile
Теперь я могу сделать что-то вроде
copyTo :: Star IO (FilePath, FilePath) ()
copyTo = writeFile' . second readFile'
Зачем мне делать это таким образом?Дело в том, что я связал воедино действия ввода-вывода без использования интерфейса, который каким-либо образом может просматривать / изменять данные , которые передаются.Это может быть интересно для приложений безопасности.(Я только что составил этот пример; я уверен, что можно найти менее надуманные.)
В любом случае, я до сих пор не ответил на этот вопрос, потому что вы спрашиваете не о декартовых категориях, а о сильных профессора .Тем не менее, они предлагают почти одинаковый интерфейс:
class Profunctor p => Strong p where
first' :: p a b -> p (a, c) (b, c)
second' :: p a b -> p (c, a) (c, b)
, и, таким образом, я мог бы также внести незначительное изменение
copyTo :: Star IO (FilePath, FilePath) ()
copyTo = writeFile' . second' readFile'
, чтобы сохранить по существу тот же пример, но с Strong
вместоCartesian
.Я все еще использую композицию Category
.И я считаю, что мы не сможем создать очень сложные примеры без какой-либо композиции.
Возникает большой вопрос: почему будет использовать интерфейс profunctor вместо категориального?Каковы проблемы, которые должны быть выполнены без композиции?Ответ лежит в значительной степени в Category
экземпляре Star
: там я должен был выполнить жесткое требование Monad f
.Это не обязательно для экземпляров profunctor: они нужны только Functor f
.Так что для наиболее сфокусированных примеров Star
как сильного профессора вам нужно взглянуть на базовые функторы, которые являются , а не аппликативами / монадами.Важные области применения таких функторов в линзах Ван Лаарховена , и их внутренняя реализация действительно, вероятно, является наиболее проницательным примером для сильных профессоров.У меня постоянно кружится голова, когда я просматриваю источник библиотеки линз, но я думаю, что один весьма впечатляющий случай - Сильный индексированный .