haskell - 当“修改”具有相同值的字段时,无法将类型“HandlerSite m0”与“HandlerSite m”匹配
问题描述
我正在开发一个 Yesod 应用程序,并希望有textField
一个修改后的fieldView
. 首先,我尝试了这个:
textField
:: ( Monad m
, RenderMessage (HandlerSite m) FormMessage
)
=> Field m Text
textField = I.textField
{ fieldView = fieldView I.textField
}
据我所知,这textField
应该与I.textField
. 但是,我收到以下错误:
Foo.hs:37:19: error:
• Couldn't match type ‘HandlerSite m0’ with ‘HandlerSite m’
Expected type: FieldViewFunc m Text
Actual type: FieldViewFunc m0 Text
NB: ‘HandlerSite’ is a type function, and may not be injective
The type variable ‘m0’ is ambiguous
• In the ‘fieldView’ field of a record
In the expression: I.textField {fieldView = fieldView I.textField}
In an equation for ‘textField’:
textField = I.textField {fieldView = fieldView I.textField}
• Relevant bindings include
textField :: Field m Text
(bound at Foo.hs:36:1)
有趣的是,这种另一种写法很好用:
textField
:: ( Monad m
, RenderMessage (HandlerSite m) FormMessage
)
=> Field m Text
textField = f
{ fieldView = fieldView
}
where
f@Field {..} = I.textField
fieldView
作为函数使用有问题吗?我现在很困惑。我尝试使用ScopedTypeVariables
to 链接m
到m0
,但它不起作用,我不明白为什么甚至需要它。什么阻止m
匹配m0
?
编辑:我刚试过:
textField
:: ( Monad m
, RenderMessage (HandlerSite m) FormMessage
)
=> Field m Text
textField = I.textField
{ fieldView = fieldView
}
where
Field {..} = I.textField
它失败了,所以我猜这个问题与提到I.textField
两次有关。这很奇怪。它不像I.textField
是一个类型类成员,它有多个定义可供选择,即使是这样,我也看不出是什么阻止了 ghc 推断它m
并且m0
是相同的......好吧HandlerSite
是一个类型族,所以我从类型检查器的角度猜测,它可能导致不同的实例RenderMessage
和不同的代码定义,这些代码以某种方式链接到I.textField
. 我想我开始看到光明了。
编辑2:我想我可以像这样链接它们:
textField
:: ( Monad m
, RenderMessage (HandlerSite m) FormMessage
)
=> Field m Text
textField = (I.textField :: Field m Text)
{ fieldView = fieldView (I.textField :: Field m Text)
}
开启ScopedTypeVariables
,但显然不是。
编辑3:按照逻辑,这有效:
textField
:: ( Monad m
, RenderMessage (HandlerSite m) FormMessage
)
=> Field m Text
textField = f
{ fieldView = fieldView f
}
where
f = I.textField
所以我想这与顶级绑定与本地绑定有关吗?
解决方案
它失败了,所以我猜这个问题与提到 I.textField 两次有关。这很奇怪。
实际上,当涉及类型族时,这很常见。让我用一个更简单的例子来说明这个问题。假设我们有一个类型族如下
type family F a
type instance F Int = String
type instance F Bool = String
注意F Int
和F Bool
实际上是同一类型,即String
. 这是可能的,因为F
可以是非内射函数。
现在,如果我们手头有以下功能
foo :: F a -> SomeResultType
我们发现我们一般不能称它为
foo "some string"
为什么?好吧,编译器无法确定要用于什么类型a
:它可能是Int
或Bool
,因为两者都会F a
成为String
。该调用不明确,因此会引发类型错误。
更糟糕的是,如果我们在代码中使用了两次该调用,例如
bar (foo "string") (foo "string")
甚至可以选择a = Int
第一次通话和a = Bool
第二次通话!
此外,考虑如果我们有一个可以产生任何 F a
.
x :: forall a . F a
然后,我们可能会想打电话给foo x
. 毕竟,拿来foo
可以生产任何。它看起来很好,但它再次模棱两可。确实,应该选择什么?许多选择适用。我们可能会尝试使用类型签名来解决这个问题F a
x
F a
a
a
foo (x :: F Int)
但这完全等同于任何
foo (x :: String)
foo (x :: F Bool)
所以它确实选择了类型a
!
在您的代码中,会发生类似的问题。让我们剖析类型错误:
Couldn't match type ‘HandlerSite m0’ with ‘HandlerSite m’
Expected type: FieldViewFunc m Text
Actual type: FieldViewFunc m0 Text
NB: ‘HandlerSite’ is a type function, and may not be injective
这告诉我们,在某些时候我们需要指定一个FieldViewFunc m Text
. 这种类型涉及一个类型族HandlerSite m
,由于非内射性,它可能与HandlerSite m0
其他一些 monad的类型相同m0
。
现在,I.textField
可以产生一个值“for any m
”。因此,使用它在某种程度上类似于使用foo x
上面。您的代码更奇特,因为如果我们对 使用“相同”调用I.textField
,编译器能够推断出我们确实想要“正确” m
。在这里,“相同”调用意味着定义一些标识符,例如 your f
to I.textField
,并使用f
两次。相反,进行两次调用I.textField
允许 GHC 选择两个不同m
的 s,每次调用一个,这样就会产生歧义。
如果您感到困惑,请不要担心——这有点难以理解,尤其是在像 Yesod 这样相对真实的框架上。
如何解决这个问题?有很多方法,但在我看来,解决这种歧义的最好的、现代的方法是打开TypeApplications
扩展(beyond ScopedTypeVariables
),然后指定我们真的想选择m
作为外部m
,如下:
textField :: forall m .
( Monad m
, RenderMessage (HandlerSite m) FormMessage
)
=> Field m Text
textField = I.textField @ m
{ fieldView = fieldView (I.textField @ m)
}
该@ m
语法用于选择类型,覆盖类型推理引擎。在许多情况下,它与编写类型注释具有相似的效果,但即使在类型注释没有的“模棱两可”的情况下也可以使用。例如foo (x @ Int)
,可以在上面更简单的示例中使用。
(我对 Yesod 不熟悉,因此如果I.textField
还由其他类型变量参数化,上述方法可能不起作用,在这种情况下,我们需要更多@ type
应用程序,例如I.textField @type @type2 ...
其中之一是@m
.)
推荐阅读
- swift - 每次我显示视图控制器时,CoreData 都会再次添加到表视图中(重复数据)
- c++ - 将函数返回的值添加到 C++ 中的数组中
- r - Shiny App 中的 write.csv 和 read.csv 在 shinyapps.io 上共享
- flutter - 如何从颤动的流式响应中获取响应
- java - 仅当字符串中有另一个元素时,如何才能使元素出现?以及如何使键显示为值?StringSubstitutor
- jenkins - 禁用 jenkins helm chart 的自动升级
- string - 如何使用两个列表理解来展平嵌套的字符串列表
- reactjs - React Native reanimated 不适用于插值
- mysql-workbench - 我该如何解决以下问题
- r - 如何创建整体行程