13

如何向 Haskell 数据构造函数添加输入检查?假设我有

import Data.Time.Calendar

data SchedulePeriod = SchedulePeriod { startDate :: Day
    , endDate :: Day
    , accrualStart :: Day
    , accrualEnd :: Day
    , resetDate :: Day
    , paymentDate :: Day
    , fraction :: Double }
    deriving (Show)

我想施加一个约束startDate < endDate。有没有办法在不创建抽象数据类型的情况下做到这一点?

4

2 回答 2

17

标准方法是使用智能构造函数在创建值之前检查先决条件,并且不导出它使用的真实构造函数。当然,正如您所说,这是创建一个抽象数据类型。

在没有智能构造函数的情况下实现此目的的唯一方法是非常邪恶的类型系统黑客(并且您将无法使用标准Day类型)。

于 2012-04-15T09:55:58.380 回答
15

接受 ehird 的回答。我只是写这个,所以我可以解释我在评论中提到的智能析构函数,我无法在评论中解释。

假设您有以下类型:

data T x y z = A | B x | C y z

ehird 已经解释了如何抽象出构造函数,这只是为了提供“智能”构造函数。就像您提到的那样,这需要隐藏构造函数,然后您不能使用它们进行模式匹配。但是,您可以使用“智能”析构函数来解决这个问题,这相当于对每个可能的构造函数进行模式匹配。

为了解释这一点,让我们首先从如果构造函数被暴露,我们将如何编写一个 T 类型的函数开始:

myFunction :: T x y z -> d
myFunction t = case t of
    A     -> f1
    B x   -> f2 x
    C y z -> f3 y z

我们从函数的类型签名中知道f1f2和的类型f3必须是:

f1 :: d
f2 :: x -> d
f3 :: y -> z -> d

因此,如果我们要概括myFunction为一个智能析构函数,我们只需将f1f2f3作为参数传递给它:

smartDestructor :: d -> (x -> d) -> (y -> z -> d) -> t -> d
smartDestructor f1 f2 f3 t = case t of
    A     -> f1
    B x   -> f2 x
    C y z -> f3 y z

So if you export smartDestructor, then people can basically pattern-match against your type without needing access to the constructors.

If you've ever used the maybe or either functions, before, then you've used a smart destructor, although in those cases the constructors are not hidden, so they are mainly provided as convenience functions:

maybe :: b -> (a -> b) -> Maybe a -> b
maybe f1 f2 m = case m of
    Nothing -> f1
    Just  a -> f2 x

either :: (a -> c) -> (b -> c) -> Either a b -> c
either f1 f2 e = case e of
    Left  a -> f1 a
    Right b -> f2 b

In your case, the purpose of the smart destructor is just so you can hide the constructors and not expose the constructors.

于 2012-04-16T02:50:57.573 回答