Haskell 中的建模交流

Modeling exchange in Haskell

我是 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 数据构造函数和 return 特定操作进行模式匹配。

我正在移植一些 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(...)
}

首要任务,采纳 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 () }

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

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

这是 build 唯一可能的签名,因为你从交易所知道的只是它有一个 Time 你可以查询,它可能没用。

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

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

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

  • 编写 MyExchange -> Exchange 类型的函数,例如,调整需要 Exchange
  • 的函数
  • 使用classy镜头,直接写出会消耗任意类型的函数

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