首页 > 解决方案 > foldMap 回调中强制的意外行为

问题描述

此代码编译:

import Data.List (isPrefixOf)
import Data.Monoid (Any(..))
import Data.Coerce

isRoot :: String -> Bool
isRoot path = getAny $ foldMap (coerce . isPrefixOf) ["src", "lib"] $ path

coerce用作包装 in 最终结果的快捷isPrefixOf方式Any

这个类似的代码无法编译(注意缺少.):

isRoot :: String -> Bool
isRoot path = getAny $ foldMap (coerce isPrefixOf) ["src", "lib"] $ path

错误是:

* Couldn't match representation of type `a0' with that of `Char'
    arising from a use of `coerce'
* In the first argument of `foldMap', namely `(coerce isPrefixOf)'
  In the first argument of `($)', namely
    `foldMap (coerce isPrefixOf) ["src", "lib"]'
  In the second argument of `($)', namely
    `foldMap (coerce isPrefixOf) ["src", "lib"] $ path'

但我的直觉是它也应该编译。毕竟,我们知道isPrefixOfwill 的参数是Strings,并且结果必须是类型Any。没有歧义。所以String -> String -> Bool应该转换为String -> String -> Any. 为什么它不起作用?

标签: haskellcoerce

解决方案


这与强制无关。这只是一般的约束解决。考虑:

class Foo a b
instance Foo (String -> Bool) (String -> Any)
instance Foo (String -> String -> Bool) (String -> String -> Any)

foo :: Foo a b => a -> b
foo = undefined

bar :: String -> String -> Any
bar = foo . isPrefixOf

baz :: String -> String -> Any
baz = foo isPrefixOf

工作正常的定义barbaz失败的定义。

bar中, 的类型isPrefixOf可以直接推断为,只需将s 的第一个参数(即)的类型与 的第一个参数类型String -> String -> Bool统一即可。barStringisPrefixOf

在中,无法从表达式baz中推断出 的类型。该函数可以对 的类型做任何事情来获得结果类型。isPrefixOffoo isPrefixOffooisPrefixString -> String -> Any

请记住,约束并不会真正影响类型统一。统一就像没有约束一样发生,当统一完成时,需要约束。

回到你原来的例子,以下是一个完全有效的强制,所以歧义是真实的:

{-# LANGUAGE TypeApplications #-}

import Data.Char
import Data.List (isPrefixOf)
import Data.Monoid (Any(..))
import Data.Coerce

newtype CaselessChar = CaselessChar Char
instance Eq CaselessChar where CaselessChar x == CaselessChar y = toUpper x == toUpper y

isRoot :: String -> Bool
isRoot path = getAny $ foldMap (coerce (isPrefixOf @CaselessChar)) ["src", "lib"] $ path

推荐阅读