使用 Sanctuary 从具有特定 RecordType 的对象中选取字段

Picking fields from object with certain RecordType with Sanctuary

我有一个对象,其选项对应于以下记录类型:

const AwsRegionsEnum = $.EnumType(
  'AWS/Regions',
  'http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html',
  [
    'us-east-1',
    'us-east-2',
    'us-west-1',
    'us-west-2',
    'ca-central-1',
    'eu-west-1',
    'eu-central-1',
    'eu-west-2',
    'ap-northeast-1',
    'ap-northeast-2',
    'ap-southeast-1',
    'ap-southeast-2',
    'ap-south-1',
    'sa-east-1',
  ]
);
const Credentials = $.RecordType({
  accessKeyId: $.String,
  secretAccessKey: $.String,
  region: AwsRegionsEnum,
});

const PullOpts = $.RecordType({
  waitTimeSeconds: S.MaybeType($.Number),
  maxNumberOfMessages: S.MaybeType($.Number),
  credentials: Credentials,
});

我想创建一个函数,从 ramda 库中的 R.pick 等记录中选择选项。但我想输入列表字段进行挑选。该列表只能包含对 PullOpts.

类型的记录有效的字段

函数的预期行为:

// pickOpts :: List(<some_type_for_validate_options>) -> PullOpts -> <constructed_type_for_return>
const pickOpts = (pickingOpts, allOpts) => {};

总结:

  1. 我怎样才能写出正确的函数参数类型 (<some_type_for_validate_options><constructed_type_for_return>)?

  2. 如何使用避难所函数编写函数体 作曲?

感谢您的帮助:)

在 Sanctuary 中,通常不需要从记录中选择某些字段。考虑这个模块:

const $ = require('sanctuary-def');
const types = require('./types');

const def = $.create({checkTypes: true, env: $.env});

//    User :: Type
const User = $.RecordType({
  id: types.UUID,
  username: $.NonEmpty($.String),
  email: types.Email,
});

//    MinimalUser :: Type
const MinimalUser = $.RecordType({
  id: types.UUID,
  username: $.NonEmpty($.String),
});

//    toMinimalUser :: User -> MinimalUser
const toMinimalUser = def(
  'toMinimalUser',
  {},
  [User, MinimalUser],
  user => ???
);

代替 ??? 应该出现的实现是什么。答案可能令人惊讶:我们只是 return user。这是因为 User 类型的每个成员也是 MinimalUser 类型的成员。允许记录包含附加字段。这使得可以定义类型为 id :: { id :: UUID } -> UUID 的函数,这些函数不限于特定类型(UserInvoice 或其他类型)。

所以,toMinimalUser可能是不必要的。由于 User 类型的每个成员也是 MinimalUser 类型的成员,我们可以将 User/MinimalUser 传递给需要 MinimalUser 的函数.

尽管类型系统不要求我们“剥离”字段,但出于隐私原因,我们可能希望这样做。考虑 toJson :: User -> String。如果我们从 User 值构建 API 响应,出于隐私原因,我们可能会排除 email 字段。在这种情况下,我会建议这个实现:

//    toJson :: User -> String
const toJson = def(
  'toJson',
  {},
  [User, $.String],
  ({id, username}) => JSON.stringify({id, username})
);

这比 R.pick 等价物更冗长,但这是我们必须付出的代价,才能以比 JavaScript 所要求的更规范的方式处理记录。

我可以想象有一天我们会在 Sanctuary 中添加这样的功能:

S.pick :: Set String -> StrMap a -> StrMap a

不过,我无法想象在任意记录上运行类似的函数,因为我们将被迫使用松散类型,例如 Set String -> Object -> Object。在处理 Sanctuary 中的记录时,我更喜欢编写稍微冗长但提供更强保证的代码。