当 "modifying" 字段具有相同值时,无法将类型“HandlerSite m0”与“HandlerSite m”匹配
Couldn't match type ‘HandlerSite m0’ with ‘HandlerSite m’ when "modifying" field with same value
我正在开发 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
到 link 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
是一个类型 class 成员,它有多个定义 select 来自,而且,即使它是,我也看不出是什么阻止了 ghc 推断 m
和 m0
是相同的....好的 HandlerSite
是一个类型族,所以我猜从类型检查器的角度来看它可能导致 RenderMessage
的不同实例等等以某种方式 link 编辑为 I.textField
的不同代码定义。我想我开始看到曙光了。
编辑 2:我想我可以 link 他们像这样:
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
所以我想这与顶级绑定和本地绑定有关?
And it failed, so I guess the problem is related with mentioning I.textField twice. This is weird.
实际上,当涉及类型族时,这很常见。让我在一个更简单的案例中展示这个问题。假设我们有一个类型族如下
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
!
此外,考虑一下如果我们有一个可以产生 any F a
.
的多态值会发生什么
x :: forall a . F a
然后,我们可能会想调用 foo x
。毕竟,foo
需要 F a
而 x
可以为任何 a
产生 F 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
,由于非内射性,它可能与某些其他 monad m0
的 HandlerSite m0
类型相同。
现在,I.textField
可以产生一个值 "for any m
"。因此,使用它有点类似于上面使用 foo x
。您的代码更奇特,因为如果我们使用 "same" 调用 I.textField
,编译器能够推断出我们确实需要 "right" m
。在这里,"same" 调用意味着定义一些标识符,例如您的 f
到 I.textField
,并使用 f
两次。相反,对 I.textField
进行两次调用允许 GHC 选择两个不同的 m
,每次调用一个,并且会出现歧义。
如果您感到困惑,请不要担心——理解起来有点棘手,尤其是在像 Yesod 这样相对真实的框架上。
如何解决这个问题?有很多方法,但在我看来,解决此类歧义的最佳现代方法是打开 TypeApplications
扩展(超出 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
语法用于选择类型,覆盖类型推理引擎。在许多情况下,它与编写类型注释具有相似的效果,但即使在 "ambiguous" 类型注释不起作用的情况下也能正常工作。例如 foo (x @ Int)
可以在上面的简单示例中使用。
(我不熟悉 Yesod,所以如果 I.textField
也被其他类型变量参数化,上面的方法可能不起作用,在这种情况下我们需要更多 @ type
应用程序,例如 I.textField @type @type2 ...
其中之一是@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
到 link 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
是一个类型 class 成员,它有多个定义 select 来自,而且,即使它是,我也看不出是什么阻止了 ghc 推断 m
和 m0
是相同的....好的 HandlerSite
是一个类型族,所以我猜从类型检查器的角度来看它可能导致 RenderMessage
的不同实例等等以某种方式 link 编辑为 I.textField
的不同代码定义。我想我开始看到曙光了。
编辑 2:我想我可以 link 他们像这样:
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
所以我想这与顶级绑定和本地绑定有关?
And it failed, so I guess the problem is related with mentioning I.textField twice. This is weird.
实际上,当涉及类型族时,这很常见。让我在一个更简单的案例中展示这个问题。假设我们有一个类型族如下
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
!
此外,考虑一下如果我们有一个可以产生 any F a
.
x :: forall a . F a
然后,我们可能会想调用 foo x
。毕竟,foo
需要 F a
而 x
可以为任何 a
产生 F 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
,由于非内射性,它可能与某些其他 monad m0
的 HandlerSite m0
类型相同。
现在,I.textField
可以产生一个值 "for any m
"。因此,使用它有点类似于上面使用 foo x
。您的代码更奇特,因为如果我们使用 "same" 调用 I.textField
,编译器能够推断出我们确实需要 "right" m
。在这里,"same" 调用意味着定义一些标识符,例如您的 f
到 I.textField
,并使用 f
两次。相反,对 I.textField
进行两次调用允许 GHC 选择两个不同的 m
,每次调用一个,并且会出现歧义。
如果您感到困惑,请不要担心——理解起来有点棘手,尤其是在像 Yesod 这样相对真实的框架上。
如何解决这个问题?有很多方法,但在我看来,解决此类歧义的最佳现代方法是打开 TypeApplications
扩展(超出 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
语法用于选择类型,覆盖类型推理引擎。在许多情况下,它与编写类型注释具有相似的效果,但即使在 "ambiguous" 类型注释不起作用的情况下也能正常工作。例如 foo (x @ Int)
可以在上面的简单示例中使用。
(我不熟悉 Yesod,所以如果 I.textField
也被其他类型变量参数化,上面的方法可能不起作用,在这种情况下我们需要更多 @ type
应用程序,例如 I.textField @type @type2 ...
其中之一是@m
.)