这个想法
我正在写一个DSL,它编译成Haskell.
该语言的用户可以定义自己的不可变数据结构和相关功能.通过关联函数,我指的是属于数据结构的函数.例如,用户可以写(以"pythonic"伪代码):
data Vector a: x,y,z :: a def method1(self, x): return x
(这相当于下面的代码,但也显示了相关函数beheva类似于具有开放世界假设的类型类):
data Vector a: x,y,z :: a def Vector.method1(self, x): return x
在这个例子中,method1
是一个与Vector
数据类型相关的函数,可以像v.testid(5)
(数据类型的v
实例Vector
)一样使用.
我正在将这样的代码翻译成Haskell代码,但我遇到了一个问题,我试图解决很长一段时间.
问题
我试图将代码从GHC 7.6移到GHC 7.7(预发布7.8)(较新的版本可以从源代码编译).该代码在GHC 7.6下完美运行,但不在GHC 7.7下.我想问你如何修复它以使其在新版本的编译器中工作?
示例代码
让我们看一下生成的简化版本(由我的编译器)Haskell代码:
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FunctionalDependencies #-} import Data.Tuple.OneTuple ------------------------------ -- data types ------------------------------ data Vector a = Vector {x :: a, y :: a, z :: a} deriving (Show) -- the Vector_testid is used as wrapper over a function "testid". newtype Vector_testid a = Vector_testid a ------------------------------ -- sample function, which is associated to data type Vector ------------------------------ testid (v :: Vector a) x = x ------------------------------ -- problematic function (described later) ------------------------------ testx x = call (method1 x) $ OneTuple "test" ------------------------------ -- type classes ------------------------------ -- type class used to access "method1" associated function class Method1 cls m func | cls -> m, cls -> func where method1 :: cls -> m func -- simplified version of type class used to "evaluate" functions based on -- their input. For example: passing empty tuple as first argument of `call` -- indicates evaluating function with default arguments (in this example -- the mechanism of getting default arguments is not available) class Call a b where call :: a -> b ------------------------------ -- type classes instances ------------------------------ instance (out ~ (t1->t1)) => Method1 (Vector a) Vector_testid out where method1 = (Vector_testid . testid) instance (base ~ (OneTuple t1 -> t2)) => Call (Vector_testid base) (OneTuple t1 -> t2) where call (Vector_testid val) = val ------------------------------ -- example usage ------------------------------ main = do let v = Vector (1::Int) (2::Int) (3::Int) -- following lines equals to a pseudocode of ` v.method1 "test" ` -- OneTuple is used to indicate, that we are passing single element. -- In case of more or less elements, ordinary tuples would be used. print $ call (method1 v) $ OneTuple "test" print $ testx v
代码编译并与GHC 7.6一起正常工作.当我尝试使用GHC 7.7编译它时,我收到以下错误:
debug.hs:61:10: Illegal instance declaration for ?Method1 (Vector a) Vector_testid out’ The liberal coverage condition fails in class ?Method1’ for functional dependency: ?cls -> func’ Reason: lhs type ?Vector a’ does not determine rhs type ?out’ In the instance declaration for ?Method1 (Vector a) Vector_testid out’
该错误是由检查功能依赖性可以做什么的新规则引起的,即liberal coverage condition
(据我所知,coverage condition
通过使用可以放宽-XUndecidableInstances
)
一些人试图解决问题
我试图通过将定义更改Method1
为:来克服此问题:
class Method1 cls m func | cls -> m where method1 :: cls -> m func
这解决了功能依赖的问题,但后面的行:
testx x = call (method1 x) $ OneTuple "test"
不再允许,导致编译错误(在7.6和7.7版本中):
Could not deduce (Method1 cls m func0) arising from the ambiguity check for ?testx’ from the context (Method1 cls m func, Call (m func) (OneTuple [Char] -> s)) bound by the inferred type for ?testx’: (Method1 cls m func, Call (m func) (OneTuple [Char] -> s)) => cls -> s at debug.hs:50:1-44 The type variable ?func0’ is ambiguous When checking that ?testx’ has the inferred type ?forall cls (m :: * -> *) func s. (Method1 cls m func, Call (m func) (OneTuple [Char] -> s)) => cls -> s’ Probable cause: the inferred type is ambiguous
编辑:
使用类型族也是不可能解决这个问题的(据我所知).如果我们Method1
用以下代码(或simmilar)替换类型类和实例:
class Method1 cls m | cls -> m where type Func cls method1 :: cls -> m (Func cls) instance Method1 (Vector a) Vector_testid where type Func (Vector a) = (t1->t1) method1 = (Vector_testid . testid)
我们会得到明显的错误Not in scope: type variable ?t1’
,因为类型族不允许使用类型,这种类型不会出现在类型表达式的LHS上.
最后一个问题
如何让这个想法在GHC 7.7下运作?我知道新的liberal coverage condition
允许GHC开发人员在类型检查方面取得了一些进展,但它应该能够以某种方式在GHC 7.6中使用从不编译器版本的端口构思.
(不强迫我的DSL用户引入任何其他类型 - 到目前为止的一切,比如类型类实例,我正在使用Template Haskell进行创建)
这不是GHC 7.7中的错误.这是一个长期存在的bug GHC当它允许违反函数依赖的情况.幸运的是,这个问题终于得到了解决.GHC 7.7发出的错误消息非常详细,指出了您的实例的问题Method1 (Vector a) Vector_testid out
.回想一下功能依赖的含义.特定
class C a b | a -> b
因此,如果类型a
,b
并且b1
是这样,C a b
并且C a b1
两者都持有,那么它必须是真实的b
并且b1
是相同的.我们来看看你的实例:
Method1 (Vector a) Vector_testid (t1->t1)
如果我们有种类b
和b1
满足Method1 (Vector Int) Vector_testid (b->b)
和Method1 (Vector a) Vector_testid (b1->b1)
,什么都意味着b
与b1
必须是相同的.因此你的实例是不正确的.GHC 7.6和之前接受该计划的事实是GHC中一个众所周知的错误(每年讨论一次).
你似乎想要的是定义类似的东西
Method1 (Vector a) Vector_testid (forall t. t -> t)
唉,虽然存在许多工作环境,但不允许使用此语法.例如,一个涉及Apply类(例如,参见HList文件).更简单的方法如下
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FunctionalDependencies #-} -- import Data.Tuple.OneTuple newtype OneTuple x = OneTuple x deriving Show ------------------------------ -- data types ------------------------------ data Vector a = Vector {x :: a, y :: a, z :: a} deriving (Show) -- testx x = call (method1 x) $ OneTuple "test" testx x = call x Method1 $ OneTuple "test" -- associate methods to classes class Methods cls m x y | cls m x -> y where call :: cls -> m -> x -> y instance Methods (Vector a) Method1 x x where call self _ x = x data Method1 = Method1 -- method label