3

我正在尝试使以下代码工作(好吧,先编译!):

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

提前致谢!

4

2 回答 2

8

它看起来Binding a应该是一个将类型值转换为类型aRepresentation a或错误消息的函数。但是,由于输入和输出是 JSON 编码的,它们都有 type JSValue; 他们的类型根本不提a

data Binding a = Binding (JSValue -> Either String JSValue)

没有信息表明这些JSValues 代表什么类型。

在 的定义中bind,编译器知道返回类型是Binding a,但该类型与JSValues 的类型之间没有联系。特别是,编译器无法推断出fromJSON应该返回 anatoJSON应该采用Representation a. 要修复它,请将显式类型签名添加到bindingquery

有时让人们感到困惑的一个细节是如何告诉 GHC 类型变量范围。这需要ScopedTypeVariables扩展。添加forall a.到 的类型签名bind,并添加forall.到正文中的其他类型签名,以bind使变量a的范围正确。

于 2013-09-22T18:46:48.560 回答
3

类型错误的本质是这一行:“类型变量‘a0’不明确”。

(免责声明:我试图在这个答案中避免使用行话。)

要了解这里发生了什么,我建议将bindingandquery绑定浮动到顶层。如果您随后注释掉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

您的错误本质上是由\x -> binding (query x)定义中的表达式引起的bind。请注意,类型变量a仅出现在 的域binding和 的范围内query。因此,当您编写它们时,会发生两件事。

  1. 类型变量未确定;它的“价值”仍然未知。

  2. 从组合的类型无法访问类型变量。对于我们的非正式目的,该类型是JSValue -> Either String JSValue.

GHC 引发错误,因为在组合期间未确定类型变量(即 1),并且将来永远无法确定(2 的结果)。

这个问题在 Haskell 中的一般形式通常被称为“ show-read问题”;在Real World Haskell 的第 6 章中搜索“模糊” 。(有些人也可能称其为“过多的多态性”。)

query正如您和 Sjoerd 所确定的(以及 RWH 章节中解释的),您可以通过在应用之前将类型归因于结果来修复此类型错误binding。在不了解您的语义的情况下,我假设您希望这个“隐藏”类型变量与类型构造函数a的参数相同。Binding因此,以下将起作用。

bind :: forall b.
 (Data b, Resource b, Endpoint b, Data (Representation b)) => Binding b
bind = Binding (\x -> binding $ (query x :: Result b))

此归属通过将类型变量a完全替换为Result b. 请注意,与保持未确定不同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 在类型检查后会迅速确定定义实际上不是递归的。

HTH。

于 2013-09-23T19:05:56.930 回答