我正在尝试使以下代码工作(好吧,首先编译!
module Orexio.Radix where
import Data.Data
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Typeable
import Text.JSON.Generic
class Resource a where
type Representation a :: *
identifier :: Resource a => Identifier a
class Endpoint a where
call :: Resource a => a -> Representation a
data Identifier a = Identifier [String] deriving (Show)
data Binding a = Binding (JSValue -> Either String JSValue)
bind :: (Data a, Resource a, Endpoint a, Data (Representation a)) => Binding a
bind = Binding (x -> binding $ query x)
where binding query = fmap (x -> toJSON $ call x) (resultToEither query)
query jsvalue = fromJSON jsvalue
{-- DEMO --}
data HelloWorld = HelloWorld {
name :: String
} deriving (Show, Typeable, Data)
instance Resource HelloWorld where
type Representation HelloWorld = String
identifier = Identifier ["helloworld"]
instance Endpoint HelloWorld where
call r = "Hello " ++ name r
所以我必须使FlexibleContexts
能够做Data (Representation a)
,但它仍然不起作用......
我有这个错误:
src/Orexio/Radix.hs:21:33:
Could not deduce (Data a0) arising from a use of `query'
from the context (Data a,
Resource a,
Endpoint a,
Data (Representation a))
bound by the type signature for
bind :: (Data a, Resource a, Endpoint a,
Data (Representation a)) =>
Binding a
at src/Orexio/Radix.hs:20:9-78
The type variable `a0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
instance Data HelloWorld -- Defined at src/Orexio/Radix.hs:29:29
instance Data () -- Defined in `Data.Data'
instance (Data a, Data b) => Data (a, b) -- Defined in `Data.Data'
...plus 42 others
In the second argument of `($)', namely `query x'
In the expression: binding $ query x
In the first argument of `Binding', namely
`( x -> binding $ query x)'
老实说,我在这里有点迷茫,我一定错过了什么,但是什么?
以下是我激活的其他扩展:DeriveDataTypeable
、ExistentialQuantification
、NoMonomorphismRestriction
、TypeFamilies
提前感谢!
Binding a
应该是一个将类型a
的值转换为类型为 Representation a
或错误消息的值的函数。 但是,由于输入和输出是 JSON 编码的,因此它们都具有类型 JSValue
;他们的类型根本没有提到a
!
data Binding a = Binding (JSValue -> Either String JSValue)
没有信息指示这些JSValue
代表的类型。
在bind
的定义中,编译器知道返回类型是Binding a
,但该类型和JSValue
类型之间没有链接。 特别是,编译器无法推断fromJSON
应该返回a
,而toJSON
应该Representation a
。 要修复此问题,请将显式类型签名添加到 binding
和 query
。
有时让人们感到困惑的一个细节是如何告诉GHC类型变量范围。 这需要ScopedTypeVariables
扩展。 将forall a.
添加到 bind
的类型签名中,并将forall.
添加到bind
正文中的其他类型签名中,以便变量a
的作用域正确。
类型错误的本质是这一行:"类型变量'a0'不明确"。
(免责声明:我试图避免在这个答案中使用行话。
为了了解这里发生了什么,我建议将binding
和query
绑定浮动到顶层。他们成功键入检查您是否随后注释掉bind
绑定。GHC 推断出以下类型。
*Orexio.Radix> :i query
query :: Data a => JSValue -> Result a
*Orexio.Radix> :i binding
binding ::
(Data (Representation a), Endpoint a, Resource a) =>
Result a -> Either String JSValue
您的错误本质上是由bind
定义中的表达式x -> binding (query x)
引起的。请注意,类型变量 a
仅出现在 binding
的域和 query
的范围中。因此,当您撰写它们时,会发生两件事。
类型变量未确定;其"值"仍然未知。
无法从组成。出于我们的非正式目的,该类型是
JSValue -> Either String JSValue
.
GHC 引发错误是因为类型变量在组合过程中未确定(即 1),并且将来永远无法确定(2 的结果)。
Haskell中这个问题的一般形状通常被称为"show
- read
问题";在现实世界Haskell的第6章中搜索"歧义"。(有些人可能也称之为"太多的多态性"。
正如您和 Sjoerd 所确定的(RWH 章节解释),您可以通过在应用 binding
之前将类型归因于 query
的结果来修复此类型错误。在不知道您的语义的情况下,我假设您打算将这个"隐藏"类型变量a
与Binding
类型构造函数的参数相同。因此,以下内容将起作用。
bind :: forall b.
(Data b, Resource b, Endpoint b, Data (Representation b)) => Binding b
bind = Binding (x -> binding $ (query x :: Result b))
此归因通过将类型变量完全替换为 Result b
来消除类型变量a
。请注意,与a
不同,b
保持未确定是可以接受的,因为它可以在顶级类型中访问;bind
的使用可能各自决定b
。
第一种解决方案需要给bind
一个明确的签名——这有时可能相当繁重。在这种情况下,由于单态限制,您可能已经需要该类型签名。但是,如果bind
接受了一个参数(但仍表现出这种不明确的类型变量错误),您仍然可以通过使用如下所示的解决方案来依赖类型推断。
dummy_ResultType :: Binding a -> Result a
dummy_ResultType = error "dummy_ResultType"
bind () = result where
result = Binding (x -> binding $ (query x `asTypeOf` dummy))
dummy = dummy_ResultType result
(如果使用error
让您担心,请参阅代理类型。
或者用一些惯用语换取直接性:
withArgTypeOf :: f x -> g x -> f x
withArgTypeOf x _ = x
bind () = result where
result = Binding (x -> binding (query x `withArgTypeOf` result))
现在推理有效。
*Orexio.Radix> :i bind
bind ::
(Data (Representation a), Data a, Endpoint a, Resource a) =>
() -> Binding a
请放心,GHC 在类型检查后会快速确定定义实际上不是递归的。
呵呵。