【问题标题】:Modeling exchange in HaskellHaskell 中的建模交换
【发布时间】:2017-02-07 06:24:11
【问题描述】:

我是 Haskell 的新手,我正在寻找为证券交易所库建模。它是一个库,因此由用户定义细节。我打算使用它的方式是让用户定义这样的东西。

data MyExchange = MyExchange { name :: ExchangeName
                             , base :: Currency
                             , quote :: Currency }
                             deriving (Eq, Show)

instance Exchange MyExchange

data MyExchangeBookMessage = 
        MyExchangeBookMessage { time :: Time
                              , exchange :: MyExchange
                              , price :: Price 
                              , side :: Side
                              , amount :: Maybe Amount }
                              deriving (Eq, Show)

instance ExchangeBookMessage MyExchangeBookMessage

我尝试了以下方法,但立即遇到了类型类的一些限制。下面是代码和错误信息。具体来说,用多种类型参数化类型类的替代方法是什么?

这是库的代码

module Lib where

data Side = Buy | Sell deriving (Eq, Show)

newtype Amount = Amount Rational deriving (Eq, Show)

newtype Price = Price Rational deriving (Eq, Show)

newtype Currency = Currency String deriving (Eq, Show)

newtype Time = Time Integer deriving (Eq, Show)

type ExchangeName = String

class Exchange a where
  name :: a -> ExchangeName
  base :: a -> Currency
  quote :: a -> Currency


class Message a where
  time :: a -> Time

class (Message a, Exchange e) => ExchangeMessage a e where
  exchange :: a -> e

class ExchangeMessage a b => BookMessage a b where
  price :: a -> Price
  side :: a -> Side
  amount :: a -> Maybe Amount

还有错误信息:

src/Lib.hs:22:1: error:
    • Too many parameters for class ‘ExchangeMessage’
      (Use MultiParamTypeClasses to allow multi-parameter classes)
    • In the class declaration for ‘ExchangeMessage’

以后我希望能够实现这样的类型类:

class Strategy s where
  run (Message m, Action a) => s -> m -> a

Strategy 实现中,run 函数将采用抽象消息 m,将其与相关的 Message 数据构造函数进行模式匹配并返回特定操作。

我正在移植一些 Scala 代码。在 Scala 中,我在底部使用了带有具体案例类的特征层次结构:

trait Exchange { 
  def name: String
  def base: Currency
  def quote: Currency 
}

case class MyExchange(base: Currency, quote: Currency) {
  val name = "my-exchange"
}

trait Message {
  def time: Long
}

trait ExchangeMessage extends Message {
  def exchange: Exchange
}

trait BookMessage extends ExchangeMessage {
  def price: Double
  def side: Side
  def amount: Option[Double]
}

case class MyBookMessage(time: Long, price: Double, side: Side, amount: Option[Double]) {
  def exchange: Exchange = MyExchange(...)
}

【问题讨论】:

  • 有一个建议给Use MultiParamTypeClasses to allow multi-parameter classes,建议你采纳。将{-# LANGAUGE MultiParamTypeClasses #-}放在文件的第一行以启用它。
  • 这会“工作”,但是当您尝试使用BookMessage 的任何方法时会遇到其他问题。我不确定b 应该在那里......
  • @luqui: BookMessage extends ExchangeMessage: b 是来自ExchangeMessageExchange 的类型。
  • @luqui:我不确定这是否是对该域建模的最佳方式。如果没有更好的处理方法,我会求助于MultiParamTypeClasses
  • 我想到了另外一点:Haskell 不是一种面向对象的语言,因此过渡可能会很艰难。由于您的大脑不习惯于函数式建模,您可能会尝试的一种技术是直接编写库的一些客户端代码,然后重构直到找到它应该使用的库。

标签: haskell types algebra domain-model


【解决方案1】:

首先,听从 GHC 的建议,在文件顶部启用MultiParamTypeCLasses

{-# LANGUAGE MultiParamTypeClasses #-}

这是一个非常常见的扩展,它可以解决眼前的问题。

然而似乎存在一些建模问题,如果您继续进行此设计,您肯定会遇到一些您没有预料到的问题。我可以深入了解您的代码含义的所有细节,但我不确定这是否会很有帮助。相反,我认为我只会为您指出正确的方向,即使用data 记录而不是类型类。 Haskell 类型类与其他 OO 语言中的类不对应,这让许多初学者感到困惑。但我认为你想这样建模:

data Exchange = Exchange 
    { name :: ExchangeName
    , base :: Currency
    , quote :: Currency 
    }

data Message = Message
    { time :: Time }

-- etc.

这将为您简化一切,这更像是 OO 类而不是您的模型。请记住,记录可以将函数和其他复杂的数据结构作为字段,这就是您获得虚拟方法的模拟的方式,例如:

data MessageLogger = MessageLogger
    { log :: String -> IO () }

【讨论】:

  • 因此,来自不同交易所的具体交易所和消息可能具有不同的字段,例如data MyExchange1 = MyExchange1 ExchangeName Currency Currency OpenTime CloseTimedata MyExchange2 = MyExchange2 ExchangeName Currency Currency Precision 但我想在它们上运行常用方法,并像对待 OOP 中的抽象类一样对待它们。 Haskell 中的类比是什么?
  • @ak。那听起来像是对类型类的很好使用,我认为您只是在模型中过度使用了它。从具体开始,当您注意到类中的重复因素时,这将确保您的抽象有良好的动机。很难给出具体的建议,而不会看到您要特别避免的重复。
  • 我在有关使用这些类型类的问题中添加了更多信息。基本上,我正在构建一个库,我在其中定义了一些抽象接口,具体细节将出现在用户应用程序中。
  • 您也可以通过组合来保留常用功能。您只需在调用站点提取相关子部分。
【解决方案2】:

首先,您可能无法为ExchangeMessage 编写类实例。原因是exchange 函数必须能够返回any 类型e。如果你想保持这种方式,你需要提供一种方法来建立一个任意的交换!

class Exchange a where
  name :: a -> ExchangeName
  base :: a -> Currency
  quote :: a -> Currency
  build :: Time -> a

这是build 唯一可能的签名,因为您可以从交换中知道它有一个可以查询的Time,它可能没用。

我认为更好的设计是为您定义的所有这些类提供具体类型。例如:

data Exchange = Exchange { getName  :: ExchangeName
                         , getBase  :: Currency
                         , getQuote :: Currency
                         } deriving (Show, Eq)

然后,一旦您编写了适用于这些具体类型的函数,您就可以:

  • 例如,编写MyExchange -> Exchange 类型的函数,以适应期望Exchange 的函数
  • 使用经典镜头,以便直接编写会消耗任意类型的函数

总而言之,对于那种应用程序,如果您想对类型感兴趣,我建议您对货币使用幻像类型,这样您就可以静态强制执行,例如,您只能计算两个金额的总和使用相同货币的钱。使用类型类来模仿 OO 的习惯不会产生易于使用的 API 或清晰的代码。

【讨论】:

  • @bartabelle: 至少有一个定义函数MyExchange -> ExchangeMyExchangeMessage -> Message 的类型类会有好处吗?原因是我希望能够获取消息列表[Message] 并以某种方式折叠它们,其中折叠的谓词将模式匹配消息与特定数据构造函数。这有意义吗?
  • 那个类型类可能有用,我无法评论您的具体用例。但是,创建新类型类是例外而不是规则,例如参见astar API。您会注意到没有诸如HasNeighborsLocalizedDistanceable 之类的类型类。只是简单的函数,API 真的很好。
猜你喜欢
  • 1970-01-01
  • 2019-02-07
  • 1970-01-01
  • 1970-01-01
  • 2020-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多