自定义地图功能:mapLift

时间:2013-10-08 01:52:03

标签: haskell monads

Writing a custom map function讨论我当前的问题后,我认为我找到了解决问题的方法:

mapLift :: Monad m => (a -> b) -> [m a] -> [m b]
mapLift f = map (liftM f)

然而,当我使用它时,我收到编译器错误。我的实际代码是:

actuals     = zipWith mapLift (mapLift eval wffs) assignments

,其中

eval :: Wff -> Assignment -> Maybe Bool
wffs :: [Either String Wff]
assignments :: [Assignment]

此代码提供以下错误消息:

Couldn't match expected type `a0 -> Wff'
            with actual type `Either String Wff'
Expected type: [a0 -> Wff]
  Actual type: [Either String Wff]
In the second argument of `mapLift', namely `wffs'
In the second argument of `zipWith', namely `(mapLift eval wffs)'

我做错了什么?我希望将eval映射到wffs,同时保留任何Left,这些actuals :: [Either String (Maybe Bool)]会从过程中的上一步发出错误消息。

修改

最初,我的想法是eval。但是,我正在考虑将eval :: Wff -> Assignment -> Either String Bool修改为actuals :: [Either String Bool],以便assignments

更新

我在原来的问题上输错了。 assignments :: [[Assignment]] 应该是:

{{1}}

3 个答案:

答案 0 :(得分:2)

我不确定你想要actuals的类型,但我们假设它是[Either String (Maybe Bool)]。在这种情况下,让我们从

开始
actuals = zipWith (\ew a -> undefined) wffs assignments

其中ewEither String WffaAssignment。我们的功能应该是什么?不知何故,我们需要将eval提升到Either。如果作业也在Either,我们可以写liftM2 eval a ew - 但事实并非如此。因此,我们可以在此处使用return a,或者先使用evalAssignment参数应用于flip,然后解除它(或许多其他可能性):

actuals = zipWith (\ew a -> liftM (flip eval a) ew) wffs assignments

或者,让我们进一步打高尔夫球:

actuals = zipWith (\ew -> flip liftM ew . flip eval) wffs assignments

在我看来,两者都不如

那么明确
\ew a -> eval <$> ew <*> return a

其中<$>是来自liftM的{​​{1}}的另一个名称。您可以详细了解Control.Applicative<$>,例如“了解您的Haskell”,但基本上<*>(相当于f <$> ma <*> mb <*> ... <*> mn足够小liftMn f ma mb ... mn)是n的monadic(实际上是applicative)版本。

此时你可以提取所有的失败或其他什么。我想知道f a ... nMaybe是否存在是一个标志,您可以略微简化您的类型。例如,如果您的Either无法评估Wff,则可以使用eval代替Either来记录错误。然后,错误将在它们发生的步骤中被捕获,并且成功将以Maybe monad的通常方式传播:

Either

另请参阅errors软件包,该软件包提供了许多有用的组合 - 在-- eval :: Wff -> Assignment -> Either String Bool actuals :: [Either String Bool] actuals = zipWith (\ew a -> ew >>= flip eval a) wffs assignments Either之间提供了一些有用的组合。

我没有以你提出的方式解决问题,即在压缩之前映射第一个列表。但是你也可以这样做:

Maybe

此处actuals = zipWith (\ef a -> liftM ($ a) ef) (mapLift eval wffs) assignments liftM ($ a)的一个温和聪明的替代品。

这似乎有点吵闹 - ef <*> return a已经是一种地图,你也可以利用它。

编辑以回应OP的编辑:如果您想要zipWith,那么所需的更改很简单:

actuals :: [[Assignment]]

这里的想法是我再次开始使用actuals :: [Either String (Maybe Bool)] actuals = concat $ zipWith (\ew -> map (liftA2 eval ew . return)) wffs assignments 形式的未知concat $ zipWith f wffs assignmentsf是因为concat类型需要以某种方式展平。如果你忘了它,类型就不会完全匹配了),然后查询编译器的[[]]类型(通过写f并检查产生的错误信息),然后编写相应的函数,然后减少(高尔夫)。 GHC 7.8将具有类型漏洞,允许您获取未定义的仍待编写表达式的类型,从而实现比现在更加优雅的类型驱动开发(TDD)方式(例如,Agda除外) )。

根据我的原始帖子,将其改为使用列表推导并使类型where f :: (); f = undefined保留为有用的练习:)

答案 1 :(得分:2)

mapLift的第一个参数是来自a -> b的函数。您作为第一个参数传递eval :: Wff -> Assignment -> Maybe Bool。由于(->)的相关性,这意味着aWffbAssignment -> Maybe Bool统一。这意味着mapLiftwffs的下一个参数预计会有[m Wff]类型,以便mEither String统一。这一切都很好。

问题是结果mapLift eval wffs最终会以[m b]类型结束,而mb统一为[Either String (Assignment -> Maybe Bool)]。这已经向南走了,但让我们继续关注它。

zipWith的类型为(a -> b -> c) -> [a] -> [b] -> [c],我们将第一个参数传递给mapLift :: (s -> t) -> [m s] -> [m t]。这意味着zipWith最终将a与功能类型(s -> t)统一,b[m s][c][m t]统一该

zipWith mapLift :: [s -> t] -> [[m s]] -> [[m t]]

现在让我们传递结果(mapLift eval wffs)并观看烟火。

zipWith mapLift :: [s -> t] -> [[m s]] -> [[m t]]
                (mapLift eval wffs) :: [Either String (Assignment -> Maybe Bool)]

为了继续,typechecker必须将[s -> t][Either String (Assignment -> Maybe Bool)]统一起来。外部[]会立即发送,但我们仍然需要找到一种显示s -> t ~ Either String (Assignment -> Maybe Bool)的方法,因为包裹Either String而这是不可能的。


问题是你的类型没有排列,因为你的语义缺少一种统一两种失败的方法 - Either StringMaybe。幸运的是,这可以通过将Either解压缩到Assignment的函数中来实现。这是一种方式

fixEither :: Either String (Assignment -> Maybe Bool) -> (Assignment -> Maybe Bool)
fixEither (Left _)  _ = Nothing
fixEither (Right f) a = f a

现在终于,如果我们要尝试运行

zipWith mapLift (map fixEither $ mapLift eval wffs) assignments

仍有问题

zipWith mapLift (map fixEither $ mapLift eval wffs)
  :: [[m Assignments]] -> [[m (Maybe Bool)]]

虽然assignments :: [Assignments]不匹配。我们需要在assignments[]的至少另一层中包含m。此外,还不清楚你想要的m


这意味着你可能还没有很好地定义你的算法。

我总是建议做一个递归算法,或者首先使用“普通”map来测试你完全理解列表和函数的各个层以及monad是如何交错的。 fmapliftM提供的快捷方式非常强大,但需要一些时间才能做到正确。

答案 2 :(得分:2)

对于这种情况,

fmap<$>就足够了(假设您想要的输出属于[Either String (Maybe Bool)]类型:

result :: [Either String (Maybe Bool)]
result =  zipWith (<$>) [flip eval $ a | a <- assignments]  wffs