在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}}
答案 0 :(得分:2)
我不确定你想要actuals
的类型,但我们假设它是[Either String (Maybe Bool)]
。在这种情况下,让我们从
actuals = zipWith (\ew a -> undefined) wffs assignments
其中ew
是Either String Wff
而a
是Assignment
。我们的功能应该是什么?不知何故,我们需要将eval
提升到Either
。如果作业也在Either
,我们可以写liftM2 eval a ew
- 但事实并非如此。因此,我们可以在此处使用return a
,或者先使用eval
将Assignment
参数应用于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 ... n
和Maybe
是否存在是一个标志,您可以略微简化您的类型。例如,如果您的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 assignments
(f
是因为concat
类型需要以某种方式展平。如果你忘了它,类型就不会完全匹配了),然后查询编译器的[[]]
类型(通过写f
并检查产生的错误信息),然后编写相应的函数,然后减少(高尔夫)。 GHC 7.8将具有类型漏洞,允许您获取未定义的仍待编写表达式的类型,从而实现比现在更加优雅的类型驱动开发(TDD)方式(例如,Agda除外) )。
根据我的原始帖子,将其改为使用列表推导并使类型where f :: (); f = undefined
保留为有用的练习:)
答案 1 :(得分:2)
mapLift
的第一个参数是来自a -> b
的函数。您作为第一个参数传递eval :: Wff -> Assignment -> Maybe Bool
。由于(->)
的相关性,这意味着a
与Wff
和b
与Assignment -> Maybe Bool
统一。这意味着mapLift
,wffs
的下一个参数预计会有[m Wff]
类型,以便m
与Either String
统一。这一切都很好。
问题是结果mapLift eval wffs
最终会以[m b]
类型结束,而m
和b
统一为[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 String
和Maybe
。幸运的是,这可以通过将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是如何交错的。 fmap
和liftM
提供的快捷方式非常强大,但需要一些时间才能做到正确。
答案 2 :(得分:2)
fmap
或<$>
就足够了(假设您想要的输出属于[Either String (Maybe Bool)]
类型:
result :: [Either String (Maybe Bool)]
result = zipWith (<$>) [flip eval $ a | a <- assignments] wffs