2017-10-10 65 views
2

我目前正在哈斯克尔建立一个服务器,并作为语言的新手,我想尝试一种新方法zu Monad组合。这个想法是,我们可以编写库方法,如如何在没有与Haskell中的类型系统进行对抗的情况下对monad进行抽象?

isGetRequest :: (SupportsRequests m r) => m Bool 
    isGetRequest = do 
     method <- liftRequests $ requestMethod 
     return $ method == GET 

    class (Monad m, RequestSupport r) => SupportsRequests m r | m -> r where 
     liftRequests :: r a -> m a 

    class (Monad r) => RequestSupport r where 
     requestMethod :: r Method 

哪些工作不知道底层monad。当然,在这个例子中,使isGetRequest直接在(RequestSupport r)monad上运行就足够了,但是我的想法是,我的库也可能对monad有多个约束。然而,我不想在同一个模块中实现所有这些不同的问题,也不希望在不同的模块(孤立实例!)中传播它们。 这就是为什么m monad只实现类,将真正关注的问题委托给其他monad。

上面的代码应该完美地工作(对GHC有一些语言扩展)。不幸的是,我有一些问题,CRUD(创建,读取,更新,删除)关注:

class (Monad m, CRUDSupport c a) => SupportsCRUD m c a | m a -> c where 
    liftCRUD :: c x -> m x 

class (Monad c) => CRUDSupport c a | c -> a where 
    list :: c [a] -- List all entities of type a 

不,我得到一个错误:

Could not deduce (SupportsCRUD m c a0) from the context [...] 
The type variable 'a0' is ambiguous [...] 
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes 
When checking the class method: liftCRUD [...] 

好像类型检查不喜欢的a参数并不直接出现在liftCRUD的签名中。这是可以理解的,因为a不能从函数依赖派生。

我的大脑中的类型检查器告诉我,在库方法中执行一些关于CRUD的方法时,稍后使用AllowAmbiguousTypes推断出类型a应该不成问题。不幸的是,GHC似乎无法做到这一点的推断步骤,例如

bookAvailable :: (SupportsCRUD m c Book) => m Bool 
bookAvailable = do 
    books <- liftCRUD (list :: c [Book]) -- I use ScopedTypeVariables 
    case books of 
     [] -> return False 
     _ -> return True 

产生

Could not deduce (SupportsCRUD m c0 a1) arising from a use of 'liftCRUD' [...] 
The type variables c0, a1 are ambiguous [...] 

看来,我仍然无法推论的编译器。我有办法解决这个问题吗?或者至少有一种方法可以理解编译器能够推断出什么?

最好的问候, bloxx

+3

'SupportsCRUD'类声明涉及三个类型变量,'m','c'和'a'。你能说出你期望编译器在你的例子中为这三个变量推断什么值,为什么? –

+1

你的fundep'm a - > c'打算做什么?这意味着“c'的选择由具体的类型'm'和'a''唯一确定,它的作用是类型检查器可以选择仅基于'm'和'a'的实例上下文,而'c'则来自你在实例头部放置的任何东西。 – jberryman

+0

@jberryman它实际上应该告诉编译器,对于monad m和“CRUD实体”a,类型检查器可以选择一个唯一的monad c(这是支持CRUD操作的单元)。 – bloxx

回答

3

要使用ScopedTypeVariables你还需要绑定要在范围上与forall的变量。所以它应该是

bookAvailable :: forall m c. (SupportsCRUD m c Book) => m Bool 
... 

这是所有必要(后我做了我以为是进入了你的问题错别字一些琐碎的修复),我得到的代码进行编译。

+0

哇!那么,这让我感到非常惊讶,现在我有一个问题:为什么GHC在推断'liftCRUD'的类型时愿意选择'a〜Book'?很明显,这种选择有适当的实例可供使用;但是很明显,稍后会有人加入,并添加另一个也是不错的选择,这对我来说是“模棱两可”的尖叫。 –

+2

Aha,在试图证明我的观点是“明显可以再来一次并添加另一个实例”之后,我想也许我看到了原因:在'CRUDSupport'上有一个关键的'c - > a'fundep' ,并且所有'SupportsCRUD'实例都意味着相应的'CRUDSupport'实例。 –

+0

@DanielWagner是的,它也绊倒了我。我错过了'CRUDSupport'是'SupportsCRUD'的超类,因为它后来被声明了,而且我习惯了先超类。 – luqui

相关问题