The idea
我正在写一个DSL,编译为 Haskell。
该语言的用户可以定义自己的不可变数据结构和关联函数。我所说的关联函数是指属于数据结构的函数。
例如,用户可以编写(用“pythonic”伪代码):
data Vector a:
x,y,z :: a
def method1(self, x):
return x
(这相当于下面的代码,但也显示了关联函数的行为类似于具有开放世界假设的类型类):
data Vector a:
x,y,z :: a
def Vector.method1(self, x):
return x
在这个例子中,method1
是一个与关联的函数Vector
数据类型,并且可以像这样使用v.testid(5)
(where 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
to:
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
EDIT:
使用类型族来解决这个问题也是不可能的(据我所知)。如果我们更换Method1
使用以下代码(或类似代码)键入类和实例:
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 生成)