在 Haskell 中定义数据类型时如何指定约束

Jir*_*Jir 0 haskell types

我理解函数式语言中“使非法状态不可表示”的原则,但在实践中我经常遇到困难。

作为一个例子,我正在尝试定义一个交易簿模型。我定义了这些数据类型:

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

data Order =
    Order
    {
      orderSide      :: Side
    , orderQuantity  :: Int
    , orderPrice     :: Float
    }
    deriving (Eq)

data Book =
    Book
        { buy  :: [Order]
        , sell :: [Order]
        }
    deriving (Show)
Run Code Online (Sandbox Code Playgroud)

基本上,这意味着 aBook是一种具有两个订单列表的类型,每侧一个。

然而,这是完全有效的:

ghci> o = Order Sell 10 92.22
ghci> Book [o] []
Book {buy = [Order {orderSide = Sell, orderQuantity = 10, orderPrice = 92.22}], sell = []}
Run Code Online (Sandbox Code Playgroud)

这也是完全错误的。

我如何表达只有Buy订单应该发送给买方而Sell订单发送给另一方的约束?

Nou*_*are 5

您可以通过多种不同的方式来做到这一点,但我认为这是最简单的:

data OrderInfo =
    OrderInfo
    {
      orderQuantity  :: Int
    , orderPrice     :: Float
    }
    deriving (Eq)

data BuyOrder = BuyOrder OrderInfo
data SellOrder = SellOrder OrderInfo

data Book =
    Book
        { buy  :: [BuyOrder]
        , sell :: [SellOrder]
        }
    deriving (Show)
Run Code Online (Sandbox Code Playgroud)

不幸的是,这使得编写适用于卖出和买入订单的通用函数变得不可能。您可以通过引入另一种类型和一些简单的转换函数来恢复这种能力:

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

data SomeOrder = SomeOrder Side OrderInfo

fromBuy :: BuyOrder -> SomeOrder
fromSell :: SellOrder -> SomeOrder
buyOrSell :: SomeOrder -> Either BuyOrder SellOrder
Run Code Online (Sandbox Code Playgroud)

您可以通过使用 GADT 和 DataKind 来避免这种开销:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}

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

data SSide a where
  SBuy :: SSide Buy
  SSell :: SSide Sell
deriving instance Show (SSide a)
deriving instance Eq (SSide a)

data Order side =
    Order
    {
      orderSide      :: SSide side
    , orderQuantity  :: Int
    , orderPrice     :: Float
    } deriving (Show, Eq)

data Book =
    Book
        { buy  :: [Order Buy]
        , sell :: [Order Sell]
        }
    deriving Show
Run Code Online (Sandbox Code Playgroud)