我正在创建一个惰性的、功能性的DSL,它允许用户使用方法定义非可变结构(类似于 OO 语言中的类,但它们不是可变的)。我将这种语言的代码编译为 Haskell 代码。
最近我遇到了这个工作流程的问题。我不想强迫用户编写显式类型,所以我想大量使用 Haskell 的类型推断器。当我翻译一个函数时会出现问题,该函数多次调用“对象”的多态方法,每次传递不同的参数类型,如下所示:
(伪代码):
class X {
def method1(a, b) {
(a, b) // return
}
}
def f(x) {
print (x.method1(1,2)) // call method1 using Ints
print (x.method1("hello", "world")) // call method1 using Strings
}
def main() {
x = X() // constructor
f(x)
}
生成我提供的 OO 伪代码的“等效”Haskell 代码的最佳方法是什么?我想:
- 能够将具有方法(可以具有默认参数)的非可变类转换为 Haskell 的代码。(保持懒惰,所以我不想使用丑陋
IORefs
和模仿可变数据结构) - 不要强迫用户显式地编写任何类型,所以我可以使用所有可用的 Haskell 机制来允许自动类型推断——比如使用Template Haskell为给定的方法(等)自动生成类型类实例。
- 能够使用我的编译器生成这样的代码,而无需实现我自己的类型推断器(或者如果没有其他解决方案,则使用我自己的类型推断器)
- 生成快速二进制文件的结果代码(在编译时进行很好的优化)。
- 能够将具有方法(可以具有默认参数)的非可变类转换为 Haskell 的代码。(保持懒惰,所以我不想使用丑陋
如果下面提议的工作流程是最好的工作流程,我们如何修复提议的 Haskell 代码,以使两者
f con_X
都能f con_Y
工作?(见下文)
目前工作状态
伪代码可以很容易地翻译成下面的 Haskell 代码(它是手写的,不是生成的,更容易阅读):
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
-- class and its constructor definition
data X a = X { _methodx1 :: a } deriving(Show)
con_X = X { _methodx1 = (\a b -> (a,b)) }
-- There can be other classes with "method1"
class F_method1 cls sig where
method1 :: cls sig -> sig
instance F_method1 X a where
method1 = _methodx1
f x = do
print $ (method1 x) (1::Int) (2::Int)
print $ (method1 x) ("Hello ") ("World")
main = do
let x = con_X
f x
上面的代码不起作用,因为 Haskell 无法推断rank高于 1的隐式类型,例如f
. 在#haskell irc上讨论了一下,找到了部分解决方案,即我们可以翻译如下伪代码:
class X {
def method1(a, b) {
(a, b) // return
}
}
class Y {
def method1(a, b) {
a // return
}
}
def f(x) {
print(x.method1(1, 2))
print(x.method1("hello", "world"))
}
def main() {
x = X()
y = Y()
f(x)
f(y)
}
到 Haskell 代码:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
data Y a = Y { _methody1 :: a } deriving(Show)
data X a = X { _methodx1 :: a } deriving(Show)
con_X = X { _methodx1 = (\a b -> (a,b)) }
con_Y = Y { _methody1 = (\a b -> a) }
class F_method1 cls sig where
method1 :: cls sig -> sig
instance F_method1 X a where
method1 = _methodx1
instance F_method1 Y a where
method1 = _methody1
f :: (F_method1 m (Int -> Int -> (Int, Int)),
F_method1 m (String -> String -> (String, String)))
=> (forall a. (Show a, F_method1 m (a -> a -> (a,a))) => m (a -> a -> (a, a))) -> IO ()
f x = do
print $ (method1 x) (1::Int) (2::Int)
print $ (method1 x) ("Hello ") ("World")
main = do
f con_X
-- f con_Y
这段代码确实有效,但仅适用于数据类型X
(因为它已经硬编码了method1
in 签名的返回类型f
。该行f con_Y
不起作用。此外,有没有办法自动生成的签名f
或者我必须编写自己的类型推论者?
更新
Crazy FIZRUK 提供的解决方案确实适用于这种特定情况,但是使用existential data types
, 比如data Printable = forall a. Show a => Printable a
强制所有具有特定名称的方法(即“method1”)在所有可能的类中具有相同的结果类型,这不是我想要实现的.
下面的例子清楚地表明了我的意思:
(伪代码):
class X {
def method1(a, b) {
(a, b) // return
}
}
class Y {
def method1(a, b) {
a // return
}
}
def f(x) {
print(x.method1(1, 2))
x.method1("hello", "world") // return
}
def main() {
x = X()
y = Y()
print (f(x).fst()) // fst returns first tuple emenet and is not defined for string
print (f(y).length()) // length returns length of String and is not defined for tuples
}
是否可以将此类代码转换为 Haskell,允许f
根据其参数的类型返回特定类型的结果?