В чем разница между '.' и «<<<» при выполнении композиции функций? - PullRequest
0 голосов
/ 28 августа 2018

Я пытаюсь выполнить композицию функций в 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 ]

0 голосов
/ 28 августа 2018

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

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, вы можете Kleisli составить их:

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

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

0 голосов
/ 28 августа 2018

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

(.) :: (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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...