4

此代码编译:

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. 为什么它不起作用?

4

4 回答 4

3

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

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
于 2021-06-27T22:38:45.300 回答
3

isPrefix已经推断出类型[a] -> [a] -> Bool(带有约束Eq a)期望的类型coerce isPrefix存在[Char] -> [Char] -> Any,所以你最终得到一个约束Coercible a Char,但实际上没有任何a约束Char。事实上,它可以是 周围的任何新Char类型,它可能有不同的Eq实例。

newtype CChar = CChar Char

instance Eq CChar where
  _ == _ = True

bad :: String -> Bool
bad path = getAny $ foldMap (coerce (isPrefixOf :: [CChar] -> [CChar] -> Bool)) ["src", "lib"] $ path
于 2021-06-27T22:38:49.603 回答
2

我想我会指出一种有时很方便的解决方法。你基本上想要

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

但想强制isPrefixOf而不是用它组成一个函数。在这种情况下,真的没有意义,但如果你有一些未知的传递函数而不是isPrefixOf,这有时对性能很重要。如果您不想为 提供完整的类型签名coerce或使用类型应用程序,则一种选择是将组合运算符替换为强制运算符。

import Data.Profunctor.Unsafe ((#.))

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

Profunctor实例->定义了类似的(#.)东西

-- (#.) :: Coercible b c => q b c -> (a -> b) -> a -> c
_ #. f = coerce f
于 2021-06-27T22:51:17.847 回答
1

这就是我的想法。

您有一个 function coerce isPrefixOf,并且通过 context 该 function 被限制为具有 type String -> String -> AnyisPrefixOf本身就有类型Eq a => [a] -> [a] -> Bool

显然coerce需要将返回值中的 the 转换Bool为 an Any,但是参数呢?我们是实例化isPrefixOf[Char] -> [Char] -> Bool然后强制到[Char] -> [Char] -> Any吗?还是我们实例化isPrefixOf[T] -> [T] -> Bool(对于某些T)然后强制TChar以及BoolAny?我们需要知道 的实例化,isPrefixOf然后才能判断这是否有效。1

如果我们isPrefixOf直接应用2,那么我们正在处理 a 的事实String将实例化isPrefixOf的类型变量Char,一切都会正常工作。但是你从不直接使用isPrefixOf; 你用coerce isPrefixOf. 因此,您正在处理的那些字符串没有连接到ainisPrefixOf的类型,它们连接到生成的coerce isPrefixOf. 这并不能帮助我们在获取它isPrefixOf 之前实例化类型。 coercea可能是任何coerce可以转化为的东西Char,它不是在这种情况下强迫 Char。需要其他东西来告诉我们,a必须是Char

这种模棱两可正是 GHC 所抱怨的。并不是编译器不够聪明,没有注意到唯一可能的选择isPrefixOfis [Char] -> [Char] -> Any,而是您编写的代码实际上缺少编译器需要(正确)推断出的一条信息。

coerce完全“通过”它破坏类型推断,因为就类型推断而言coerce :: a -> bCoercible a b约束是否真正经得起审查是另一回事)。对于可以“尝试”转换的类型没有限制coerce,只有可以成功转换的类型,因此无法绘制统一链coerce。如果有任何类型变量,您需要独立确定每一侧的类型。3


1实际上,可能有多种有效的方法来实例化它,从而导致最终函数的不同行为。一个明显newtype CaseInsensitiveChar = C Char的例子是Eq实例使用toLower; isPrefixOf :: [CaseInsensitiveChar] -> [CaseInsensitiveChar] -> Bool可以被强制执行[Char] -> [Char] -> Any,但与强制执行的行为不同isPrefixOf :: [Char] -> [Char] -> Bool

2或者更确切地说,将它传递给foldMap应用于字符串的 a。

3 “边”我指的isPrefixOf是应用程序“内部”的方式coerce,其他一切只与结果交互,coerce“外部”也是如此。

于 2021-06-28T00:38:51.587 回答