sanctuary.js 中的数据建模挑战
Data modelling challenges in sanctuary.js
我正在 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
})
我的困惑点如下:
- 如何使用 sanctuary-def 定义简单类型?从上面的示例中,如果我想定义一个 Amount 类型并在 Payment RecordType 定义中使用它,我将如何去做?我必须为此定义另一个 RecordType 吗?
- 我这里可能是错的,但我目前的理解是 RecordType 等同于函数式编程中的产品类型。使用sanctuary.js定义和类型的方法是什么?上面的 EnumType 很接近,但似乎是针对简单值,而不是其他类型。更像是,如果我有另外两种类型,Cash 和 Card,我将如何为另一种类型建模 PaymentMethod 作为 Cash 和 Card 之间的选择?
我很乐意提供任何指示。我对函数式编程和 sanctuary.js 都很陌生,所以如果我遗漏了一些明显的东西,我会很感激在正确的方向上轻推。
非常感谢。
让我们考虑付款方式。为了简化示例,我们假设付款方式是现金,或者是带有关联卡号的 credit/debit 卡。在 Haskell 中,我们可以像这样定义类型及其数据构造函数:
data PaymentMethod = Cash | Card String
为了使用 sanctuary-def 定义 PaymentMethod
,我们需要知道 Cash
和 Card
数据构造函数是如何实现的。可以自由地手动定义这些或使用 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 的一行非常复杂。我希望这个例子能说明拼图的各个部分是如何组合在一起的。
我们可能想为 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
、Card
和 foldPaymentMethod
后,我们就可以构建和解构 PaymentMethod
值,而无需担心实现细节。例如:
> foldPaymentMethod ('Cash') (number => `Card (${S.show (number)})`) (Cash)
'Cash'
> foldPaymentMethod ('Cash') (number => `Card (${S.show (number)})`) (Card ('2468101214161820'))
'Card ("2468101214161820")'
我正在 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
})
我的困惑点如下:
- 如何使用 sanctuary-def 定义简单类型?从上面的示例中,如果我想定义一个 Amount 类型并在 Payment RecordType 定义中使用它,我将如何去做?我必须为此定义另一个 RecordType 吗?
- 我这里可能是错的,但我目前的理解是 RecordType 等同于函数式编程中的产品类型。使用sanctuary.js定义和类型的方法是什么?上面的 EnumType 很接近,但似乎是针对简单值,而不是其他类型。更像是,如果我有另外两种类型,Cash 和 Card,我将如何为另一种类型建模 PaymentMethod 作为 Cash 和 Card 之间的选择?
我很乐意提供任何指示。我对函数式编程和 sanctuary.js 都很陌生,所以如果我遗漏了一些明显的东西,我会很感激在正确的方向上轻推。
非常感谢。
让我们考虑付款方式。为了简化示例,我们假设付款方式是现金,或者是带有关联卡号的 credit/debit 卡。在 Haskell 中,我们可以像这样定义类型及其数据构造函数:
data PaymentMethod = Cash | Card String
为了使用 sanctuary-def 定义 PaymentMethod
,我们需要知道 Cash
和 Card
数据构造函数是如何实现的。可以自由地手动定义这些或使用 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 的一行非常复杂。我希望这个例子能说明拼图的各个部分是如何组合在一起的。
我们可能想为 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
、Card
和 foldPaymentMethod
后,我们就可以构建和解构 PaymentMethod
值,而无需担心实现细节。例如:
> foldPaymentMethod ('Cash') (number => `Card (${S.show (number)})`) (Cash)
'Cash'
> foldPaymentMethod ('Cash') (number => `Card (${S.show (number)})`) (Card ('2468101214161820'))
'Card ("2468101214161820")'