[英]Creating methods bound to records in Haskell
I'm creating a lazy, functional DSL , which allows users to define non-mutable structures with methods (something like classes from OO languages, but they are not mutable). 我正在创建一个懒惰的,功能性的DSL ,它允许用户使用方法定义非可变结构(类似于OO语言的类,但它们不可变)。 I compile the code of this language to Haskell code. 我将这种语言的代码编译为Haskell代码。
Recently I faced a problem with this workflow. 最近我遇到了这个工作流程的问题。 I do not want to force the user to write explicit types, so I want to heavily use Haskell's type inferencer. 我不想强迫用户编写显式类型,所以我想大量使用Haskell的类型推理器。 The problem occurs when I'm translating a function, which calls multiple times a polymorphic method of an "object", passing each time different argument types, like here: 当我正在翻译一个函数时会出现问题,该函数多次调用“对象”的多态方法,每次都传递不同的参数类型,如下所示:
(pseudocode): (伪):
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)
}
What is the best way of generating "equivalent" Haskell code of the OO pseudocode I've provided? 生成我提供的OO伪代码的“等效”Haskell代码的最佳方法是什么? I want: 我想要:
IORefs
and mimic mutable data structures) (保留懒惰,所以我不想使用丑陋的IORefs
和模仿可变数据结构) If the proposed below workflow is the best possible one, how can we fix the proposed Haskell code, in such a way, that both f con_X
and f con_Y
will work? 如果下面提出的工作流程是最好的,那么我们如何以这样的方式修复建议的Haskell代码, f con_X
和f con_Y
都可以工作? (see below) (见下文)
Current work status 目前的工作状态
The pseudocode can be easily translated into following Haskell code (it is hand-written, not generated, to be simpler to read): 伪代码可以很容易地转换成下面的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
The above code does not work, because Haskell cannot infer implicit types of rank higher than 1, like the type of f
. 上面的代码不工作,因为Haskell中不能推断隐含的类型的秩大于1,类似的类型f
。 After a bit of discussion on #haskell irc, a partial solution was found, namely we can translate the following pseudo code: 在对#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)
}
to Haskell code: 到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
This code indeed works, but only for data type X
(because it has hardcoded the return type of method1
in signature of f
. The line f con_Y
does not work. Additionally, is there any way to automatically generate the signature of f
or do I have to write my own type inferencer for that? 这段代码确实有效,但仅适用于数据类型X
(因为它在f
签名中硬编码了method1
的返回类型。行f con_Y
不起作用。另外,有没有办法自动生成f
的签名或者我是为此必须写我自己的类型推理器?
UPDATE UPDATE
The solution provided by Crazy FIZRUK indeed works for this specific case, but using existential data types
, like data Printable = forall a. Show a => Printable a
Crazy FIZRUK提供的解决方案确实适用于这种特定情况,但使用existential data types
,如data Printable = forall a. Show a => Printable a
data Printable = forall a. Show a => Printable a
force all methods with a specific name (ie. "method1") to have the same result type across all possible classes, which is not what I want to achieve. data Printable = forall a. Show a => Printable a
强制所有具有特定名称的方法(即“method1”)在所有可能的类中具有相同的结果类型,这不是我想要实现的。
The following example clearly shows what I mean: 以下示例清楚地显示了我的意思:
(pseudocode): (伪):
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
}
Is it possible to translate such code to Haskell, allowing f
to return result of a specific type based on type of its argument? 是否可以将此类代码转换为Haskell,允许f
根据其参数的类型返回特定类型的结果?
Ok, this is how you can mimic the desired behavior. 好的,这就是你可以模仿所需行为的方式。 You'll need two extensions, namely RankNTypes
and ExistentialQuantification
. 您需要两个扩展名,即RankNTypes
和ExistentialQuantification
。
First, put rank-2 types into X
and Y
. 首先,将rank-2类型放入X
和Y
Because it is the property of class method (I mean OO class here): 因为它是类方法的属性(我的意思是OO类):
data X = X { _X'method :: forall a b. a -> b -> (a, b) }
data Y = Y { _Y'method :: forall a b. a -> b -> a }
Next, you need to specify what properties have the return type of "method". 接下来,您需要指定哪些属性具有返回类型“method”。 This is because when calling method
in f
you don't know the implementation of class you're using. 这是因为在f
调用method
,你不知道你正在使用的类的实现。 You can either constraint return type with a typeclass or, probably, use Data.Dynamic
(I'm not sure about last). 您可以使用类型类约束返回类型,或者可能使用Data.Dynamic
(我不确定最后一个)。 I will demonstrate the first variant. 我将演示第一个变体。
I will wrap the constraint in an existential type Printable
: 我将约束包装在一个存在类型Printable
:
data Printable = forall a. Show a => Printable a
instance Show Printable where
show (Printable x) = show x
Now we can define the desired interface that we will use in type signature of f
: 现在我们可以定义我们将在f
类型签名中使用的所需接口:
class MyInterface c where
method :: forall a b. (Show a, Show b) => (a, b) -> c -> Printable
It is important that the interface is also polymorphic. 接口也是多态的很重要。 I placed arguments in a tuple to mimic also usual OOP syntax (see below). 我将参数放在一个元组中,以模仿通常的OOP语法(见下文)。
Instances for X
and Y
are straightforward: 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
Now f
can be written simply: 现在f
可以简单地写:
f :: MyInterface c => c -> IO ()
f obj = do
print $ obj & method(1, 2)
print $ obj & method("Hello, ", "there")
Now we can create some objects of OO classes X
and Y
: 现在我们可以创建一些OO类X
和Y
的对象:
objX :: X
objX = X $ λa b -> (a, b)
objY :: Y
objY = Y $ λa b -> a
And run the thing! 并运行的东西!
main :: IO ()
main = do
f objX
f objY
Profit! 利润!
Helper function for convenient syntax: 助手功能,方便语法:
(&) :: a -> (a -> b) -> b
x & f = f x
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.