1

我正在使用 javascript 中的函数式编程构建基于域驱动设计的应用程序。我已将 sanctuary.js 生态系统作为我选择的工具,但我在建模类型方面面临一些挑战。

为了把事情放在上下文中,让我们以下面的代码为例:

  const { create } = require('sanctuary')
  const $ = require('sanctuary-def')
  const def = create({
    checkTypes: process.env.NODE_ENV === 'development',
    env: $.env
  })

  const Currency = $.EnumType
    ('Currency')
    ('http://example.com')
    (['USD', 'EUR'])

  const Payment = $.RecordType({
    amount: $.PositiveNumber,
    currency: Currency,
    method: $.String
  })

我的困惑点如下:

  1. 如何使用 sanctuary-def 定义简单类型?从上面的例子中,如果我想定义一个Amount类型并在 Payment RecordType 定义中使用它,我该怎么做呢?我必须为此定义另一个 RecordType 吗?
  2. 我在这里可能错了,但到目前为止我的理解是RecordType相当于函数式编程中的产品类型。使用 sanctuary.js 定义 sum 类型的方法是什么?上面的EnumType很接近,但似乎是用于简单值,而不是其他类型。更像是,如果我有另外两种类型CashCard,我将如何将另一种类型PaymentMethod 建模为CashCard之间的选择?

我很高兴有任何指示。我对函数式编程和 sanctuary.js 都很陌生,所以如果我缺少明显的东西,我会很感激朝着正确的方向轻推。

非常感谢。

4

1 回答 1

1

让我们考虑付款方式。为了简单起见,我们假设付款方式是现金,或者是带有关联卡号的信用卡/借记卡。在 Haskell 中,我们可以像这样定义类型及其数据构造函数:

data PaymentMethod = Cash | Card String

为了PaymentMethod使用 sanctuary-def 进行定义,我们需要知道CashCarddata 构造函数是如何实现的。可以自由地手动定义这些或使用诸如Daggy 之类的库。让我们手写它们:

//    Cash :: PaymentMethod
const Cash = {
  '@@type': 'my-package/PaymentMethod',
  'tagName': 'Cash',
};

//    Card :: String -> PaymentMethod
const Card = number => ({
  '@@type': 'my-package/PaymentMethod',
  'tagName': 'Card',
  'number': number,
});

定义了数据构造函数后,我们可以使用它$.NullaryType来定义PaymentMethod类型:

const $ = require ('sanctuary-def');
const type = require ('sanctuary-type-identifiers');

//    PaymentMethod :: Type
const PaymentMethod = $.NullaryType
  ('PaymentMethod')
  ('https://example.com/my-package#PaymentMethod')
  ([])
  (x => type (x) === 'my-package/PaymentMethod');

请注意,因为每个PaymentMethod值都将具有特殊@@type属性,所以我们可以使用type来确定任意 JavaScript 值是否是该PaymentMethod类型的成员。

我意识到在 JavaScript 中近似一行 Haskell 是相当复杂的。我希望这个例子展示了拼图的各个部分是如何组合在一起的。


我们可能想像这样定义一个case-folding函数PaymentMethod

//    foldPaymentMethod :: a -> (String -> a) -> PaymentMethod -> a
const foldPaymentMethod = cash => card => paymentMethod => {
  switch (paymentMethod.tagName) {
    case 'Cash': return cash;
    case 'Card': return card (paymentMethod.number);
  }
};

定义了Cash, CardfoldPaymentMethod我们就能够构造和解构PaymentMethod值,而不必担心实现细节。例如:

> foldPaymentMethod ('Cash') (number => `Card (${S.show (number)})`) (Cash)
'Cash'

> foldPaymentMethod ('Cash') (number => `Card (${S.show (number)})`) (Card ('2468101214161820'))
'Card ("2468101214161820")'
于 2020-06-10T10:32:59.810 回答