Monad变压器用于发信号通知没有解决方案列表与`Nothing`

时间:2018-01-27 16:25:53

标签: haskell

问候Haskellers的同事。

这是我更大的约束满足问题的玩具版本 目前正在努力。

下面的代码使用list monad转换器来表示给定的 正整数n作为不同的小偶数的总和 方式。

import Control.Monad.Trans.List
import Control.Monad

sumToN' :: Int -> ListT Maybe [Int]
sumToN' n
  | n == 0 = pure []
  | otherwise = 
      do
        x <- ListT $ Just [1..4]
        guard $ x <= n
        guard $ even x
        fmap (x:) $ sumToN' $ n - x

加载到GHCi后,该函数按预期工作。

λ> sumToN' 8
ListT (Just [[2,2,2,2],[2,2,4],[2,4,2],[4,2,2],[4,4]])
λ> sumToN' 7
ListT (Just [])

但是,我希望通过定义一个来为我的代码添加一些样式 如果没有解决方案,将返回ListT Nothing的函数 找到。这不是我得到的。我得到的结果可能是一个 为mzero定义ListT的结果。

λ> mzero :: ListT Maybe [Int]
ListT (Just [])

我的问题是:是否可以使用monad变换器进行组合 列表和Maybe,以便Nothing表示没有解决方案 代码。(我不是在寻找黑客。我想知道monad 变形金刚会以某种方式直接支持这一点。)

作为背景信息,以下代码不使用monad 变压器根本就达到了与上述相同的结果 表示[]没有解决方案。

sumToN :: Int -> [[Int]]
sumToN 0 = [[]]
sumToN n = do
  x <- [1..4]
  guard $ x <= n
  guard $ even x
  map (x:) $ sumToN $ n - x

这给我们提供了与列表转换器基本相同的结果。

λ> sumToN 8
[[2,2,2,2],[2,2,4],[2,4,2],[4,2,2],[4,4]]
λ> sumToN 7
[]

1 个答案:

答案 0 :(得分:1)

虽然我认为简单地删除Maybe是惯用的解决方案,但这里是您要问的问题的答案。由于我们知道monad变换器堆栈的确切性质,我们可以使用模式匹配来反省它的值。

testList :: ListT Maybe a -> ListT Maybe a
testList (ListT (Just [])) = ListT Nothing
testList x = x

如果你想要一些与类型系统对话的东西,你可以用NonEmpty构建一个monad实例(请记住,这不再是一个表现良好的monad变换器而不是{{} 1}}是,但它会做你想要的)。我们可以称之为ListT

NonEmptyT

现在我们可以写

newtype NonEmptyT m a = NonEmptyT { unNonEmptyT :: m (NonEmpty a) }

-- Instance implementations omitted for brevity

instance Functor m => Functor (NonEmptyT m) where
    ...

instance Applicative m => Applicative (NonEmptyT m) where
    ...

instance Monad m => Monad (NonEmptyT m) where
    ...

instance MonadTrans NonEmptyT where
    ...

这需要一个普通的列表并将其转换为testList' :: [a] -> NonEmptyT Maybe a testList' [] = NonEmptyT Nothing testList' (x:xs) = NonEmptyT $ Just (x :| xs) 。与以前的方法有所不同的是,您不喜欢(NonEmptyT Maybe)的价值在我们的新类型中甚至没有意义。您可以拥有ListT (Just []),但可以NonEmptyT Nothing,但表达式NonEmptyT (Just someNonemptyList)甚至不会进行类型检查。

这并不是你正在寻找的行为,变压器实际上与它下面的层相互作用并理解它,但我认为它是朝着正确方向迈出的一步,因为它完全是正确的禁止程序员构建你不想存在的价值。

Full working example

(脚注:不幸的是,GHC并不想让我在这个新类型上使用NonEmptyT (Just [])GeneralizedNewtypeDeriving,所以你实际上必须手工编写实例。如果有人知道为什么这些扩展在这种情况下失败了,我很想知道原因。)