И это не удалось, поэтому я думаю, что проблема связана с упоминанием 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
для некоторой другой монады m0
.
Теперь I.textField
может выдавать значение "для любого m
".Следовательно, использование его как-то похоже на использование foo x
выше.Ваш код более своеобразен, поскольку, если мы используем «тот же» вызов I.textField
, компилятор может сделать вывод, что мы действительно хотим получить «правильный» m
.Здесь «тот же» вызов означает определение некоторого идентификатора, такого как ваш f
до I.textField
, и дважды используйте f
.Вместо этого, два вызова I.textField
позволяют GHC выбрать два различных m
s, по одному для каждого вызова, и возникает неоднозначность.
Если вы растерялись, не беспокойтесь - это немногосложно понять, особенно в относительно реальной среде, такой как 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
используется для выбора типа, переопределяя механизм вывода типов.Во многих случаях это имеет эффект, аналогичный написанию аннотации типов, но работает даже в «неоднозначных» случаях, когда аннотации типов этого не делают.Например, foo (x @ Int)
сработал бы в более простом примере выше.
(я не знаком с Yesod, поэтому приведенное выше может не сработать, если I.textField
также параметризован переменными других типов, в этом случае мынужно больше @ type
приложений, например I.textField @type @type2 ...
одно из которых @m
.)