简体   繁体   English

创建绑定到Haskell中记录的方法

[英]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)
}
  1. What is the best way of generating "equivalent" Haskell code of the OO pseudocode I've provided? 生成我提供的OO伪代码的“等效”Haskell代码的最佳方法是什么? I want: 我想要:

    • to be able to translate non-mutable classes with methods (which can have default arguments) to Haskell's code. 能够将带有方法(可以有默认参数)的非可变类转换为Haskell的代码。 (preserving laziness, so I do not want to use ugly IORefs and mimic mutable data structures) (保留懒惰,所以我不想使用丑陋的IORefs和模仿可变数据结构)
    • not to force the user to explicitly write any types, so I can use all available Haskell mechanisms to allow automatic type inference - like using Template Haskell to automatically generate typeclass instances for given methods (etc.). 不要强制用户显式写任何类型,所以我可以使用所有可用的Haskell机制来允许自动类型推断 - 比如使用Template Haskell为给定方法自动生成类型类实例(等等)。
    • to be able to generate such code with my compiler, without the need of implementing my own type inferencer (or with my own type inferencer if there is no other solution) 能够使用我的编译器生成这样的代码,而不需要实现我自己的类型推理器(或者如果没有其他解决方案,则使用我自己的类型推理器)
    • the result code to produce fast binaries (be nicely optimized while compiling). 生成快速二进制文件的结果代码(在编译时可以很好地优化)。
  2. 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_Xf 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根据其参数的类型返回特定类型的结果?

Solution

Ok, this is how you can mimic the desired behavior. 好的,这就是你可以模仿所需行为的方式。 You'll need two extensions, namely RankNTypes and ExistentialQuantification . 您需要两个扩展名,即RankNTypesExistentialQuantification

First, put rank-2 types into X and Y . 首先,将rank-2类型放入XY 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: XY实例很简单:

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类XY的对象:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM