构建GUI小部件类的层次结构几乎是面向对象编程的标准练习。你有一些抽象的Widget
类,有一个可以包含其他小部件的小部件的抽象子类,然后你有大量的支持文本显示的小部件的抽象类,支持作为输入焦点的小部件,具有布尔状态的小部件,直到实际的具体类,如按钮,滑块,滚动条,复选框等。
我的问题是:在Haskell中执行此操作的最佳方式是什么?
有许多事情使构建Haskell GUI变得困难,但不是是我问题的一部分。在Haskell中进行交互式I / O有点棘手。实现GUI几乎总是意味着将包装器写入极低级别的C或C ++库。编写这样的包装器的人倾向于逐字复制现有的API(可能是任何知道包装库的人都会有宾至如归的感觉)。这些问题目前我不感兴趣。我纯粹感兴趣的是如何最好地模拟Haskell中的子类型多态性。
我们想要从我们假设的GUI库中获得什么样的属性?好吧,我们希望可以随时添加新的窗口小部件类型。 (换句话说,一组封闭的可能小部件并不好。)我们希望最大限度地减少代码重复。 (很多的小部件类型!)理想情况下,我们希望能够在必要时规定一个特定的小部件类型,同时也能够处理任何小部件的集合如果需要,请输入。
上述所有内容在任何自尊的OO语言中当然都是微不足道的。但是在Haskell中执行此操作的最佳方法是什么?我可以想到几种方法,但我不确定哪种方法会“最好”。
答案 0 :(得分:9)
wxHaskell GUI库充分利用幻像类型来构建窗口小部件层次结构。
这个想法如下:所有小部件共享相同的实现,即它们是指向C ++对象的外部指针。但是,这并不意味着所有小部件都需要具有相同的类型。相反,我们可以构建这样的层次结构:
type Object a = ForeignPtr a
data CWindow a
data CControl a
data CButton a
type Window a = Object (CWindow a)
type Control a = Window (CControl a)
type Button a = Control (CButton a)
这样,类型Control A
的值也匹配类型Window b
,因此您可以将控件用作窗口,但不能反过来。如您所见,子类型是通过嵌套类型参数实现的。
有关此技术的更多信息,请参阅Dan Leijen's paper on wxHaskell中的第5节。
注意,该技术似乎局限于小部件的实际表示是统一的,即总是相同的情况。但是,我有信心,通过一些思考,它可以扩展到小部件具有不同表示的情况。
特别是,观察是可以通过在数据类型中包含方法来建模面向对象,如下所示
data CWindow a = CWindow
{ close :: IO ()
, ...
}
data CButton a = CButton
{ onClick :: (Mouse -> IO ()) -> IO ()
, ...
}
子类型可以在这里保存一些样板,但不是必需的。
答案 1 :(得分:6)
要了解在Haskell中可以完成哪些OOP(例如子类型多态),您可以查看OOHaskell。这再现了各种强大的OOP类型系统的语义,保留了大多数类型推断。实际数据编码没有得到优化,但我怀疑类型系列可能允许更好的演示。
可以使用类型类对接口层次结构(例如Widget)进行建模。可以添加新实例,因此打开了一组具体小部件。如果您想要一个可能的小部件的特定列表,那么GADT可以是一个简洁的解决方案。
子类的特殊操作是向上转发和向下转换。
首先需要有一个Widgets集合,通常的结果是使用存在类型。如果您阅读HList library的所有位,还有其他有趣的解决方案。上传非常简单,编译器可以确定所有强制转换在编译时都有效。向下转换本质上是动态的,需要一些运行时类型信息支持,通常是Data.Typeable。给定类似于Typeable的东西,向下转换只是另一个类型类,结果包含在Maybe中以指示失败。
与大部分相关的样板都有,但QusiQuoting和Templating可以减少这个。类型推断仍然可以在很大程度上起作用。
我没有探索新的约束类型和类型,但它们增强了对向上转换和向下转换的存在性解决方案。