我正在创建一个懒惰的、实用的 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)
}
IORefs
并模仿可变数据结构)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
的签名中硬编码了 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
根据参数的类型返回特定类型的结果?
最佳答案
解决方案
好的,这就是您可以模仿所需行为的方式。您需要两个扩展名,即 RankNTypes
和 ExistentialQuantification
.
首先,将rank-2类型放入X
和 Y
.因为它是类方法的属性(我这里指的是OO类):
data X = X { _X'method :: forall a b. a -> b -> (a, b) }
data Y = Y { _Y'method :: forall a b. a -> b -> a }
接下来,您需要指定哪些属性具有“方法”的返回类型。这是因为当调用
method
在 f
你不知道你正在使用的类的实现。您可以使用类型类约束返回类型,或者可能使用 Data.Dynamic
(我不确定最后一个)。我将演示第一个变体。我将约束包装在一个存在类型
Printable
:data Printable = forall a. Show a => Printable a
instance Show Printable where
show (Printable x) = show x
现在我们可以定义我们将在
f
的类型签名中使用的所需接口(interface)。 :class MyInterface c where
method :: forall a b. (Show a, Show b) => (a, b) -> c -> Printable
重要的是接口(interface)也是多态的。我将参数放在一个元组中以模仿通常的 OOP 语法(见下文)。
X
的实例和 Y
很简单:instance MyInterface X where
method args x = Printable . uncurry (_X'method x) $ args
instance MyInterface Y where
method args y = Printable . uncurry (_Y'method y) $ args
现在
f
可以简单地写成:f :: MyInterface c => c -> IO ()
f obj = do
print $ obj & method(1, 2)
print $ obj & method("Hello, ", "there")
现在我们可以创建一些 OO 类的对象
X
和 Y
:objX :: X
objX = X $ λa b -> (a, b)
objY :: Y
objY = Y $ λa b -> a
和运行的东西!
main :: IO ()
main = do
f objX
f objY
利润!
方便语法的辅助函数:
(&) :: a -> (a -> b) -> b
x & f = f x
关于oop - 在 Haskell 中创建绑定(bind)到记录的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19484232/