Неудачная композиция функториальных и монадических c операций - PullRequest
0 голосов
/ 14 июля 2020

Еще один новый ie вопрос в моей борьбе за изучение идиоматики c Haskell: Я пытаюсь составить умный конструктор из некоторых функций проверки, но не могу выровнять типы.

Это тип, который я хочу построить:

newtype Tag = Tag { getTag :: Text }

Это функции проверки:

validateCharacters :: Text -> Maybe Text
canonicalize :: Text -> Text
validateTagLength :: Text -> Maybe Text

А вот умный конструктор, который я пытаюсь написать :

mkTag t = Tag
       <$> validateTagLength
       >=> canonicalize
       <$> validateCharacters t

Насколько я понимаю, типы должны складываться: canonicalize <$> validateCharacters относится к типу Text -> Maybe Text, как и validateTagLength, а Kleisil fi sh должен объединять монади c функции a -> m b. Наконец, сопоставление конструктора с результирующей монадой Maybe должно вернуть ожидаемый Maybe Tag. Однако я получаю следующие типы ошибок:

    • Couldn't match type ‘Maybe Text’ with ‘Text’
      Expected type: Text -> Text
        Actual type: Text -> Maybe Text
    • In the second argument of ‘(<$>)’, namely ‘validateTagLength’
      In the first argument of ‘(>=>)’, namely
        ‘UnconstrainedTag <$> validateTagLength’
    [...]

и

    • Couldn't match expected type ‘b0 -> m c’
                  with actual type ‘Maybe Text’
    • Possible cause: ‘(<$>)’ is applied to too many arguments
      In the second argument of ‘(>=>)’, namely
        ‘canonicalize <$> validateCharacters t’
    [...]

Где моя ошибка? Я упускаю какие-то правила приоритета?

Ответы [ 2 ]

4 голосов
/ 14 июля 2020

В вашем коде используется canonicalize <$> validateCharacters t (обратите внимание на последний t!), Который имеет тип Maybe Text, поэтому вы не можете >=>, поскольку это не функция.

Вы могли бы использовать что-то например,

mkTag t :: Text -> Maybe Tag
mkTag t = do
   canonic <- canonicalize <$> validateCharacters t
   Tag <$> validateTagLength canonic

или

mkTag t :: Text -> Maybe Tag
mkTag t = Tag <$> ((canonicalize <$> validateCharacters t) >>= validateTagLength)

, что, возможно, менее читаемо. На мой взгляд, даже использование =<< для исправления порядка выглядит хуже, чем вариант do.

Если вам действительно нужно безточечное решение, возможно, это тоже может сработать, и это не так уж плохо :

mkTag = fmap Tag . validateTagLength <=< fmap canonicalize . validateCharacters
3 голосов
/ 14 июля 2020

Если вам нужен строгий «конвейер» слева направо, вам нужно превратить canonicalize в стрелку Клейсли (т.е. переключиться с a -> b на a -> Maybe b), составив его с return. Вы также должны сначала применить validateTagLength к t, чтобы «заполнить» конвейер.

mkTag t = Tag <$> (validateTagLength t
                   >>= return . canonicalize
                   >>= validateCharacters)

Вы можете сделать это без точек, используя >=>, создав стрелку Клейсли из Tag также.

mkTag = validateTagLength
        >=> return . canonicalize
        >=> validateCharacters
        >=> return . Tag

(Возможно, вам будет удобнее определить klift = (return .), позволяя писать mkTag = validateTagLength >=> klift canonicalize >=> ....)

Однако в

* 1020 нет ничего плохого *
...