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:
How I can write type my function arguments correct
(<some_type_for_validate_options>
and
<constructed_type_for_return>
)?
How I can write body of the function using sanctuary function compositions?
Thanks for any help:)
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.