oop - 在 Haskell 中创建绑定(bind)到记录的方法

标签 oop haskell types polymorphism higher-rank-types

我正在创建一个懒惰的、实用的 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 代码,使 f con_Xf 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根据参数的类型返回特定类型的结果?

    最佳答案

    解决方案

    好的,这就是您可以模仿所需行为的方式。您需要两个扩展名,即 RankNTypesExistentialQuantification .

    首先,将rank-2类型放入XY .因为它是类方法的属性(我这里指的是OO类):

    data X = X { _X'method :: forall a b. a -> b -> (a, b) }
    data Y = Y { _Y'method :: forall a b. a -> b -> a }
    

    接下来,您需要指定哪些属性具有“方法”的返回类型。这是因为当调用 methodf你不知道你正在使用的类的实现。您可以使用类型类约束返回类型,或者可能使用 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 类的对象 XY :
    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/

    相关文章:

    java - 在可变对象的 getter 中进行深度复制

    scala - 具有通用返回类型的可选函数参数

    javascript - 如何让具体化固定 Action 按钮在 Angular 2 中工作

    c# Reflection - 查找集合的通用类型

    php - 如何获取创建当前对象的类?

    javascript - 静态私有(private)字段 Javascript

    haskell - Haskell中的种类级别身份

    haskell - 常量超出范围但定义明确(或者我相信)

    objective-c - 在 NSObject 子类的自定义 init 方法中使用 self = [super init]

    string - 为什么 Haskell 在处理字符串时分配大量内存?