В чем разница между '.' и "<<<" при выполнении композиции функций?

Я пытаюсь выполнить композицию функций в Haskell, и я не уверен, какой оператор является правильным для использования.

Документы содержат эти два типа подписей:

(.) :: (b -> c) -> (a -> b) -> a -> c 
(<<<) :: Category cat => cat b c -> cat a b -> cat a c 

Очевидно, что разница между этими двумя вариантами заключается в наличии / отсутствии Category cat, но что означает эта аннотация, и как я должен использовать информацию, чтобы выбрать одного оператора над другим?

Я также заметил третий вариант вышеупомянутых двух сигнатур при сравнении двух других операторов:

(>>) :: forall a b. m a -> m b -> m b 
(>>>) :: Category cat => cat a b -> cat b c -> cat a c

Что это forall среднее значение аннотации >> для использования в третьем сценарии?

2 ответа

Во-первых, вы должны признать, что (.) определяется в Prelude в функциональной форме

(.) :: (b -> c) -> (a -> b) -> a -> c 

а также более общая функция, предоставляемая Category учебный класс:

class Category cat where
    -- | the identity morphism
    id :: cat a a

    -- | morphism composition
    (.) :: cat b c -> cat a b -> cat a c

(->) экземпляр Category проясняет, что два одинаковы для функций:

instance Category (->) where
    id = GHC.Base.id
    (.) = (GHC.Base..)

Определение (<<<) дает понять, что это просто синоним (.)

-- | Right-to-left composition
(<<<) :: Category cat => cat b c -> cat a b -> cat a c
(<<<) = (.)

предназначен для симметрии с (>>>) оператор. Вы можете написать либо f >>> g или же g <<< fв зависимости от того, что имеет больше смысла для вашего конкретного использования.


(>>) это совершенно другой оператор (по крайней мере, до тех пор, пока вы не слишком углубляетесь в теорию монад). (>>) это версия (>>=) который игнорирует результат первого операнда, используя его только для эффекта.

x >> y == x >>= (\_ -> y)

Чисто синтаксическое различие, которое, несмотря на поверхностность, на самом деле может быть наиболее распространенным вариантом использования, заключается в том, что <<< имеет более низкий приоритет, чем .:

infixr 9 Control.Category..
infixr 1 Control.Category.<<<

Это похоже на разницу между простым применением функции f x (который связывает крепче, чем любой инфикс, так что это в основном infixl 10) и использование $ оператор как f $ x, который имеет самый низкий приоритет infixr 0 $, Это означает, что вы можете выбрать, для какого из них требуется меньше скобок в выражении. <<< удобен, когда вы хотите составить функции, которые сами определены неким выражением инфикса; это часто случается при работе с линзами.

Более теоретически интересно то, что Category Версия работает не только с функциями, но и с морфизмами из других, категорий. Простым примером является категория принуждений: если у вас есть, например, список newtype -обернутые значения, и вы хотите получить базовые представления, было бы неэффективно map поверх списка - это создаст копию всего списка, который, однако, содержит ту же самую информацию времени выполнения. Принуждение позволяет вам использовать исходный список все время, но без обхода системы типов - компилятор будет в каждой точке отслеживать, в каком "представлении" списка элементы имеют какой тип. Принуждения на самом деле не являются функциями - они всегда запрещены во время выполнения - но они могут быть составлены как функции (например, Product Int в Int, а затем принудительно Int в Sum Int).

Для других примеров Haskellers обычно ссылаются на Kleisli категории. Они содержат функции вида a -> m b, где m это монада Хотя вы не можете напрямую сочинять, например, readFile :: FilePath -> IO String с firstFileInDirectory :: FilePath -> IO FilePath потому что есть несоответствие между FilePath а также IO FilePath Клейсли вы можете составить их:

import Control.Monad
main = writeFile "firstfileContents.txt" <=< readFile <=< firstFileInDirectory
            $ "src-directory/"

и то же самое можно написать

import Control.Arrow
main = runKleisli ( Kleisli (writeFile "firstfileContents.txt")
                 <<< Kleisli readFile
                 <<< Kleisli firstFileInDirectory
            ) $ "src-directory/"

Какой смысл? Ну, это позволяет вам абстрагироваться от различных категорий и, таким образом, иметь код, который будет работать как с чистыми функциями, так и с IO функции. Но честно говоря я думаю Kleisli плохо справляется с мотивацией использования других категорий: все, что вы можете написать с помощью стрелок Клейсли, обычно более читабельно при написании со стандартным монадическим do нотации, или просто с =<< или же <=< операторы. Это по- прежнему позволяет абстрагироваться от вычислений, которые могут быть чистыми или нечистыми, просто выбирая разные монады (IO, ST или просто Identity).
По-видимому, есть некоторые специализированные парсеры, которые Arrows но не могут быть написаны как монады, но они действительно не завоевали популярность - кажется, преимущества не уравновешивают менее интуитивный стиль.

В математике есть еще много интересных категорий, но, к сожалению, они, как правило, не могут быть выражены как Category Потому что не каждый тип Haskell может быть объектом. Пример, который я хотел бы привести, - это категория линейных отображений, объектами которых являются только типы Haskell, которые представляют векторные пространства, такие как Double или же (Double, Double) или же InfiniteSequence Double, Линейные отображения по сути являются матрицами, но имеют не только проверенную типоразмерность доменов и кодоменов, но и возможность представлять различные пространства с особым значением, например, предотвращая добавление вектора положения к вектору гравитационного поля. А поскольку векторы не обязательно должны быть буквально представлены массивами чисел, вы можете иметь оптимизированные представления для каждого приложения, например, для сжатых данных изображений, для которых вы хотите выполнить машинное обучение.

Линейные отображения не являются Category экземпляр, но они являются экземпляром ограниченной категории.

Другие вопросы по тегам