3

I have an object with options that corresponds to the following record type:

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,
});

And I want to create a function that picks options from such records like R.pick from ramda library. But I want to type list fields for picking. That list can contain only fields that are valid for record of type PullOpts.

Expected behavior for the function:

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

Summary:

  1. How I can write type my function arguments correct (<some_type_for_validate_options> and <constructed_type_for_return>)?

  2. How I can write body of the function using sanctuary function compositions?

Thanks for any help:)

4

1 回答 1

4

In Sanctuary, it is usually not necessary to pick certain fields from a record. Consider this module:

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 => ???
);

What is the implementation that should appear in place of the ???. The answer may be surprising: we simply return user. This is because every member of the User type is also a member of the MinimalUser type. A record is permitted to contain additional fields. This makes it possible to define functions with types such as id :: { id :: UUID } -> UUID which are not restricted to a particular type (User, Invoice, or whatever).

So, toMinimalUser may be unnecessary. Since every member of the User type is also a member of the MinimalUser type, we can pass a User/MinimalUser to a function which expects a MinimalUser.

Although the type system won't require us to “strip” fields, we may wish to do so for privacy reasons. Consider toJson :: User -> String. If we're constructing an API response from a User value we may which to exclude the email field for privacy reasons. In this case I would suggest this implementation:

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

This is more verbose than the R.pick equivalent, but that's a price we must pay to deal with records in a more disciplined manner than is required by JavaScript.

I can imagine us one day adding a function such as this to Sanctuary:

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

I can't imagine, though, a similar function operating on arbitrary records as we'd be forced to use a loose type such as Set String -> Object -> Object. When dealing with records in Sanctuary I prefer to write code which is slightly more verbose but provides stronger guarantees.

于 2017-10-05T14:33:15.107 回答