函数和单子操作的失败组合

Failing composition of functorial and monadic operations

我努力学习惯用语的另一个新手问题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 <$> validateCharactersText -> Maybe Text 类型,validateTagLength 也是,Kleisil 鱼应该组合单子函数 a -> m b.最后,将构造函数映射到生成的 Maybe monad 应该 return 预期的 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’
    [...]

我的错误在哪里?我是否遗漏了一些优先规则?

您的代码使用 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

如果你想要一个严格的从左到右的“管道”,你需要将 canonicalize 变成 Kleisli 箭头(即从 a -> b 切换到 a -> Maybe b)用 return 合成它。您还必须首先将 validateTagLength 应用于 t,以“启动”管道。

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

您可以使用 >=> 使这一点成为免费的,也可以从 Tag 创建一个 Kleisli 箭头。

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

(您可能会发现定义 klift = (return .) 更具可读性,让您编写 mkTag = validateTagLength >=> klift canonicalize >=> ...。)

不过,

没什么问题
mkTag t = do
    t1 <- validateTagLength t
    let t2 = canonicalize t1
    t3 <- validateCharacters t2
    return $ Tag t3