diff --git a/docs/api/en/-unknown-module-.folktale.data.maybe.fromjson.html b/docs/api/en/-unknown-module-.folktale.data.maybe.fromjson.html deleted file mode 100644 index 99a3172..0000000 --- a/docs/api/en/-unknown-module-.folktale.data.maybe.fromjson.html +++ /dev/null @@ -1,57 +0,0 @@ - - - -
-Parses a JavaScript object previously serialised as aMaybe.toJSON()
into a proper Maybe structure.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-type JSONParser = {
- fromJSON: (JSONSerialisation, Array JSONParser) => Variant
-}
-
-(JSONSerialisation, Array JSONParser) => Variant
Parses a JavaScript object previously serialised as aMaybe.toJSON()
into a proper Maybe structure.
See the docs for core/adt/derivations/serialization
for more details.
const Maybe = require('folktale/data/maybe');
-
-Maybe.fromJSON(Maybe.Just(1).toJSON());
-// ==> Maybe.Just(1)
-
-Maybe.fromJSON(Maybe.Nothing().toJSON());
-// ==> Maybe.Nothing()
-
function(value, parsers = { [typeName]: adt }, keysIndicateType = false) {
- const valueTypeName = value[typeJsonKey];
- const valueTagName = value[tagJsonKey];
- const valueContents = value[valueJsonKey];
- assertType(typeName, valueTypeName);
- const parsersByType = keysIndicateType ? parsers
- : /*otherwise*/ indexByType(values(parsers));
-
- const parsedValue = mapValues(valueContents, parseValue(parsersByType));
- return extend(Object.create(adt[valueTagName].prototype), parsedValue);
- }
Tests if an arbitrary value is a Maybe instance.
-Tests if an arbitrary value is a Maybe instance.
-const Maybe = require('folktale/data/maybe');
-
-Maybe.hasInstance({ value: 1 });
-// ==> false
-
-Maybe.hasInstance(Maybe.Just(1));
-// ==> true
-
hasInstance(value) {
- return Boolean(value)
- && value[TYPE] === this[TYPE];
- }
The constructor function for this variant.
-The constructor function for this variant.
-get constructor() {
- return constructor;
- }
Tests if an arbitrary value is a Just case of a Maybe instance.
-Tests if an arbitrary value is a Just case of a Maybe instance.
-const Maybe = require('folktale/data/maybe');
-
-Maybe.Just.hasInstance({ value: 1 });
-// ==> false
-
-Maybe.Just.hasInstance(Maybe.Just(1));
-// ==> true
-
-Maybe.Just.hasInstance(Maybe.Nothing());
-// ==> false
-
hasInstance(value) {
- return Boolean(value)
- && adt.hasInstance(value)
- && value[TAG] === name;
- }
Performs a deep-comparison of two Maybe values for equality. See core/adt/derivations/equality
for details.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Performs a deep-comparison of two Maybe values for equality. See core/adt/derivations/equality
for details.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).equals(Maybe.Just(1));
-// ==> true
-
-Maybe.Just(1).equals(Maybe.Nothing());
-// ==> false
-
-Maybe.Nothing().equals(Maybe.Nothing());
-// ==> true
-
-Maybe.Just(Maybe.Just(1)).equals(Maybe.Just(Maybe.Just(1)));
-// ==> true
-
function(value) {
- assertType(adt)(`${this[tagSymbol]}#equals`, value);
- return sameType(this, value) && compositesEqual(this, value, Object.keys(this));
- }
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
forall M, a, b:
- (M a).((a) => M b) => M b
-where M is Chain
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
'fantasy-land/chain'(transformation) {
- return this.chain(transformation);
- }
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
'fantasy-land/equals'(that) {
- return this.equals(that);
- }
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
('F 'a).(('a) => 'b) => 'F 'b
-where 'F is Functor
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
'fantasy-land/map'(transformation) {
- return this.map(transformation);
- }
A textual representation for Maybe instances.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation for Maybe instances.
-function() {
- return `${variantName}(${plainObjectToString.call(this)})`;
- }
True if the value is a Just
instance.
The .isJust
field is deprecated in favour of the new Just.hasInstance(value)
method.
-The .hasInstance()
version allow safely testing any value, even non-objects, and also
-do union instance checking, rather than a simple tag check;
True if the value is a Just
instance.
get [`is${name}`]() {
- warnDeprecation(`.is${name} is deprecated. Use ${name}.hasInstance(value)
-instead to check if a value belongs to the ADT variant.`);
- return true;
- }
Chooses and executes a function for each variant in the Maybe structure.
-('a is Variant).({ 'b: (Object Any) => 'c }) => 'c
-where 'b = 'a[`@@folktale:adt:tag]
Chooses and executes a function for each variant in the Maybe structure.
-const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).matchWith({
- Nothing: () => 'nothing',
- Just: ({ value }) => `got ${value}`
-});
-// ==> 'got 1'
-
-Maybe.Nothing().matchWith({
- Nothing: () => 'nothing',
- Just: ({ value }) => `got ${value}`
-});
-// ==> 'nothing'
-
matchWith(pattern) {
- return pattern[name](this);
- }
Converts a Maybe value to a JavaScript object that may be stored as a JSON value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-
-Variant . () => JSONSerialisation
Converts a Maybe value to a JavaScript object that may be stored as a JSON value.
-See the docs for core/adt/derivations/serialization
for more details.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).toJSON();
-// ==> { '@@type': 'folktale:Data.Maybe', '@@tag': 'Just', '@@value': { value: 1 } }
-
-Maybe.Nothing().toJSON();
-// ==> { '@@type': 'folktale:Data.Maybe', '@@tag': 'Nothing', '@@value': {} }
-
function() {
- return {
- [typeJsonKey]: typeName,
- [tagJsonKey]: tagName,
- [valueJsonKey]: mapValues(this, serializeValue)
- };
- }
The constructor function for this variant.
-The constructor function for this variant.
-get constructor() {
- return constructor;
- }
Tests if an arbitrary value is a Nothing case of a Maybe instance.
-Tests if an arbitrary value is a Nothing case of a Maybe instance.
-const Maybe = require('folktale/data/maybe');
-
-Maybe.Nothing.hasInstance(Maybe.Just(1));
-// ==> false
-
-Maybe.Nothing.hasInstance(Maybe.Nothing());
-// ==> true
-
hasInstance(value) {
- return Boolean(value)
- && adt.hasInstance(value)
- && value[TAG] === name;
- }
Performs a deep-comparison of two Maybe values for equality. See core/adt/derivations/equality
for details.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Performs a deep-comparison of two Maybe values for equality. See core/adt/derivations/equality
for details.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).equals(Maybe.Just(1));
-// ==> true
-
-Maybe.Just(1).equals(Maybe.Nothing());
-// ==> false
-
-Maybe.Nothing().equals(Maybe.Nothing());
-// ==> true
-
-Maybe.Just(Maybe.Just(1)).equals(Maybe.Just(Maybe.Just(1)));
-// ==> true
-
function(value) {
- assertType(adt)(`${this[tagSymbol]}#equals`, value);
- return sameType(this, value) && compositesEqual(this, value, Object.keys(this));
- }
A textual representation for Maybe instances.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation for Maybe instances.
-function() {
- return `${variantName}(${plainObjectToString.call(this)})`;
- }
True if the value is a Nothing
instance.
The .isNothing
field is deprecated in favour of the new Nothing.hasInstance(value)
method.
-The .hasInstance()
version allow safely testing any value, even non-objects, and also
-do union instance checking, rather than a simple tag check;
True if the value is a Nothing
instance.
get [`is${name}`]() {
- warnDeprecation(`.is${name} is deprecated. Use ${name}.hasInstance(value)
-instead to check if a value belongs to the ADT variant.`);
- return true;
- }
Chooses and executes a function for each variant in the Maybe structure.
-('a is Variant).({ 'b: (Object Any) => 'c }) => 'c
-where 'b = 'a[`@@folktale:adt:tag]
Chooses and executes a function for each variant in the Maybe structure.
-const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).matchWith({
- Nothing: () => 'nothing',
- Just: ({ value }) => `got ${value}`
-});
-// ==> 'got 1'
-
-Maybe.Nothing().matchWith({
- Nothing: () => 'nothing',
- Just: ({ value }) => `got ${value}`
-});
-// ==> 'nothing'
-
matchWith(pattern) {
- return pattern[name](this);
- }
Converts a Maybe value to a JavaScript object that may be stored as a JSON value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-
-Variant . () => JSONSerialisation
Converts a Maybe value to a JavaScript object that may be stored as a JSON value.
-See the docs for core/adt/derivations/serialization
for more details.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).toJSON();
-// ==> { '@@type': 'folktale:Data.Maybe', '@@tag': 'Just', '@@value': { value: 1 } }
-
-Maybe.Nothing().toJSON();
-// ==> { '@@type': 'folktale:Data.Maybe', '@@tag': 'Nothing', '@@value': {} }
-
function() {
- return {
- [typeJsonKey]: typeName,
- [tagJsonKey]: tagName,
- [valueJsonKey]: mapValues(this, serializeValue)
- };
- }
The tag for this variant, unique among the Maybe variants.
-The tag for this variant, unique among the Maybe variants.
-get tag() {
- return name;
- }
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-get type() {
- return typeId;
- }
The variants in the Maybe structure.
-Array Variant
The variants in the Maybe structure.
-Constructs a Maybe value that represents a failure (a Nothing
).
Constructs a Maybe value that represents a successful value (a Just
).
A Number.
-values(variants)
The tag for this variant, unique among the Maybe variants.
-The tag for this variant, unique among the Maybe variants.
-get tag() {
- return name;
- }
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-get type() {
- return typeId;
- }
The constructor for this variant.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The constructor for this variant.
-get constructor() {
- return constructor;
- }
Tests if an arbitrary value is a Error case of a Result instance.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Tests if an arbitrary value is a Error case of a Result instance.
-const Result = require('folktale/data/result');
-
-Result.Error.hasInstance({ value: 1 });
-// ==> false
-
-Result.Error.hasInstance(Result.Error(1));
-// ==> true
-
-Result.Error.hasInstance(Result.Ok(1));
-// ==> false
-
hasInstance(value) {
- return Boolean(value)
- && adt.hasInstance(value)
- && value[TAG] === name;
- }
Performs a deep-comparison of two Result values for equality. See core/adt/derivations/equality
for details.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Performs a deep-comparison of two Result values for equality. See core/adt/derivations/equality
for details.
const Result = require('folktale/data/result');
-
-Result.Ok(1).equals(Result.Ok(1));
-// ==> true
-
-Result.Error(1).equals(Result.Error(1));
-// ==> true
-
-Result.Ok(1).equals(Result.Error(1));
-// ==> false
-
-Result.Ok(Result.Ok(1)).equals(Result.Ok(Result.Ok(1)));
-// ==> true
-
function(value) {
- assertType(adt)(`${this[tagSymbol]}#equals`, value);
- return sameType(this, value) && compositesEqual(this, value, Object.keys(this));
- }
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
forall F, a, b, c, d:
- (F a b).((a) => c, (b) => d) => F c d
-where F is Bifunctor
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
'fantasy-land/bimap'(f, g) {
- return this.bimap(f, g);
- }
A textual representation for Result instances.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation for Result instances.
-function() {
- return `${variantName}(${plainObjectToString.call(this)})`;
- }
True if the value is a Error
instance.
The .isError
field is deprecated in favour of the new Error.hasInstance(value)
method.
-The .hasInstance()
version allow safely testing any value, even non-objects, and also
-do union instance checking, rather than a simple tag check;
True if the value is a Error
instance.
get [`is${name}`]() {
- warnDeprecation(`.is${name} is deprecated. Use ${name}.hasInstance(value)
-instead to check if a value belongs to the ADT variant.`);
- return true;
- }
Chooses and executes a function for each variant in the Result structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
('a is Variant).({ 'b: (Object Any) => 'c }) => 'c
-where 'b = 'a[`@@folktale:adt:tag]
Chooses and executes a function for each variant in the Result structure.
-const Result = require('folktale/data/result');
-
-Result.Ok(1).matchWith({
- Ok: ({ value }) => `ok ${value}`,
- Error: ({ value }) => `error ${value}`
-});
-// ==> 'ok 1'
-
-Result.Error(1).matchWith({
- Ok: ({ value }) => `ok ${value}`,
- Error: ({ value }) => `error ${value}`
-});
-// ==> 'error 1'
-
matchWith(pattern) {
- return pattern[name](this);
- }
Converts a Result value to a JavaScript object that may be stored as a JSON value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-
-Variant . () => JSONSerialisation
Converts a Result value to a JavaScript object that may be stored as a JSON value.
-See the docs for core/adt/derivations/serialization
for more details.
const Result = require('folktale/data/result');
-
-Result.Ok(1).toJSON();
-// ==> { '@@type': 'folktale:Data.Result', '@@tag': 'Ok', '@@value': { value: 1 } }
-
-Result.Error(1).toJSON();
-// ==> { '@@type': 'folktale:Data.Result', '@@tag': 'Error', '@@value': { value: 1 } }
-
-Result.Error(undefined).toJSON();
-// ==> { '@@type': 'folktale:Data.Result', '@@tag': 'Error', '@@value': { value: null } }
-
function() {
- return {
- [typeJsonKey]: typeName,
- [tagJsonKey]: tagName,
- [valueJsonKey]: mapValues(this, serializeValue)
- };
- }
The constructor for this variant.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The constructor for this variant.
-get constructor() {
- return constructor;
- }
Tests if an arbitrary value is a Ok case of a Result instance.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Tests if an arbitrary value is a Ok case of a Result instance.
-const Result = require('folktale/data/result');
-
-Result.Ok.hasInstance({ value: 1 });
-// ==> false
-
-Result.Ok.hasInstance(Result.Error(1));
-// ==> false
-
-Result.Ok.hasInstance(Result.Ok(1));
-// ==> true
-
hasInstance(value) {
- return Boolean(value)
- && adt.hasInstance(value)
- && value[TAG] === name;
- }
Performs a deep-comparison of two Result values for equality. See core/adt/derivations/equality
for details.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Performs a deep-comparison of two Result values for equality. See core/adt/derivations/equality
for details.
const Result = require('folktale/data/result');
-
-Result.Ok(1).equals(Result.Ok(1));
-// ==> true
-
-Result.Error(1).equals(Result.Error(1));
-// ==> true
-
-Result.Ok(1).equals(Result.Error(1));
-// ==> false
-
-Result.Ok(Result.Ok(1)).equals(Result.Ok(Result.Ok(1)));
-// ==> true
-
function(value) {
- assertType(adt)(`${this[tagSymbol]}#equals`, value);
- return sameType(this, value) && compositesEqual(this, value, Object.keys(this));
- }
A textual representation for Result instances.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation for Result instances.
-function() {
- return `${variantName}(${plainObjectToString.call(this)})`;
- }
True if the value is a Ok
instance.
The .isOk
field is deprecated in favour of the new Ok.hasInstance(value)
method.
-The .hasInstance()
version allow safely testing any value, even non-objects, and also
-do union instance checking, rather than a simple tag check;
True if the value is a Ok
instance.
get [`is${name}`]() {
- warnDeprecation(`.is${name} is deprecated. Use ${name}.hasInstance(value)
-instead to check if a value belongs to the ADT variant.`);
- return true;
- }
Chooses and executes a function for each variant in the Result structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
('a is Variant).({ 'b: (Object Any) => 'c }) => 'c
-where 'b = 'a[`@@folktale:adt:tag]
Chooses and executes a function for each variant in the Result structure.
-const Result = require('folktale/data/result');
-
-Result.Ok(1).matchWith({
- Ok: ({ value }) => `ok ${value}`,
- Error: ({ value }) => `error ${value}`
-});
-// ==> 'ok 1'
-
-Result.Error(1).matchWith({
- Ok: ({ value }) => `ok ${value}`,
- Error: ({ value }) => `error ${value}`
-});
-// ==> 'error 1'
-
matchWith(pattern) {
- return pattern[name](this);
- }
Converts a Result value to a JavaScript object that may be stored as a JSON value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-
-Variant . () => JSONSerialisation
Converts a Result value to a JavaScript object that may be stored as a JSON value.
-See the docs for core/adt/derivations/serialization
for more details.
const Result = require('folktale/data/result');
-
-Result.Ok(1).toJSON();
-// ==> { '@@type': 'folktale:Data.Result', '@@tag': 'Ok', '@@value': { value: 1 } }
-
-Result.Error(1).toJSON();
-// ==> { '@@type': 'folktale:Data.Result', '@@tag': 'Error', '@@value': { value: 1 } }
-
-Result.Error(undefined).toJSON();
-// ==> { '@@type': 'folktale:Data.Result', '@@tag': 'Error', '@@value': { value: null } }
-
function() {
- return {
- [typeJsonKey]: typeName,
- [tagJsonKey]: tagName,
- [valueJsonKey]: mapValues(this, serializeValue)
- };
- }
The tag for this variant, unique among the Result variants.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The tag for this variant, unique among the Result variants.
-get tag() {
- return name;
- }
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-get type() {
- return typeId;
- }
The variants in the Result structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Array Variant
The variants in the Result structure.
-Constructs a Result whose value represents a failure.
-Constructs a Result whose value represents a success.
-A Number.
-values(variants)
The tag for this variant, unique among the Result variants.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The tag for this variant, unique among the Result variants.
-get tag() {
- return name;
- }
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-get type() {
- return typeId;
- }
Parses a JavaScript object previously serialised as aResult.toJSON()
into a proper Result structure.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-type JSONParser = {
- fromJSON: (JSONSerialisation, Array JSONParser) => Variant
-}
-
-(JSONSerialisation, Array JSONParser) => Variant
Parses a JavaScript object previously serialised as aResult.toJSON()
into a proper Result structure.
See the docs for core/adt/derivations/serialization
for more details.
const Result = require('folktale/data/result');
-
-Result.fromJSON(Result.Ok(1).toJSON());
-// ==> Result.Ok(1)
-
-Result.fromJSON(Result.Error(1).toJSON());
-// ==> Result.Error(1)
-
function(value, parsers = { [typeName]: adt }, keysIndicateType = false) {
- const valueTypeName = value[typeJsonKey];
- const valueTagName = value[tagJsonKey];
- const valueContents = value[valueJsonKey];
- assertType(typeName, valueTypeName);
- const parsersByType = keysIndicateType ? parsers
- : /*otherwise*/ indexByType(values(parsers));
-
- const parsedValue = mapValues(valueContents, parseValue(parsersByType));
- return extend(Object.create(adt[valueTagName].prototype), parsedValue);
- }
Tests if an arbitrary value is a Result instance.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Tests if an arbitrary value is a Result instance.
-const Result = require('folktale/data/result');
-
-Result.hasInstance({ value: 1 });
-// ==> false
-
-Result.hasInstance(Result.Ok(1));
-// ==> true
-
-Result.hasInstance(Result.Error(1));
-// ==> true
-
hasInstance(value) {
- return Boolean(value)
- && value[TYPE] === this[TYPE];
- }
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
ap(that) {
- return this.apply(that);
- }
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
('F 'a).('F ('a) => 'b) => 'F 'b
-where 'F is Apply
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
'fantasy-land/ap'(that) {
- return that.apply(this);
- }
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
forall F, a:
- (F).(a) => F a
-where F is Applicative
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
'fantasy-land/of'(value) {
- return this.of(value);
- }
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
forall F, a, b, c, d:
- (F a b).((a) => c, (b) => d) => F c d
-where F is Bifunctor
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
'fantasy-land/bimap'(f, g) {
- return this.bimap(f, g);
- }
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
forall M, a, b:
- (M a).((a) => M b) => M b
-where M is Chain
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
'fantasy-land/chain'(transformation) {
- return this.chain(transformation);
- }
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
('F 'a).(('a) => 'b) => 'F 'b
-where 'F is Functor
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
'fantasy-land/map'(transformation) {
- return this.map(transformation);
- }
The container for instance methods for the Task structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The container for instance methods for the Task structure.
-Constructs a new task that awaits both tasks to resolve. The result of the new task is a tuple containing the results of the left and right tasks, if they all succeed, or the first failure if they fail.
-Combines two tasks such that the resulting task assimilates the result of the -first one to resolve.
-Executes a Task and returns a TaskExecution
object representing the execution.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Chooses and executes a function for each variant in a Task. The function must -return a new task, whose value and state will be assimilated.
-Transforms a failed task's value and state.
-Inverts the state of a Task. That is, turns successful tasks into failed ones, and failed tasks into successful ones.
-Applies the function in the left task to the value on the right task. The left -task is ran to completion before the right task is started.
-Transforms the rejected or resolved values of a Task with a function. The state of the task is not changed.
-Transforms the value and state of a Task.
-Transforms the value of a successful task.
-Transforms the value of a failed task.
-Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task
function.
Cancels a task execution. Does nothing if the task has already been resolved.
-Cancels a task execution. Does nothing if the task has already been resolved.
-const { task } = require('folktale/data/task');
-
-let message = 'world';
-const helloIn50 = task(
- resolver => setTimeout(() => {
- message = 'hello';
- resolver.resolve();
- }, 50),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const execution = helloIn50.run();
-execution.cancel();
-try {
- const result = await execution.promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(message == 'world');
-}
-
Gets the eventual value of a Task as a Folktale Future
.
Gets the eventual value of a Task as a Folktale Future
.
const { task, of, rejected } = require('folktale/data/task');
-const { Cancelled, Resolved, Rejected } = require('folktale/data/future/_execution-state');
-
-of(1).run().future()._state;
-// ==> Resolved(1)
-
-rejected(1).run().future()._state;
-// ==> Rejected(1)
-
-task(r => r.cancel()).run().future()._state;
-// ==> Cancelled()
-
The container for instance methods for the TaskExecution structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The container for instance methods for the TaskExecution structure.
-Cancels a task execution. Does nothing if the task has already been resolved.
-Adds a functions to be called when the task settles for each possible state it can transition to.
-Represents the execution of a Task, with methods to cancel it, react to its
-results, and retrieve its eventual value. TaskExecution objects aren't created
-directly by users, but instead returned as a result from Task's run()
method.
-b
Adds a functions to be called when the task settles for each possible state it can transition to.
-Adds a functions to be called when the task settles for each possible state it can transition to.
-const { task } = require('folktale/data/task');
-
-task(r => r.resolve('hello')).run().listen({
- onCancelled: () => { throw 'never happens' },
- onRejected: (error) => { throw 'never happens' },
- onResolved: (value) => { $ASSERT(value == 'hello') }
-});
-
-task(r => r.reject('hello')).run().listen({
- onCancelled: () => { throw 'never happens' },
- onRejected: (error) => { $ASSERT(error == 'hello') },
- onResolved: (value) => { throw 'never happens' }
-});
-
-task(r => r.cancel()).run().listen({
- onCancelled: () => { $ASSERT(true) },
- onRejected: (error) => { throw 'never happens' },
- onResolved: (value) => { throw 'never happens' }
-});
-
Gets the eventual value of the task as a JavaScript's Promise
.
Gets the eventual value of the task as a JavaScript's Promise
.
Since Promises don't have support for representing cancellations, Folktale's -tasks represent cancellation as a rejection with a special object. See the -documentation for Futures and Deferreds for more details.
-const { task, of, rejected } = require('folktale/data/task');
-
-const result1 = await of(1).run().promise();
-$ASSERT(result1 == 1);
-
-try {
- const result2 = await rejected(1).run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 1);
-}
-
-try {
- const result3 = await task(r => r.cancel()).run().promise();
- throw 'never happens';
-} catch (error) {
- const { Cancelled } = require('folktale/data/future/_execution-state');
- $ASSERT(Cancelled.hasInstance(error));
-}
-
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
ap(that) {
- return this.apply(that);
- }
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
('F 'a).('F ('a) => 'b) => 'F 'b
-where 'F is Apply
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
'fantasy-land/ap'(that) {
- return that.apply(this);
- }
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
forall F, a:
- (F).(a) => F a
-where F is Applicative
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
'fantasy-land/of'(value) {
- return this.of(value);
- }
The constructor for this variant.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The constructor for this variant.
-get constructor() {
- return constructor;
- }
Tests if an arbitrary value is a Failure case of a Validation instance.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Tests if an arbitrary value is a Failure case of a Validation instance.
-const Validation = require('folktale/data/validation');
-
-Validation.Failure.hasInstance({ value: 1 });
-// ==> false
-
-Validation.Failure.hasInstance(Validation.Success(1));
-// ==> false
-
-Validation.Failure.hasInstance(Validation.Failure(1));
-// ==> true
-
hasInstance(value) {
- return Boolean(value)
- && adt.hasInstance(value)
- && value[TAG] === name;
- }
Performs a deep-comparison of two Validation values for equality.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Performs a deep-comparison of two Validation values for equality.
-See core/adt/derivations/equality
for details.
const { Success, Failure } = require('folktale/data/validation');
-
-Success(1).equals(Success(1));
-// ==> true
-
-Failure(1).equals(Failure(1));
-// ==> true
-
-Success(1).equals(Failure(1));
-// ==> false
-
-Success(Success(1)).equals(Success(Success(1)));
-// ==> true
-
function(value) {
- assertType(adt)(`${this[tagSymbol]}#equals`, value);
- return sameType(this, value) && compositesEqual(this, value, Object.keys(this));
- }
Part of the Semigroup instance for Fantasy Land 2.x+. See the concat
method for details.
('S 'a).('S 'a) => 'S 'a
-where 'S is Semigroup
Part of the Semigroup instance for Fantasy Land 2.x+. See the concat
method for details.
'fantasy-land/concat'(that) {
- return this.concat(that);
- }
A textual representation for Validation instances.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation for Validation instances.
-function() {
- return `${variantName}(${plainObjectToString.call(this)})`;
- }
True if the value is a Failure
instance.
The .isFailure
field is deprecated in favour of the new Failure.hasInstance(value)
method.
-The .hasInstance()
version allow safely testing any value, even non-objects, and also
-do union instance checking, rather than a simple tag check;
True if the value is a Failure
instance.
get [`is${name}`]() {
- warnDeprecation(`.is${name} is deprecated. Use ${name}.hasInstance(value)
-instead to check if a value belongs to the ADT variant.`);
- return true;
- }
Chooses and executes a function for each variant in the Validation structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
('a is Variant).({ 'b: (Object Any) => 'c }) => 'c
-where 'b = 'a[`@@folktale:adt:tag]
Chooses and executes a function for each variant in the Validation structure.
-const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').matchWith({
- Failure: ({ value }) => `Failure: ${value}`,
- Success: ({ value }) => `Success: ${value}`
-});
-// ==> 'Success: a'
-
-Failure('a').matchWith({
- Failure: ({ value }) => `Failure: ${value}`,
- Success: ({ value }) => `Success: ${value}`
-});
-// ==> 'Failure: a'
-
matchWith(pattern) {
- return pattern[name](this);
- }
Converts a Validation value to a JavaScript object that may be stored as a JSON value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-
-Variant . () => JSONSerialisation
Converts a Validation value to a JavaScript object that may be stored as a JSON value.
-See the docs for core/adt/derivations/serialization
for more details.
const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').toJSON();
-// ==> { '@@type': 'folktale:Data.Validation', '@@tag': 'Success', '@@value': { value: 'a' }}
-
-Failure('a').toJSON();
-// ==> { '@@type': 'folktale:Data.Validation', '@@tag': 'Failure', '@@value': { value: 'a' }}
-
function() {
- return {
- [typeJsonKey]: typeName,
- [tagJsonKey]: tagName,
- [valueJsonKey]: mapValues(this, serializeValue)
- };
- }
The constructor for this variant.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The constructor for this variant.
-get constructor() {
- return constructor;
- }
Tests if an arbitrary value is a Success case of a Validation instance.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Tests if an arbitrary value is a Success case of a Validation instance.
-const Validation = require('folktale/data/validation');
-
-Validation.Success.hasInstance({ value: 1 });
-// ==> false
-
-Validation.Success.hasInstance(Validation.Success(1));
-// ==> true
-
-Validation.Success.hasInstance(Validation.Failure(1));
-// ==> false
-
hasInstance(value) {
- return Boolean(value)
- && adt.hasInstance(value)
- && value[TAG] === name;
- }
Performs a deep-comparison of two Validation values for equality.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Performs a deep-comparison of two Validation values for equality.
-See core/adt/derivations/equality
for details.
const { Success, Failure } = require('folktale/data/validation');
-
-Success(1).equals(Success(1));
-// ==> true
-
-Failure(1).equals(Failure(1));
-// ==> true
-
-Success(1).equals(Failure(1));
-// ==> false
-
-Success(Success(1)).equals(Success(Success(1)));
-// ==> true
-
function(value) {
- assertType(adt)(`${this[tagSymbol]}#equals`, value);
- return sameType(this, value) && compositesEqual(this, value, Object.keys(this));
- }
A textual representation for Validation instances.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation for Validation instances.
-function() {
- return `${variantName}(${plainObjectToString.call(this)})`;
- }
True if the value is a Success
instance.
The .isSuccess
field is deprecated in favour of the new Success.hasInstance(value)
method.
-The .hasInstance()
version allow safely testing any value, even non-objects, and also
-do union instance checking, rather than a simple tag check;
True if the value is a Success
instance.
get [`is${name}`]() {
- warnDeprecation(`.is${name} is deprecated. Use ${name}.hasInstance(value)
-instead to check if a value belongs to the ADT variant.`);
- return true;
- }
Chooses and executes a function for each variant in the Validation structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
('a is Variant).({ 'b: (Object Any) => 'c }) => 'c
-where 'b = 'a[`@@folktale:adt:tag]
Chooses and executes a function for each variant in the Validation structure.
-const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').matchWith({
- Failure: ({ value }) => `Failure: ${value}`,
- Success: ({ value }) => `Success: ${value}`
-});
-// ==> 'Success: a'
-
-Failure('a').matchWith({
- Failure: ({ value }) => `Failure: ${value}`,
- Success: ({ value }) => `Success: ${value}`
-});
-// ==> 'Failure: a'
-
matchWith(pattern) {
- return pattern[name](this);
- }
Converts a Validation value to a JavaScript object that may be stored as a JSON value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-
-Variant . () => JSONSerialisation
Converts a Validation value to a JavaScript object that may be stored as a JSON value.
-See the docs for core/adt/derivations/serialization
for more details.
const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').toJSON();
-// ==> { '@@type': 'folktale:Data.Validation', '@@tag': 'Success', '@@value': { value: 'a' }}
-
-Failure('a').toJSON();
-// ==> { '@@type': 'folktale:Data.Validation', '@@tag': 'Failure', '@@value': { value: 'a' }}
-
function() {
- return {
- [typeJsonKey]: typeName,
- [tagJsonKey]: tagName,
- [valueJsonKey]: mapValues(this, serializeValue)
- };
- }
The tag for this variant, unique among the Validation variants.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The tag for this variant, unique among the Validation variants.
-get tag() {
- return name;
- }
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-get type() {
- return typeId;
- }
The variants in the Validation structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Array Variant
The variants in the Validation structure.
-Constructs a Validation whose value represents a failure.
-Constructs a Validation whose value represents a success.
-A Number.
-values(variants)
The tag for this variant, unique among the Validation variants.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The tag for this variant, unique among the Validation variants.
-get tag() {
- return name;
- }
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-get type() {
- return typeId;
- }
Parses a JavaScript object previously serialised as aValidation.toJSON()
into a proper Validation structure.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-type JSONParser = {
- fromJSON: (JSONSerialisation, Array JSONParser) => Variant
-}
-
-(JSONSerialisation, Array JSONParser) => Variant
Parses a JavaScript object previously serialised as aValidation.toJSON()
into a proper Validation structure.
See the docs for core/adt/derivations/serialization
for more details.
const Validation = require('folktale/data/validation');
-
-Validation.fromJSON(Validation.Success(1).toJSON());
-// ==> Validation.Success(1)
-
-Validation.fromJSON(Validation.Failure(1).toJSON());
-// ==> Validation.Failure(1)
-
function(value, parsers = { [typeName]: adt }, keysIndicateType = false) {
- const valueTypeName = value[typeJsonKey];
- const valueTagName = value[tagJsonKey];
- const valueContents = value[valueJsonKey];
- assertType(typeName, valueTypeName);
- const parsersByType = keysIndicateType ? parsers
- : /*otherwise*/ indexByType(values(parsers));
-
- const parsedValue = mapValues(valueContents, parseValue(parsersByType));
- return extend(Object.create(adt[valueTagName].prototype), parsedValue);
- }
Tests if an arbitrary value is a Validation instance.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Tests if an arbitrary value is a Validation instance.
-const Validation = require('folktale/data/validation');
-
-Validation.hasInstance({ value: 1 });
-// ==> false
-
-Validation.hasInstance(Validation.Success(1));
-// ==> true
-
-Validation.hasInstance(Validation.Failure(1));
-// ==> true
-
hasInstance(value) {
- return Boolean(value)
- && value[TYPE] === this[TYPE];
- }
The basis of all algebraic data types.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The basis of all algebraic data types.
-ADT is used basically to share some methods for refining data structures -created by this module, derivation being one of them.
-Allows a function to provide functionality to variants in an ADT.
-{
- /*~
- * type: |
- * ADT . (...(Variant, ADT) => Any) => ADT
- */
- derive(...derivations) {
- derivations.forEach(derivation => {
- this.variants.forEach(variant => derivation(variant, this));
- });
- return this;
- }
-}
The constructor for this variant.
-The constructor for this variant.
-get constructor() {
- return constructor;
- }
Constructs a tagged union data structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a tagged union data structure.
-const data = require('folktale/core/adt/data');
-
-var List = data('List', {
- Nil(){ },
- Cons(value, rest) {
- return { value, rest };
- }
-});
-
-var { Nil, Cons } = List;
-
-Cons('a', Cons('b', Cons('c', Nil())));
-// ==> { value: 'a', rest: { value: 'b', ..._ }}
-
Data modelling is a very important part of programming, directly -affecting things like correctness and performance. Folktale is -in general mostly interested in correctness, and providing tools -for achieving that.
-When modelling data in a program, there are several different -choices that one must make in an attempt to capture the rules of -how that data is manipulated and what they represent. Data modeling -tends to come in three different concepts:
-Scalars represent concepts that have only one atomic -value at a time. This value makes sense on its own, and can't -be divided into further concepts. Examples of this are numers, -strings, etc.
-Product represent bigger concepts that are made out of
-possibly several smaller concepts, each of which is independent
-of each other, and always present. An object that contains a
-person's name
and age
is an example of a product, arrays
-are another example.
Unions represent one of out of many concepts, at any given -time. JS doesn't have many data structures that capture the idea -of a union, but there are many cases where this happens in a -codebase:
-Reading a file may either give you the data in that -file or an error object;
-Accessing a property in an object may either give you the -value or undefined;
-Querying a database may give you a connection error (maybe -we weren't able to contact the database), a query error -(maybe the query wasn't well formed), a "this value isn't -here" response, or the value you want.
-Out of these, you're probably already familiar with products and scalars, -because they're used everywhere in JavaScript, but maybe you're not -familiar with unions, since JavaScript doesn't have many of them built-in.
-For example, when reading a file in Node, you have this:
-fs.readFile(filename, (error, value) => {
- if (error != null) {
- handleError(error);
- } else {
- handleSuccess(value);
- }
-});
-
The callback function receives two arguments, error
and value
, but
-only one of them may ever be present at any given time. If you have a
-value, then error
must be null, and if you have an error, then value
-must be null. Nothing in the representation of this data tells you
-that, or forces you to deal with it like that.
If you compare it with an API like fetch
, where you get a Promise
-instead, many of these problems are solved:
fetch(url).then(
- (response) => handleSuccess(response),
- (error) => handleError(error)
-);
-
Here the result of fetch
can be either a response or an error, like in
-the readFile
example, but the only way of getting to that value is
-through the then
function, which requires you to define separate branches
-for handling each case. This way it's not possible to forget to deal with
-one of the cases, or make mistakes in the branching condition, such as
-if (error == null) { handleError(...) }
— which the first version of
-this documentation had, in fact.
So, properly modelling your data helps making sure that a series of errors -can't ever occurr in your program, which is great as you have to deal with -less problems, but how does Core.ADT help you in that?
-To answer this question let's consider a very simple, everyday problem: you -have a function that can return any value, but it can also fail. How do you -differentiate failure from regular values?
-const find = (predicate, items) => {
- for (let i = 0; i < items.length; ++i) {
- const item = items[i];
- if (predicate(item)) return item;
- }
- return null;
-};
-
The example above returns the item if the predicate matches anything, or null
-if it doesn't. But null
is also a valid JavaScript value:
find(x => true, [1, 2, 3]); // ==> 1
-find(x => false, [1, 2, 3]); // ==> null
-find(x => true, [null, 1, 2]); // ==> null
-
Now, there isn't a way of differentiating failure from success if your arrays
-have a null
value. One could say "this function works for arrays without
-nulls", but there isn't a separate type that can enforce those guarantees
-either. This confusing behaviour opens the door for bugs that are very
-difficult to find, since they're created way before they hit the find
-function.
A more practical approach is to return something that can't be in the array.
-For example, if we return an object like: { found: Bool, value: Any }
, then
-we don't run into this issue:
const find2 = (predicate, items) => {
- for (let i = 0; i < items.length; ++i) {
- const item = items[i];
- if (predicate(item)) return { found: true, value: item };
- }
- return { found: false };
-};
-
-find2(x => true, [1, 2, 3]); // ==> { found: true, value: 1 }
-find2(x => false, [1, 2, 3]); // ==> { found: false }
-find2(x => true, [null, 1, 2]); // ==> { found: true, value: null }
-
We can differentiate between successes and failures now, but in order to
-use the value we need to unpack it. Now we have two problems: found
and
-value
aren't entirely related, and we have to create this ad-hoc relationship
-through an if
statement. That's very easy to get wrong. Another problem is
-that nothing forces people to check found
before looking at value
.
So, a better solution for this is to use tagged unions and pattern matching:
-const data = require('folktale/core/adt/data');
-
-const Maybe = data('Maybe', {
- None() { return {} },
- Some(value) { return { value } }
-});
-
-const find3 = (predicate, items) => {
- for (let i = 0; i < items.length; ++i) {
- const item = items[i];
- if (predicate(item)) return Maybe.Some(item);
- }
- return Maybe.None();
-};
-
-find3(x => true, [1, 2, 3]); // ==> Maybe.Some(1)
-find3(x => false, [1, 2, 3]); // ==> Maybe.None()
-find3(x => true, [null, 1, 2]); // ==> Maybe.Some(null)
-
-find3(x => true, [1, 2, 3]).matchWith({
- None: () => "Not found",
- Some: ({ value }) => "Found " + value
-}); // ==> "Found 1"
-
Let's consider a more complex case. Imagine you're writing a function to -handle communicating with some HTTP API. Like in the case presented in -the previous section, a call to the API may succeed or fail. Unlike the -previous example, here a failure has more information associated with it, -and we can have different kinds of failures:
-A common way of writing this in Node would be like this:
-api.method((error, response) => {
- if (error != null) {
- if (error.code === "http") {
- // handle network failures here
- }
- if (error.code === "service") {
- // handle service failures here
- }
- } else {
- try {
- var data = normalise(response);
- // handle success here
- } catch(e) {
- // handle invalid responses here
- }
- }
-});
-
But again, in this style of programming it's easier to make mistakes that are hard -to catch, since we're assigning meaning through control flow in an ad-hoc manner, -and there's nothing to tell us if we've got it wrong. It's also harder to abstract, -because we can't capture these rules as data, so we have to add even more special -control flow structures to handle the abstractions.
-Let's model it as a tagged union instead. We could make a single data structure -that captures all 4 possible results, and that would be a reasonable way of modelling -this. But on the other hand, we wouldn't be able to talk about failures in general, -because this forces us to handle each failure case independently. Instead we'll have -two tagged unions:
-const data = require('folktale/core/adt/data');
-
-const Result = data('Result', {
- Ok(value) {
- return { value };
- },
- Error(reason) {
- return { reason };
- }
-});
-
-const APIError = data('APIError', {
- NetworkError(error){
- return { error };
- },
- ServiceError(code, message) {
- return { code, message };
- },
- ParsingError(error, data) {
- return { error, data };
- }
-});
-
Then we can construct these values in the API, and make sure people will handle -all cases when using it:
-function handleError(error) {
- error.matchWith({
- NetworkError: ({ error }) => { ... },
- ServiceError: ({ code, message }) => { ... },
- ParsingError: ({ error, data }) => { ... }
- })
-}
-
-api.method(response => {
- response.matchWith({
- Error: ({ reason }) => handleError(reason),
- Ok: ({ value }) => { ... }
- })
-});
-
When you're modelling data with ADTs it's tempting to create a lot of -very specific objects to capture correctly all of the choices that may -exist in a particular domain, but Core.ADT only gives you construction -and pattern matching, so what if you want your types to have a notion -of equality?
-That's where the concept of derivation comes in. A derivation is a
-function that provides a set of common functionality for an ADT and
-its variants. For example, if one wanted to add the notion of equality
-to an ADT, they could derive
Equality
as follows:
const data = require('folktale/core/adt/data');
-const Equality = require('folktale/core/adt/derivations/equality');
-
-const Either = data('Either', {
- Left(value) { return { value } },
- Right(value){ return { value } }
-}).derive(Equality);
-
Note the .derive(Equality)
invocation. derive
is a method that can
-be called at any time on the ADT to provide new common functionality
-to it. In this case, the Equality
derivation gives all variants an
-equals()
method:
Either.Left(1).equals(Either.Left(1)); // ==> true
-Either.Left(1).equals(Either.Right(1)); // ==> false
-Either.Right(1).equals(Either.Right(2)); // ==> false
-Either.Right(2).equals(Either.Right(2)); // ==> true
-
While Core.ADT provides a set of common derivations (categorised
-Derivation
in the documentation), one may create their own derivation
-functions to use with Folktale's ADTs. See the Extending ADTs
-section for details.
The ADT module approaches this problem in a structural-type-ish way, which -happens to be very similar to how OCaml's polymorphic variants work, and -how different values are handled in untyped languages.
-In essence, calling data
with a set of patterns results in the creation
-of N constructors, each with a distinct tag.
Revisiting the previous List
ADT example, when one writes:
const data = require('folktale/core/adt/data');
-
-var List = data('List', {
- Nil: () => {},
- Cons: (value, rest) => ({ value, rest })
-})
-
That's roughly equivalent to the idiomatic:
-var List = {};
-
-function Nil() { }
-Nil.prototype = Object.create(List);
-
-function Cons(value, rest) {
- this.value = value;
- this.rest = rest;
-}
-Cons.prototype = Object.create(List);
-
The data
function takes as arguments a type identifier (which can be any
-object, if you want it to be unique), and an object with the variants. Each
-property in this object is expected to be a function that returns the
-properties that'll be provided for the instance of that variant.
The given variants are not returned directly. Instead, we return a wrapper -that will construct a proper value of this type, and augment it with the -properties provided by that variant initialiser.
-The ADT module relies on JavaScript's built-in reflective features first, -and adds a couple of additional fields to this.
-The provided type for the ADT, and the tag provided for the variant -are both reified in the ADT structure and the constructed values. These -allow checking the compatibility of different values structurally, which -sidesteps the problems with realms.
-The type of the ADT is provided by the global symbol @@folktale:adt:type
:
const data = require('folktale/core/adt/data');
-
-var Id = data('Identity', { Id: () => {} });
-Id[Symbol.for('@@folktale:adt:type')]
-// ==> 'Identity'
-
The tag of the value is provided by the global symbol @@folktale:adt:tag
:
var List = data('List', {
- Nil: () => {},
- Cons: (h, t) => ({ h, t })
-});
-List.Nil()[Symbol.for('@@folktale:adt:tag')]
-// ==> 'Nil'
-
These symbols are also exported as properties of the data
function
-itself, so you can use data.typeSymbol
and data.tagSymbol
instead
-of retrieving a symbol instance with the Symbol.for
function.
is-a
testsSometimes it's desirable to test if a value belongs to an ADT or
-to a variant. Out of the box the structures constructed by ADT
-provide a hasInstance
check that verify if a value is structurally
-part of an ADT structure, by checking the Type and Tag of that value.
const data = require('folktale/core/adt/data');
-
-var IdA = data('IdA', { Id: (x) => ({ x }) });
-var IdB = data('IdB', { Id: (x) => ({ x }) });
-
-IdA.hasInstance(IdA.Id(1)) // ==> true
-IdA.hasInstance(IdB.Id(1)) // ==> false
-
const data = require('folktale/core/adt/data');
-
-var Either = data('Either', {
- Left: value => ({ value }),
- Right: value => ({ value })
-});
-var { Left, Right } = Either;
-
-Left.hasInstance(Left(1)); // ==> true
-Left.hasInstance(Right(1)); // ==> false
-
Note that if two ADTs have the same type ID, they'll be considered
-equivalent by hasInstance
. You may pass an object (like
-Symbol('type name')
) to data
to avoid this, however reference
-equality does not work across realms in JavaScript.
Since all instances inherit from the ADT and the variant's prototype
-it's also possible to use proto.isPrototypeOf(instance)
to check
-if an instance belongs to an ADT by reference equality, rather than
-structural equality.
Because all variants inherit from the ADT namespace, it's possible -to provide new functionality to all variants by simply adding new -properties to the ADT:
-const data = require('folktale/core/adt/data');
-
-var List = data('List', {
- Nil: () => {},
- Cons: (value, rest) => ({ value, rest })
-});
-
-var { Nil, Cons } = List;
-
-List.sum = function() {
- return this.matchWith({
- Nil: () => 0,
- Cons: ({ value, rest }) => value + rest.sum()
- });
-};
-
-Cons(1, Cons(2, Nil())).sum();
-// ==> 3
-
A better approach, however, may be to use the derive
function from
-the ADT to provide new functionality to every variant. derive
accepts
-many derivation functions, which are just functions taking a variant and
-ADT, and providing new functionality for that variant.
If one wanted to define a JSON serialisation for each variant, for example,
-they could do so by using the derive
functionality:
function ToJSON(variant, adt) {
- var { tag, type } = variant;
- variant.prototype.toJSON = function() {
- var json = { tag: `${type}:${tag}` };
- Object.keys(this).forEach(key => {
- var value = this[key];
- if (value && typeof value.toJSON === "function") {
- json[key] = value.toJSON();
- } else {
- json[key] = value;
- }
- });
- return json;
- }
-}
-
-var List = data('List', {
- Nil: () => {},
- Cons: (value, rest) => ({ value, rest })
-}).derive(ToJSON);
-
-var { Nil, Cons } = List;
-
-Nil().toJSON()
-// ==> { tag: "List:Nil" }
-
-Cons(1, Nil()).toJSON()
-// ==> { tag: "List:Cons", value: 1, rest: { "tag": "List:Nil" }}
-
The basis of all algebraic data types.
-A Symbol.
-A Symbol.
-(typeId, patterns) => {
- const ADTNamespace = Object.create(ADT);
- const variants = defineVariants(typeId, patterns, ADTNamespace);
-
- extend(ADTNamespace, variants, {
- // This is internal, and we don't really document it to the user
- [TYPE]: typeId,
-
- /*~
- * type: Array Variant
- * module: null
- * ~belongsTo: ADTNamespace
- */
- variants: values(variants),
-
- /*~
- * ~belongsTo: ADTNamespace
- * module: null
- * type: |
- * ADT.(Variant) -> Boolean
- */
- hasInstance(value) {
- return Boolean(value)
- && value[TYPE] === this[TYPE];
- }
- });
-
- return ADTNamespace;
-}
Allows a function to provide functionality to variants in an ADT.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Allows a function to provide functionality to variants in an ADT.
-The derive
method exists to support meta-programming on ADT objects,
-such that additional functionality (implementation of interfaces or
-protocols, for example) may be provided by libraries instead of having
-to be hand-coded by the user.
The operation accepts many derivation
functions, which will be invoked
-for each variant in the ADT, where a Variant is just an object with the
-following attributes:
interface Variant(Any...) -> 'a <: self.prototype {
- tag : String,
- type : Any,
- constructor : Constructor,
- prototype : Object
-}
-
derive(...derivations) {
- derivations.forEach(derivation => {
- this.variants.forEach(variant => derivation(variant, this));
- });
- return this;
- }
The container for instance methods for Success variants of the Validation structure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
The container for instance methods for Success variants of the Validation structure.
-Performs a deep-comparison of two Validation values for equality.
-Tests if an arbitrary value is a Validation instance.
-True if the value is a Success
instance.
Constructs a Validation holding a Success value.
-Transforms a Validation into a Maybe. Failure values are lost in the process.
-Transforms a Validation into a Reseult.
-The value contained in an Success instance of the Validation structure.
-A textual representation for Validation instances.
-A textual representation for Validation instances.
-This method has been renamed to unsafeGet()
.
Extracts the value of a Validation structure, if it's a Success, otherwise returns the provided default value.
-Returns the value inside of the Validation structure, regardless of its state.
-Extracts the value from a Validation
structure.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
Part of the Semigroup instance for Fantasy Land 2.x+. See the concat
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Allows a function to provide functionality to variants in an ADT.
-Applies a function to each case of a Validation.
-Chooses and executes a function for each variant in the Validation structure.
-Parses a JavaScript object previously serialised as aValidation.toJSON()
into a proper Validation structure.
Converts a Validation value to a JavaScript object that may be stored as a JSON value.
-The constructor for this variant.
-The variants in the Validation structure.
-Transforms each side of a Validation with a function, without changing the status of the validation. That is, failures will still be failures, successes will still be successes.
-Transforms the successful value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-Transforms the failure value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-Constructs a Validation whose value represents a failure.
-Constructs a Validation whose value represents a success.
-InternalConstructor.prototype
Provides a textual representation for ADTs.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Provides a textual representation for ADTs.
-The debugRepresentation
serialisation bestows ES2015's Symbol.toStringTag
, used
-for the native Object.prototype.toString
, along with a .toString()
-method and Node's REPL .inspect()
method.
const { data, derivations } = require('folktale/core/adt');
-const { Id } = data('Id', {
- Id(value){ return { value } }
-}).derive(derivations.debugRepresentation);
-
-Object.prototype.toString.call(Id(1));
-// => '[object Id.Id]'
-
-Id(1).toString();
-// ==> 'Id.Id({ value: 1 })'
-
-Id(1).inspect();
-// ==> 'Id.Id({ value: 1 })'
-
This derivation defines ES2015's ToStringTag
symbol, which is used
-by Object.prototype.toString to construct a default textual
-representation of the object.
This means that instead of getting '[object Object]'
, you'll get
-a more helpful '[object <Type>.<Tag>]'
representation, where this
-function is used.
This derivation defines a .toString()
method. .toString
is called
-in many places to define how to represent an object, but also called
-when JS operators want to convert an object to a String. This derivation
-only cares about representation that's suitable for debugging.
The representation includes the type, tag, and key/value pairs of the -data structure.
-Node's REPL uses .inspect()
instead of the regular .toString()
.
-This derivation also provides the .inspect()
method, but just as
-an alias for the .toString()
method.
(variant, adt) => { // eslint-disable-line max-statements
- const typeName = adt[typeSymbol];
- const variantName = `${adt[typeSymbol]}.${variant.prototype[tagSymbol]}`;
-
- // (for Object.prototype.toString)
- adt[Symbol.toStringTag] = typeName;
- variant.prototype[Symbol.toStringTag] = variantName;
-
- // (regular JavaScript representations)
- /*~
- * stability: experimental
- * module: null
- * authors:
- * - "@boris-marinov"
- *
- * type: |
- * () => String
- */
- adt.toString = () => typeName;
-
- /*~
- * stability: experimental
- * mmodule: null
- * authors:
- * - "@boris-marinov"
- *
- * type: |
- * () => String
- */
- variant.toString = () => variantName;
-
- /*~
- * stability: experimental
- * module: null
- * authors:
- * - "@boris-marinov"
- *
- * type: |
- * (ADT).() => String
- */
- variant.prototype.toString = function() {
- return `${variantName}(${plainObjectToString.call(this)})`;
- };
-
- // (Node REPL representations)
- adt.inspect = adt.toString;
- variant.inspect = variant.toString;
- variant.prototype.inspect = variant.prototype.toString;
-
- return variant;
-}
Provides structural equality for ADTs.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Provides structural equality for ADTs.
-The equality
derivation bestows Fantasy Land's fantasy-land/equals
-method upon ADTs constructed by Core.ADT, as well as an equals
-alias. This equals
method performs structural equality, and may
-be configured on how to compare values that don't implement Equality.
const { data, derivations } = require('folktale/core/adt');
-const Result = data('Result', {
- Ok(value){
- return { value };
- },
- Error(value) {
- return { value };
- }
-}).derive(derivations.equality);
-const { Ok, Error } = Result;
-
-Ok(1).equals(Ok(1));
-// ==> true
-
-Ok(1).equals(Error(1));
-// ==> false
-
-Error(Error(1)).equals(Error(Error(1)));
-// ==> true
-
The equals
method provided by this derivation checks for structural
-equivalence. That is, two values are considered equal if they have the
-same content.
For simple ADTs this is pretty easy to see. For example, consider the -following definition:
-const { data, derivations } = require('folktale/core/adt');
-const Id = data('Id', {
- Id(value){ return { value } }
-}).derive(derivations.equality);
-
-const a = Id.Id(1);
-const b = Id.Id(1);
-
Here we have an ADT with a single case, Id
, and we've made two
-instances of this data structure, each containing the value 1
.
-However, if we try to compare them using JavaScript standard
-operators, we'll not be comparing their contents, but rather whether
-or not they are the same object:
a === a; // ==> true
-b === b; // ==> true
-a === b; // ==> false
-
So a === b
is false, even though both a
and b
have the same
-contents. This is because ===
compares values by their identity,
-and each object has a different identity.
If we want to compare things by value, we can use the equals
method
-provided by this equality derivation instead:
a.equals(b); // ==> true
-a.equals(a); // ==> true
-a.equals(Id.Id(2)); // ==> false
-
When comparing with the equals
method, two values are considered
-equal if they represent the same value. This is called structural
-equality.
Given two data structures, they are considered equal if:
-The following example shows these in practice:
-const { data, derivations } = require('folktale/core/adt');
-
-// ┌◦ TYPE
-// ┌╌┴╌╌╌╌┐
-const Result = data('Result', {
-// ┌◦ TAG ┌◦ KEYS
-// ┌┴┐ ┌╌┴╌╌╌╌╌┐
- Ok(value){ return { value } },
-
-// ┌◦ TAG ┌◦ KEYS
-// ┌╌┴╌┐ ┌╌┴╌╌╌╌╌┐
- Error(value){ return { value } }
-}).derive(derivations.equality);
-
-const { Ok, Error } = Result;
-
So here we have the Result
ADT. Values created from this ADT always
-have the same type: "Result". A type is expected to be unique within
-all ADTs in a program:
Ok(1)[data.typeSymbol]; // ==> 'Result'
-Error(1)[data.typeSymbol]; // ==> 'Result'
-
Each variant has its own tag, which is the name you give to the -constructor. A tag is unique within an ADT, but not necessarily unique -amongst other ADTs:
-Ok(1)[data.tagSymbol]; // ==> 'Ok'
-Error(1)[data.tagSymbol]; // ==> 'Error'
-
Finally, the keys in an ADT are the same as the keys which the constructor
-returns. So, in this case, both Ok and Error have [value]
as the key:
Object.keys(Ok(1)); // ==> ['value']
-Object.keys(Error(1)); // ==> ['value']
-
So if we compare these two for equality:
-Ok(1).equals(Ok(1)); // ==> true
-// same type, tag, keys and values.
-
-Ok(1).equals(Ok(2)); // ==> false
-// same type, tag, and keys. Different values (1 !== 2).
-
-Ok(1).equals(Error(1)); // ==> false
-// same type, keys, and values. Different tags ('Ok' !== 'Error').
-
-const { Error: E } = data('Res', {
- Error(value){ return { value } }
-}).derive(derivations.equality);
-
-E(1).equals(Error(1)); // ==> false
-// same tag, keys, and values. Different types ('Result' !== 'Res')
-
The values in an ADT aren't always JS primitives, such as numbers and
-strings. Equality's equals
method handles these in two different ways:
If the values implement Equality, then the values are compared using the
-left's equals
method. This means that if all values implement Equality
-or are primitives, deep equality just works.
If the values do not implement Equality, the provided equality comparison is
-used to compare both values. By default, this comparison tests plain objects
-and arrays structurally, and all other values with ===
.
Here's an example:
-const { data, derivations } = require('folktale/core/adt');
-const { Id } = data('Id', {
- Id(value){ return { value } }
-}).derive(derivations.equality);
-
-// This is fine, because all values implement Equality or are primitives
-Id(Id(1)).equals(Id(Id(1))); // ==> true
-
-// This is not fine, because they're arrays
-Id([1]).equals(Id([1])); // ==> true
-
-// This is fine, because they're plain objects
-Id({ a: 1 }).equals(Id({ a: 1 })); // ==> true
-
-const { Other } = data('Other', {
- Other(value) { return { value } }
-});
-
-// This isn't fine, because they're not plain objects
-Id({ value: 1 }).equals(Id(Other(1))); // ==> false
-
A plain object is any object that doesn't overwrite the .toString
-or Symbol.toStringTag
properties.
To handle complex JS values, one must provide their own deep equality
-function. Folktale does not have a deep equality function yet, but
-most functional libraries have an equals
function for that.
Here's an example of an equality function that checks array equality:
-const isEqual = (a, b) =>
- Array.isArray(a) && Array.isArray(b) ? arrayEquals(a, b)
-: a == null ? a === b
-: a['fantasy-land/equals'] ? a['fantasy-land/equals'](b)
-: a.equals ? a.equals(b)
-: a === b;
-
-const arrayEquals = (a, b) =>
- Array.isArray(a) && Array.isArray(b)
-&& a.length === b.length
-&& a.every((x, i) => isEqual(x, b[i]));
-
-const { Id: Id2 } = data('Id', {
- Id(value){ return { value } }
-}).derive(derivations.equality.withCustomComparison(isEqual));
-
-Id2([1]).equals(Id2([1])); // ==> true
-Id2(Id2(1)).equals(Id2(Id2(1))); // ==> true
-Id2(2).equals(Id2(1)); // ==> false
-
Because the equals
method is defined directly in objects,
-and invoked using the method call syntax, it creates an asymmetry
-problem. That is, if there are two objects, a
and b
, then
-a equals b
is not the same as b equals a
, since the equals
-method may be different on those objects!
Here's an example of the asymmetry problem:
-const { data, derivations } = require('folktale/core/adt');
-const { Id } = data('Id', {
- Id(value){ return { value } }
-}).derive(derivations.equality);
-
-const bogus = {
- equals(that){ return that.value === this.value },
- value: 1
-};
-
-// This is what you expect
-Id(1).equals(bogus); // ==> false
-
-// And so is this
-Id(Id(1)).equals(Id(bogus)); // ==> false
-
-// But this is not
-Id(bogus).equals(Id(Id(1))); // ==> true
-
To avoid this problem all Equality implementations should do type
-checking and make sure that they have the same equals
method.
-Equality implementations derived by this derivation do so by
-checking the type
and tag
of the ADTs being compared.
There are no optimisations for deep equality provided by this method, -thus you should expect it to visit every value starting from the root. -This can be quite expensive for larger data structures.
-If you expect to be working with larger data structures, and check -equality between them often, you are, usually, very out of luck. You -may consider providing your own Equality implementation with the following -optimisations:
-If two objects are the same reference, you don't need to check
-them structurally, for they must be equal — Equality's .equals
-does this, but if you're providing your own equality function,
-you must do it there as well;
If two objects have the same type, but different hashes, then -they must have different values (assuming you haven't messed up -your hash function);
-If two objects have the same type, and the same hashes, then they -might be equal, but you can't tell without looking at all of its -values.
-Here's an example of this optimisation applied to linked lists that -can only hold numbers (with a very naive hash function):
-const hash = Symbol('hash code');
-const { data, derivations } = require('folktale/core/adt');
-
-const { Cons, Nil } = data('List', {
- Nil(){ return { [hash]: 0 } },
- Cons(value, rest) {
- return {
- [hash]: value + rest[hash],
- value, rest
- }
- }
-});
-
-Nil.prototype.equals = function(that) {
- return Nil.hasInstance(that);
-}
-
-Cons.prototype.equals = function(that) {
- if (this === that) return true
- if (!Cons.hasInstance(that)) return false
- if (this[hash] !== that[hash]) return false
-
- return this.value === that.value
- && this.rest.equals(that.rest)
-}
-
-const a = Cons(1, Cons(2, Cons(3, Nil())));
-const b = Cons(1, Cons(2, Cons(3, Nil())));
-const c = Cons(1, b);
-
-a.equals(b); // ==> true
-a.equals(c); // ==> false
-
--NOTE:
-
You should use a suitable hashing algorithm for your data structures.
(valuesEqual) => {
- /*~
- * type: ('a, 'a) => Boolean
- */
- const equals = (a, b) => {
- // identical objects must be equal
- if (a === b) return true;
-
- // we require both values to be setoids if one of them is
- const leftSetoid = isSetoid(a);
- const rightSetoid = isSetoid(b);
- if (leftSetoid) {
- if (rightSetoid) return flEquals(a, b);
- else return false;
- }
-
- // fall back to the provided equality
- return valuesEqual(a, b);
- };
-
-
- /*~
- * type: (Object Any, Object Any, Array String) => Boolean
- */
- const compositesEqual = (a, b, keys) => {
- for (let i = 0; i < keys.length; ++i) {
- const keyA = a[keys[i]];
- const keyB = b[keys[i]];
- if (!(equals(keyA, keyB))) {
- return false;
- }
- }
- return true;
- };
-
-
- const derivation = (variant, adt) => {
- /*~
- * stability: experimental
- * module: null
- * authors:
- * - "@boris-marinov"
- * - Quildreen Motta
- *
- * type: |
- * forall S, a:
- * (S a).(S a) => Boolean
- * where S is Setoid
- */
- variant.prototype.equals = function(value) {
- assertType(adt)(`${this[tagSymbol]}#equals`, value);
- return sameType(this, value) && compositesEqual(this, value, Object.keys(this));
- };
- provideAliases(variant.prototype);
- return variant;
- };
- copyDocs(createDerivation, derivation, {
- type: '(Variant, ADT) => Void'
- });
-
-
- return derivation;
-}
Pre-built derivations for Folktale ADTs.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Pre-built derivations for Folktale ADTs.
-Provides a textual representation for ADTs.
-Provides structural equality for ADTs.
-Provides JSON serialisation and parsing for ADTs.
-{
- serialization: require('./serialization'),
- equality: require('./equality'),
- debugRepresentation: require('./debug-representation')
-}
Provides JSON serialisation and parsing for ADTs.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Provides JSON serialisation and parsing for ADTs.
-The serialization
derivation bestows .toJSON()
and .fromJSON(value)
-upon ADTs constructed by Core.ADT. Both serialisation and parsing
-are recursive, and .fromJSON
can automatically reify values of
-other types.
const { data, derivations } = require('folktale/core/adt');
-const Id = data('Id', {
- Id(value){ return { value } }
-}).derive(
- derivations.serialization,
- derivations.equality
-);
-
-Id.Id(1).toJSON();
-// ==> { '@@type': 'Id', '@@tag': 'Id', '@@value': { value: 1 } }
-
-Id.fromJSON(Id.Id(1).toJSON());
-// ==> Id.Id(1)
-
This derivation provides JSON serialisation through the .toJSON
method,
-which converts rich ADTs into objects that can be safely serialised as
-JSON. For example:
const { data, derivations } = require('folktale/core/adt');
-
-const { Id } = data('Id', {
- Id(value){ return { value } }
-}).derive(derivations.serialization);
-
-Id(1).toJSON();
-// ==> { '@@type': 'Id', '@@tag': 'Id', '@@value': { value: 1 } }
-
During the transformation, if any of the values contains a .toJSON
-method, that's called to serialise the structure. Otherwise the value is
-just returned as-is:
Id(Id(1)).toJSON();
-// ==> { '@@type': 'Id', '@@tag': 'Id', '@@value': { value: { '@@type': 'Id', '@@tag': 'Id', '@@value': { 'value': 1 } } } }
-
It's not necessary to call the .toJSON()
method directly in most cases, since
-JSON.stringify
will already invoke that for you:
JSON.stringify(Id(1));
-// ==> '{"@@type":"Id","@@tag":"Id","@@value":{"value":1}}'
-
-JSON.stringify(Id([Id(1)]));
-// ==> '{"@@type":"Id","@@tag":"Id","@@value":{"value":[{"@@type":"Id","@@tag":"Id","@@value":{"value":1}}]}}'
-
The reverse process of serialisation is parsing, and the .fromJSON
method
-provided by this derivation is able to reconstruct the proper ADT from
-serialised data:
const { data, derivations } = require('folktale/core/adt');
-
-const Id = data('Id', {
- Id(value){ return { value } }
-}).derive(
- derivations.serialization,
- derivations.equality
-);
-
-const json = Id.Id(1).toJSON();
-Id.fromJSON(json);
-// ==> Id.Id(1)
-
In general, as long as the values in an ADT are either ADT instances or simple -values supported by JSON, the following equivalence holds:
-ADT.fromJSON(adt.toJSON()) = adt
-
Some ADTs instances may contain other ADT instances as values. Serialising them -is simple because JavaScript's dispatch takes care of selecting the correct -serialisation for us. With parsing we don't have that luck, so instead the -ADT takes a list of parsers as argument:
-const A = data('A', {
- A(value) { return { value } }
-}).derive(
- derivations.serialization,
- derivations.equality
-);
-
-const B = data('B', {
- B(value) { return { value } }
-}).derive(
- derivations.serialization,
- derivations.equality
-);
-
-A.fromJSON(A.A(B.B(1)).toJSON(), [A, B]);
-// ==> A.A(B.B(1))
-
In order to support the serialisation and parsing of ADTs, this module
-uses a specific format that encodes that information in the serialised
-data. This way, .toJSON()
produces values of this interface, and
-.fromJSON(value)
expects values of this interface:
type JSONSerialisation = {
- "@@type": String,
- "@@tag": String,
- "@@value": Object Any
-}
-
(variant, adt) => {
- const typeName = adt[typeSymbol];
- const tagName = variant.prototype[tagSymbol];
-
- /*~
- * stability: experimental
- * module: null
- * authors:
- * - "@boris-marinov"
- *
- * type: |
- * type JSONSerialisation = {
- * "@@type": String,
- * "@@tag": String,
- * "@@value": Object Any
- * }
- *
- * Variant . () => JSONSerialisation
- */
- variant.prototype.toJSON = function() {
- return {
- [typeJsonKey]: typeName,
- [tagJsonKey]: tagName,
- [valueJsonKey]: mapValues(this, serializeValue)
- };
- };
-
- /*~
- * stability: experimental
- * module: null
- * authors:
- * - "@boris-marinov"
- *
- * type: |
- * type JSONSerialisation = {
- * "@@type": String,
- * "@@tag": String,
- * "@@value": Object Any
- * }
- * type JSONParser = {
- * fromJSON: (JSONSerialisation, Array JSONParser) => Variant
- * }
- *
- * (JSONSerialisation, Array JSONParser) => Variant
- */
- adt.fromJSON = function(value, parsers = { [typeName]: adt }, keysIndicateType = false) {
- const valueTypeName = value[typeJsonKey];
- const valueTagName = value[tagJsonKey];
- const valueContents = value[valueJsonKey];
- assertType(typeName, valueTypeName);
- const parsersByType = keysIndicateType ? parsers
- : /*otherwise*/ indexByType(values(parsers));
-
- const parsedValue = mapValues(valueContents, parseValue(parsersByType));
- return extend(Object.create(adt[valueTagName].prototype), parsedValue);
- };
-}
Provides utilities to define tagged unions.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Provides utilities to define tagged unions.
-Modelling data is important for a range of reasons. From performance -to correctness to safety. Tagged unions give you a way of modelling -choices that forces the correct handling of them, unlike predicate-based -branching, such as the one used by if statements and other common -control flow structures.
-Most of the structures provided by Folktale are tagged unions. But
-Folktale also gives you a primitive for constructing new ones in an
-easy way. The data
function provided by this module achieves that
-goal:
const data = require('folktale/core/adt/data');
-
-const Maybe = data('Maybe', {
- Some(value){ return { value } },
- None() { return {} }
-});
-
-Maybe.Some(1).matchWith({
- Some: ({ value }) => `Found ${value}`,
- None: () => "Not found"
-});
-// ==> "Found 1"
-
Tagged unions constructed by this module allow one to easily bestow
-common functionality in them through the derive
function. For example,
-one could add the concept of equality to the Maybe
data structure
-constructed previously by using the Equality
derivation, which is also
-provided by this module:
const Equality = require('folktale/core/adt/derivations/equality');
-Maybe.derive(Equality);
-
-Maybe.Some(1).equals(Maybe.Some(1)); // ==> true
-Maybe.Some(2).equals(Maybe.Some(1)); // ==> false
-
These structures also provide a way of testing if a value belongs to
-an ADT in a cross-realm way using the .hasInstance
method on the ADT
-or variant:
Maybe.hasInstance(Maybe.None()); // ==> true
-Maybe.Some.hasInstance(Maybe.None()); // ==> false
-Maybe.Some.hasInstance(Maybe.Some(1)); // ==> true
-
See the documentation on the data
function for details.
Core.ADT provides features to construct tagged unions, and common -derivations for those structures. These operations are divided as -follows:
-Constructing Data Structures: functions that construct new -tagged unions.
-Extending ADTs: functions that allow one to extend existing -ADTs and variants with new functionality.
-Derivation: functions that can be used as derivations to -provide common functionality to ADTs.
-Constructs a tagged union data structure.
-Pre-built derivations for Folktale ADTs.
-{
- data: require('./data'),
- derivations: require('./derivations')
-}
Applies the function inside an applicative to the value of another applicative.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a, b:
- (F (a) => b, F a) => F b
-where F is Apply
Applies the function inside an applicative to the value of another applicative.
-Applies the function inside an applicative to the value of another applicative.
-Applies the function inside an applicative to the value of another applicative.
-(applicativeFunction, applicativeValue) =>
- isNew(applicativeValue) ? applicativeValue[ap](applicativeFunction)
-: isOld(applicativeFunction) ? warn(applicativeFunction.ap(applicativeValue))
-: /*otherwise*/ unsupported(applicativeFunction)
Applies the function inside an applicative to the value of another applicative.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a, b:
- (F (a) => b) => (F a) => F b
-where F is Apply
Applies the function inside an applicative to the value of another applicative.
-curry(2, apply)
Applies the function inside an applicative to the value of another applicative.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a, b:
- (F (a) => b).(F a) => F b
-where F is Apply
Applies the function inside an applicative to the value of another applicative.
-function(applicativeValue) {
- return apply(this, applicativeValue);
-}
Maps one function over each side of a Bifunctor.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a, b, c, d:
- (F a b, (a) => c, (b) => d) => F c d
-where F is Bifunctor
Maps one function over each side of a Bifunctor.
-Maps one function over each side of a Bifunctor.
-Maps one function over each side of a Bifunctor.
-(bifunctor, transformLeft, transformRight) =>
- isNew(bifunctor) ? bifunctor[flBimap](transformLeft, transformRight)
-: isOld(bifunctor) ? warn(bifunctor.bimap(transformLeft, transformRight))
-: /*otherwise*/ unsupported(bifunctor)
Maps one function over each side of a Bifunctor.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a, b, c, d:
- ((a) => c) => ((b) => d) => (F a b) => F c d
-where F is Bifunctor
Maps one function over each side of a Bifunctor.
-curry(3, (transformLeft, transformRight, bifunctor) => // eslint-disable-line no-magic-numbers
- bimap(bifunctor, transformLeft, transformRight)
-)
Maps one function over each side of a Bifunctor.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a, b, c, d:
- (F a b).((a) => c, (b) => d) => F c d
-where F is Bifunctor
Maps one function over each side of a Bifunctor.
-function(transformLeft, transformRight) {
- return bimap(this, transformLeft, transformRight);
-}
Transforms a monad with an unary function.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall C, a, b:
- (C a, (a) => C b) => C b
-where C is Chain
Transforms a monad with an unary function.
-Transforms a monad with an unary function.
-Transforms a monad with an unary function.
-(monad, transformation) =>
- isNew(monad) ? monad[flChain](transformation)
-: isOld(monad) ? warn(monad.chain(transformation))
-: /*otherwise*/ unsupported(monad)
Joins two semigroups.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall S, a:
- (S a, S a) => S a
-where S is Semigroup
Joins two semigroups.
-Joins two semigroups.
-Joins two semigroups.
-(semigroupLeft, semigroupRight) =>
- isNewSemigroup(semigroupLeft) ? semigroupLeft[flConcat](semigroupRight)
-: isOldSemigroup(semigroupLeft) ? warn(semigroupLeft.concat(semigroupRight))
-: /*otherwise*/ unsupported(semigroupLeft)
Curried versions of the fantasy-land functions.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Curried versions of the fantasy-land functions.
-Applies the function inside an applicative to the value of another applicative.
-Maps one function over each side of a Bifunctor.
-Transforms a monad with an unary function.
-Joins two semigroups.
-Returns the identity object for a monoid.
-Compares two setoids for equality.
-Transforms the contents of a Functor.
-Constructs an applicative containing the given value.
-{
- apply: require('./apply').curried,
- bimap: require('./bimap').curried,
- chain: require('./chain').curried,
- concat: require('./concat').curried,
- empty: require('./empty').curried,
- equals: require('./equals').curried,
- map: require('./map').curried,
- of: require('./of').curried
-}
Returns the identity object for a monoid.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Returns the identity object for a monoid.
-(monoid) =>
- isNew(monoid) ? monoid[flEmpty]()
-: isCtorNew(monoid) ? monoid.constructor[flEmpty]()
-: isOld(monoid) ? warn(monoid.empty())
-: isCtorOld(monoid) ? warn(monoid.constructor.empty())
-: /*otherwise*/ unsupported(monoid)
Compares two setoids for equality.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall S, a:
- (S a, S a) => Boolean
-where S is Setoid
Compares two setoids for equality.
-Compares two setoids for equality.
-Compares two setoids for equality.
-(setoidLeft, setoidRight) =>
- isNew(setoidLeft) ? setoidLeft[flEquals](setoidRight)
-: isOld(setoidLeft) ? warn(setoidLeft.equals(setoidRight))
-: /*otherwise*/ unsupported(setoidLeft)
Allows invoking methods of Fantasy Land structures without -worrying about the differences in multiple versions of the spec.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Allows invoking methods of Fantasy Land structures without -worrying about the differences in multiple versions of the spec.
-Applies the function inside an applicative to the value of another applicative.
-Constructs an applicative containing the given value.
-Maps one function over each side of a Bifunctor.
-Transforms the contents of a Functor.
-Transforms a monad with an unary function.
-Returns the identity object for a monoid.
-Joins two semigroups.
-Compares two setoids for equality.
-{
- apply: require('./apply'),
- concat: require('./concat'),
- chain: require('./chain'),
- empty: require('./empty'),
- map: require('./map'),
- of: require('./of'),
- equals: require('./equals'),
- bimap: require('./bimap'),
- curried: require('./curried'),
- infix: require('./infix')
-}
Method versions of the fantasy-land functions, supporting the
-structure::fn(...)
syntax.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Method versions of the fantasy-land functions, supporting the
-structure::fn(...)
syntax.
Applies the function inside an applicative to the value of another applicative.
-Maps one function over each side of a Bifunctor.
-Transforms a monad with an unary function.
-Joins two semigroups.
-Returns the identity object for a monoid.
-Compares two setoids for equality.
-Transforms the contents of a Functor.
-Constructs an applicative containing the given value.
-{
- apply: require('./apply').infix,
- bimap: require('./bimap').infix,
- chain: require('./chain').infix,
- concat: require('./concat').infix,
- empty: require('./empty').infix,
- equals: require('./equals').infix,
- map: require('./map').infix,
- of: require('./of').infix
-}
Transforms the contents of a Functor.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a, b:
- (F a, (a) => b) => F b
-where F is Functor
Transforms the contents of a Functor.
-Transforms the contents of a Functor.
-Transforms the contents of a Functor.
-(functor, transformation) =>
- isNew(functor) ? functor[flMap](transformation)
-: isOld(functor) ? warn(functor.map(transformation))
-: /*otherwise*/ unsupported(functor)
Constructs an applicative containing the given value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a:
- (F) => (a) => F a
-where F is Applicative
Constructs an applicative containing the given value.
-curry(2, of)
Constructs an applicative containing the given value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs an applicative containing the given value.
-function(value) {
- return of(this, value);
-}
Constructs an applicative containing the given value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall F, a:
- (F, a) => F a
-where F is Applicative
Constructs an applicative containing the given value.
-Constructs an applicative containing the given value.
-Constructs an applicative containing the given value.
-(applicative, value) =>
- isNew(applicative) ? applicative[flOf](value)
-: isCtorNew(applicative) ? applicative.constructor[flOf](value)
-: isOld(applicative) ? warn(applicative.of(value))
-: isCtorOld(applicative) ? warn(applicative.constructor.of(value))
-: /*otherwise*/ unsupported(applicative)
Provides essential functionality for functional programs.
-Provides essential functionality for functional programs.
-This module provides a range of features that are often used as -foundational building blocks in functional programming. It also -provides features for handling some common built-in JavaScript -structures in a functional way.
-The operations are divided as follows:
-Modelling Data: The Core.ADT module provides an -implementation of Tagged Unions with limited pattern matching. -This can be used to model your application's data more accurately, -and thus make it harder to misuse that data;
-Combining Functions: The Core.Lambda module provides
-operations on functions that allow combining functions together
-(like compose
) or changing how you apply functions (like curry
-or partialise
);
Working with Objects as Dictionaries: The Core.Object module -provides operations that let you treat regular JavaScript objects -as dictionaries. Several JavaScript APIs expect that usage of -objects, but the built-in operations don't support this well, -so the Core.Object module addresses that.
-Writing generic code with Fantasy-Land: The Core.FantasyLand -module takes care of the differences in the various versions of -the Fantasy Land spec, so you can write generic code that supports -many libraries implementing any version of the spec.
-Provides utilities to define tagged unions.
-Allows invoking methods of Fantasy Land structures without -worrying about the differences in multiple versions of the spec.
-{
- lambda: require('./lambda'),
- adt: require('./adt'),
- object: require('./object'),
- fantasyLand: require('./fantasy-land')
-}
Conveniently composes multiple functions.
-Conveniently composes multiple functions.
-Because compose
is limited to two functions, composing more than that
-is awkward:
const compose = require('folktale/core/lambda/compose')
-
-const inc = (x) => x + 1;
-const double = (x) => x * 2;
-const square = (x) => x * x;
-
-const incDoubleSquare = compose(inc, compose(double, square));
-incDoubleSquare(3);
-// ==> 19
-
In these cases one may use compose.all
, which is a variadic convenience
-for composing multiple functions:
const incDoubleSquare2 = compose.all(inc, double, square);
-incDoubleSquare2(3);
-// ==> 19
-
function(...fns) {
- /* eslint-disable no-magic-numbers */
- if (fns.length < 1) { // eslint-disable-next-line prefer-rest-params
- throw new TypeError(`compose.all requires at least one argument, ${arguments.length} given.`);
- }
- return fns.reduce(compose);
-}
Combines two unary functions, from right to left.
-Combines two unary functions, from right to left.
-const compose = require('folktale/core/lambda/compose');
-
-const inc = (x) => x + 1;
-const double = (x) => x * 2;
-
-compose(inc, double)(2);
-// ==> inc(double(2))
-
```
-You can put several @annotate
lines together to associate the same documentation with multiple objects:
```md
-Conveniently composes multiple functions.
-Conveniently composes function with the This-Binding syntax.
-(f, g) => (value) => f(g(value))
Conveniently composes function with the This-Binding syntax.
-Conveniently composes function with the This-Binding syntax.
-This is a free-method version of compose
that applies the this
-argument first, then the function it takes as argument. It's meant
-to be used with the This-Binding Syntax proposal.
const then = compose.infix;
-const inc = (x) => x + 1;
-const double = (x) => x * 2;
-
-inc::then(double)(2); // ==> 6
-
function(that) {
- return compose(that, this);
-}
The constant combinator; always returns the first argument given.
-The constant combinator; always returns the first argument given.
-const constant = require('folktale/core/lambda/constant');
-
-[1, 2, 3].map(constant(0));
-// ==> [0, 0, 0]
-
Constant combinators can be passed to higher-order operations if you -want to provide a plain value, but the operation expects a function -providing a value:
-const constant = require('folktale/core/lambda/constant');
-
-[1, 2, 3].map(constant(0));
-// ==> [0, 0, 0]
-
For a primitive, there's usually not much of a difference between
-using the constant
combinator or an arrow function. In fact, for
-most cases, using the arrow function is preferred:
[1, 2, 3].map(() => 0);
-// ==> [0, 0, 0]
-
The problem with using arrows is that the value is computed lazily.
-That is, it's computed only when the arrow is evaluated, and recomputed
-many times if the arrow is evaluated many times. The constant
combinator
-lets you evaluate something eagerly instead.
You can see the importance of this when effects are involved:
-let counter = 0;
-const next = () => ++counter;
-
-['a', 'b', 'c'].map(constant(next()));
-// ==> [1, 1, 1]
-
-counter = 0;
-['a', 'b', 'c'].map(_ => next());
-// ==> [1, 2, 3]
-
Expensive pure computations are another place where constant
is desirable
-over plain arrows, given that one'd rather avoid re-evaluating the
-computation unnecessarily.
(value) => (_) => value
Transforms functions of arity N into a chain of N unary functions
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
(Number, (Any...) => 'a) => Any... => 'a or ((Any...) => 'a)
Transforms functions of arity N into a chain of N unary functions
-const curry = require('folktale/core/lambda/curry');
-
-const property = curry(2, (key, object) => object[key]);
-const people = [
- { name: 'Alissa', age: 26 },
- { name: 'Max', age: 19 },
- { name: 'Talib', age: 28 }
-];
-
-people.map(property('name'));
-// ==> ['Alissa', 'Max', 'Talib']
-
Currying is the process of transforming a function that takes N arguments -all at once, into a series of functions that take one argument at a time. -This makes it possible to construct new funcionality by specialising part -of the arguments.
-For example, the property
function takes two arguments: the object that
-contains the property, and the name of the property to retrieve:
const property = (object, key) => object[key];
-
Because of this, every time someone call property
they must provide both
-arguments:
property({ greeting: 'Hi' }, 'greeting');
-// ==> 'Hi'
-
Some of the time, you only have some of the arguments, however. For example,
-if you wanted to create a function that always gets the greeting
of a
-particular object, you would have to do so manually:
const greeting = (object) => property(object, 'greeting');
-
-greeting({ greeting: 'Hi' });
-// ==> 'Hi'
-
Currying alleviates the need for constructing these functions manually. If
-property
was curried, you'd be able to create a new greeting
function
-by just specifying some of the arguments, like you would do when invoking
-the function:
const property2 = (key) => (object) => object[key];
-const greeting2 = property2('greeting');
-
-greeting({ greeting: 'Hi' });
-
Note that the way we use arguments in a curried function is slightly -different. When designing a function to be curried, you should consider -which parameters are most likely to be fixed, and which parameters will -be provided afterwards. In this case, it's much more likely for someone -to have the name of a property than it is for them to have the object -in which that property lives:
-const score = [{ type: 'win' }, { type: 'draw' }];
-score.map(property2('type'));
-// ==> ['win', 'draw']
-
Functional programming places a heavy emphasis on function composition,
-but sometimes we have functions that don't exactly fit the places we
-want to use them. For example, Array.prototype.map
expects a function
-that takes three arguments:
type Array.prototype.map =
- Array<'element> . (
- ('element, index is Number, Array<'element>) => 'newElement
- ) => Array<'newElement>
-
That is, given an array of of some element
type, our callback, which
-receives not only this element
, but also the index of that element in
-the array, and even the original array, is supposed to return a newElement
-to put in the old element's place.
We can use Array.prototype.map
to easily transform the data in the
-array by some function. To expand a bit on the initial example, suppose
-we have an array of people:
const people = [
- { name: 'Alissa', age: 26, pronoun: 'she' },
- { name: 'Max', age: 19, pronoun: 'they' },
- { name: 'Talib', age: 28, pronoun: 'he' }
-];
-
And then you want to get the name of one of those people. We can use
-Array.prototype.map
for this:
people.map((element, index, array) => element.name);
-// ==> ['Alissa', 'Max', 'Talib']
-
--NOTE
-
Because functions in JavaScript are variadic, you don't need to -create a function with three parameters here. The following code -is strictly equivalent:people.map(person => person.name); -// ==> ['Alissa', 'Max', 'Talib'] -
This is all well and good, because this is the only place where we use -this kind of functionality. But what if we have very similar functionality -in more places. For example, just like we got the names of these people, -we could get their ages:
-people.map(person => person.age);
-// ==> [26, 19, 28]
-
Or the pronoun they use:
-people.map(person => person.pronoun);
-// ==> ['she', 'they', 'he']
-
At this point, we're duplicating this functionality in many places. It -would be more productive to move it to a function we can reuse. And this -is easy enough:
-const property = (object, key) => object[key];
-
But this attempt is not really a good one in this case. Now composing -things is even more trouble:
-people.map(person => property(person, 'name'));
-
Ideally, we'd want:
-people.map(property);
-
But property
takes two arguments, and Array.prototype.map
can only
-provide one of them: the object we should retrieve the property from.
-Where do we get the other one from? Well, the name of the property is
-static. We know exactly what we need from the callsite.
As said in the previous section, thinking about which arguments are likely
-to be specified, and what aren't is important when designing curried
-functions. For this case, we want to specialise the key
, and leave
-object
to be provided by some other piece of code:
const property = (key) => (object) => object[key];
-
- people.map(property('name'));
- // ==> ['Alissa', 'Max', 'Talib']
-
- people.map(property('age'));
- // ==> [26, 19, 28]
-
- people.map(property('pronoun'));
- // ==> ['she', 'they', 'he']
-
So, with currying, it becomes much simpler to shape a function so it -fits the expectations of the place where you want to use it. It alleviates -the need of making this translation manually, but it also requires some -prior thought on how these functions are likely to be used.
-curry
Works?The curry
operation makes it simpler to construct curried functions
-that work well with JavaScript, where functions may, and often do, take
-more than one argument.
Consider the following example:
-const joinedBy = (separator) => (list) => (item) =>
- list.concat([separator, item]);
-
It's a curried function that takes 3 arguments, one at a time. To invoke -it one must do so like this:
-joinedBy(',')(['a'])('b');
-// ==> ['a', ',', 'b']
-
This makes it harder to use it for functions that pass two arguments to
-their callbacks, like Array.prototype.reduce
, because JavaScript passes
-them all at once:
['b'].reduce(joinedBy(','), ['a']);
-// ==> [<function>, <function>]
-
This is where curry
helps. It allows you to curry functions, while
-unrolling application of more than one argument:
const curry = require('folktale/core/lambda/curry');
-
-const joinedBy2 = curry(3, (separator, list, item) =>
- list.concat([separator, item])
-);
-
-joinedBy2(',', ['a'], 'b');
-// ==> ['a', ',', 'b']
-
-['b'].reduce(joinedBy2(','), ['a']);
-// ==> ['a', ',', 'b']
-
curry
, Under The HoodHow can curry
construct functions that support such different styles
-of passing arguments? The secret is in how curry
does unrolling. A
-function constructed by curry
takes two arguments:
In return, curry
gives you back a function that, at first, only collects
-arguments. That is, until we reach the amount of arguments expected (arity),
-applying the curry
ed function gives you back a new function that you
-continue to apply:
const curry = require('folktale/core/lambda/curry');
-
-const f = curry(4, (a, b, c, d) => [a, b, c, d]);
-
-// Previous arguments: []
-const f1 = f();
-// New arguments: []
-
-// Previous arguments: []
-const f2 = f1(1);
-// New arguments: [1]
-
-// Previous arguments: [1]
-const f3 = f2(2, 3);
-// New arguments: [1, 2, 3]
-
-// Previous arguments: [1, 2, 3]
-f3(4);
-// ==> [1, 2, 3, 4]
-
The curried function keeps track of these arguments in an internal array. -This array is not modified when you apply a curried function. Instead, you -get a new function with a separate "internal arguments array":
-// Previous arguments: [1]
-const f2_a = f2(4); // => [1, 4]
-const f2_b = f2(5); // => [1, 5]
-
-f2_a(5, 6); // ==> [1, 4, 5, 6]
-f2_b(5, 6); // ==> [1, 5, 5, 6]
-
Once the curried function has collected all of the arguments it needs to, -it "unrolls" the application. That is, it provides the arguments collected -to the original function:
-const plus = (a, b, c) => a + b + c;
-const plus2 = curry(3, plus);
-
- plus2(1)(2)(3)
-=== plus2(1, 2)(3)
-=== plus2(1, 2, 3)
-=== plus(1, 2, 3)
-=== 1 + 2 + 3;
-
What happens if a curried function receives more arguments than it expects,
-though? If the wrapped function is a regular JavaScript function, it's the
-same. curry
passes all of the arguments to it, and because JavaScript
-functions are variadic, those additional arguments get (usually) ignored:
plus2(1)(2)(3, 4, 5)
-=== plus2(1, 2)(3, 4, 5)
-=== plus2(1, 2, 3, 4, 5)
-=== plus(1, 2, 3, 4, 5)
-=== 1 + 2 + 3;
-
If the wrapped function is itself a curried function, things get more -interesting though, because the curried functio will, itself, unroll -the rest of the application!
-const subtract = curry(2, (x, y) => x - y);
-const flip = curry(3, (f, x, y) => f(y, x));
-
- subtract(1)(2)
-=== subtract(1, 2)
-=== 1 - 2;
-
- flip(subtract)(1)(2)
-=== flip(subtract, 1)(2)
-=== flip(subtract, 1, 2)
-=== subtract(2, 1)
-=== 2 - 1;
-
Unrolling makes it possible to compose curried functions naturally, without -getting in the way of regular JavaScript functions.
---NOTE
-
Usingcurry
for real variadic functions is strongly discouraged, given -that it's hard to predict which arguments will end up being provided to -the variadic function.
curry
While curry
certainly helps composing functions, it's important to note
-that, because a lot of functions in JavaScript are variadic, and because
-people take advantage of this (by relying on the number of arguments
-provided for optional parameters or overloading the signature),
-composition of such functions is not well-defined, and curry
makes
-things even less predictable for these cases.
Because of this, the use of curry
for variadic functions is strongly
-discouraged.
One also must consider the overhead of introducing curry
in a codebase.
-For most code, this overhead is negligible, but curry
should be avoided
-in code paths that require more performance.
(arity, fn) => {
- const curried = (oldArgs) => (...newArgs) => {
- const allArgs = oldArgs.concat(newArgs);
- const argCount = allArgs.length;
-
- return argCount < arity ? curried(allArgs)
- : /* otherwise */ fn(...allArgs);
- };
-
- return curried([]);
-}
Core.Lambda provides you tools for transforming and combining -functions.
-Core.Lambda provides you tools for transforming and combining -functions.
-Functional programming places a heavy emphasis on writing programs -by combining lots of small, focused functions. JavaScript doesn't -really have a good support for this out of the box, so you're left -with composing these functions manually, by defining a new function.
-This is not so bad in ECMAScript 2015, thanks to the new arrow -function syntax:
-const people = [
- { name: 'Alissa' },
- { name: 'Max' },
- { name: 'Talib' }
-];
-people.map(person => person.name);
-// ==> ['Alissa', 'Max', 'Talib']
-
But there are times in which arrow functions don't quite cut it. -For example, if one wants to evaluate something eagerly, a constant -combinator makes more sense:
-const counter = {
- value: 0,
- next() { return ++this.value },
- reset() { this.value = 0 }
-};
-const constant = require('folktale/core/lambda/constant');
-
-counter.reset();
-[0, 0, 0].map(constant(counter.next()));
-// ==> [1, 1, 1]
-
-// Arrows are evaluated lazily, so they don't work for this
-counter.reset();
-[0, 0, 0].map(_ => counter.next());
-// ==> [1, 2, 3]
-
-// One must store the value somewhere instead
-counter.reset();
-[0, 0, 0].map((x => _ => x)(counter.next()))
-
Core.Lambda provides combinators and operations that transform the -signature of a function. The operations in the module are divided -as thus:
-Combining: contains functions that combines functionality -present in different functions into a single function. Composing -functions is an example.
-Combinators: functions that just re-arrange the arguments -they're given. They're convenient ways of writing a particular -operation, but don't have any special behaviour of their own, -nor use anything besides the arguments they're given. Constant -and Identity are common combinators.
-Currying and Partialisation: functions that transform -how parameters are provided to a function. Currying allows a -function to take parameters one at a time, whereas partialisation -allows one to provide some of the positional parameters without -executing the function before the rest is provided.
-Combines two unary functions, from right to left.
-The constant combinator; always returns the first argument given.
-The identity combinator; always returns the argument given.
-Transforms functions of arity N into a chain of N unary functions
-Creates a new function where some of the arguments are specified.
-{
- identity: require('./identity'),
- constant: require('./constant'),
- curry: require('./curry'),
- compose: require('./compose'),
- partialize: require('./partialize')
-}
The identity combinator; always returns the argument given.
-The identity combinator; always returns the argument given.
-const identity = require('folktale/core/lambda/identity');
-
-identity(1);
-// ==> 1
-
-[1, 2, 3].map(identity);
-// ==> [1, 2, 3]
-
There aren't many reasons to use the identity
combinator in real
-JavaScript code. Readability is the only compelling one. Figuring
-out the concept of identity
from reading the word identity
is
-easier than working your way through its implementation.
Compare:
-const identity = require('folktale/core/lambda/identity');
-
-either.bimap(identity, (counter) => counter + 1);
-
With:
-either.bimap(
- (failure) => failure,
- (counter) => counter + 1
-)
-
(value) => value
Represents a place in an argument list that needs to be filled.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Represents a place in an argument list that needs to be filled.
-hole
Creates a new function where some of the arguments are specified.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
(Number, (Any... => Any)) => ((hole | Any)...) => Any :: (throw TypeError)
Creates a new function where some of the arguments are specified.
-const partialize = require('folktale/core/lambda/partialize');
-
-const clamp = (min, max, number) =>
- number < min ? min
-: number > max ? max
-: number;
-
-const _ = partialize.hole;
-const clamp_ = partialize(3, clamp);
-
-const atLeast = clamp_(_, Infinity, _);
-const atMost = clamp_(-Infinity, _, _);
-
-atLeast(3, 2); // ==> 3
-atLeast(3, 5); // ==> 5
-
-atMost(5, 3); // ==> 3
-atMost(5, 10); // ==> 5
-
With higher-order programming, one often wants to specialise some of -the arguments of a function before passing it to another function. -This kind of configuration is often done by creating a new function -manually:
-const plus = (a, b) => a + b;
-const add5 = (x) => plus(5, x);
-
-[1, 2, 3].map(add5);
-// ==> [6, 7, 8]
-
And for most cases this is reasonable. For functions that take more
-parameters, this can be cumbersome, however. The partialize
function
-allows creating a new function by specialising some of the arguments,
-and filling the remaining ones when the function is called.
Places where the caller of the function should fill are specified as
-hole
, which is a special constant used by partialize
:
const partialize = require('folktale/core/lambda/partialize');
-
-const _ = partialize.hole;
-const partialAdd5 = partialize(2, plus)(5, _);
-[1, 2, 3].map(partialAdd5);
-// ==> [6, 7, 8]
-
Partial application and currying are related concepts. Currying -refers to transforming a function of arity N, into N functions of -arity 1. Partial application, on the other hand, refers to -fixing some (but not all) arguments of a function.
-Both concepts are used to improve function composition, where the -shape of the function you have does not reflect the shape of the -function expected by function you're calling. So, in essence, these -techniques transform the shape of your function to make them "fit" -some API.
-partialize
and curry
differ on how they achieve this, however.
-While curry
creates N functions, and lets you specify arguments
-one by one, partialize
requires you to specify all arguments at
-once, distinguishing which ones are fixed, and which ones have to
-be provided (using "holes").
Because of this, curry
can be more natural, but it requires that
-the APIs be designed thinking about currying before hand, and it
-often interacts poorly with JavaScript, due to the use of variadic
-functions. partialize
does not have such problems.
partialize
Works?The partialize
function transforms regular functions into
-functions that can accept holes for arguments that are not
-defined yet. Whenever a partial function receives a hole as
-an argument, it constructs a new function so the holes can
-be filled later:
const partialize = require('folktale/core/lambda/partialize');
-
-const clamp = (min, max, number) =>
- number < min ? min
-: number > max ? max
-: number
-
-const partialClamp = partialize(3, clamp);
-
In the example above, partialClamp
is a function that takes
-arguments that may or may not be holes. A hole is a special
-constant defined by partialize
itself. It's convenient to
-bind such constant to the _
binding:
const _ = partialize.hole;
-
A partial function is considered saturated when, among the -arguments provided to it, no hole exists. When a partial function -is saturated, its original behaviour is executed:
-partialClamp(3, 5, 6); // ==> 5
-
If a partial function is not saturated, then it its execution -results in a new partial function:
-const atLeast = partialClamp(_, Infinity, _);
-atLeast(5, 3); // ==> 5
-
-const atLeast5 = atLeast(5, _);
-atLeast5(3); // ==> 5
-
Note that to prevent confusing behaviour, Folktale's partialize
-forces you to always pass the exact number of arguments that the
-partial function expects. Passing more or less arguments to a
-partial function is a TypeError. This ensures that all new partial
-functions can properly invoke the original behaviour when saturated,
-rather than returning previous unsaturated functions.
partialize
partialize
is a convenience function for transforming the shape
-of functions, and it relies on variadic application, as well as
-doing a fair bit of processing before each call to determine
-saturation. Combined, these make partialize
a poor choice for
-any code that needs to be performant.
Represents a place in an argument list that needs to be filled.
-(arity, fn) => (...args) => { // eslint-disable-line max-statements
- /* eslint-disable no-magic-numbers */
- if (args.length < arity) {
- throw new TypeError(`The partial function takes at least ${arity} arguments, but was given ${args.length}.`);
- }
-
- // Figure out if we have holes
- let holes = 0;
- for (let i = 0; i < args.length; ++i) {
- if (args[i] === hole) {
- holes += 1;
- }
- }
-
-
- if (holes > 0) {
- return partialize(holes, (...newArgs) => { // eslint-disable-line max-statements
- let realArgs = []; // eslint-disable-line prefer-const
- let argIndex = 0;
-
- for (let i = 0; i < args.length; ++i) {
- const arg = args[i];
- if (arg === hole) {
- realArgs.push(newArgs[argIndex]);
- argIndex += 1;
- } else {
- realArgs.push(arg);
- }
- }
-
- return fn(...realArgs);
- });
- } else {
- return fn(...args);
- }
-}
Constructs an object from an array of (key, value) pairs.
-Constructs an object from an array of (key, value) pairs.
- The resulting object is a plain JavaScript object, inheriting from
-Object.prototype
.
The pairs are added to the object with Object.defineProperty
, so no setters
-defined in Object.prototype
will be triggered during the process. All
-properties are enumerable, writable, and configurable.
const fromPairs = require('folktale/core/object/from-pairs');
-
-fromPairs([['x', 10], ['y', 20]]);
-// ==> { x: 10, y: 20 }
-
Properties are inserted in the object in the same order of the array. In an -ECMAScript 2015-compliant engine this means that the following equivalence -holds:
-const fromPairs = require('folktale/core/object/from-pairs');
-
-Object.keys(fromPairs(xs)) === xs.map(([k, v]) => k)
-
However, in engines that don't conform to ECMAScript 2015, this equivalence -is not guaranteed.
-(pairs) =>
- pairs.reduce((r, [k, v]) => define(r, k, { value: v,
- writable: true,
- enumerable: true,
- configurable: true
- }),
- {})
Core.Object provides utilities for working with objects as -dictionaries and records.
-Core.Object provides utilities for working with objects as -dictionaries and records.
-JS historically lacked data structures designed specifically for -representing records and dictionaries, and objects just doubled -as those for the common use cases. While JS has added Map and -Set structures natively, using objects as records and dictionaries -is still common place, and there aren't many tools to use them -for those use cases natively.
-For example, these objects are used as options for configuring
-operations, like babylon's parse
operation, or Node's readFile
.
-They're used to represent headers in most HTTP libraries, or to
-represent environment variables in Node, etc.
Folktale's Core.Object primarily aims to provide common tools for -using objects as dictionaries. In doing so, most of the operations -in this module are only concerned about own, enumerable properties, -and don't respect the original object's shape. That is, a -transformation such as:
-toPairs(fromPairs(object))
-
Doesn't return a value necessarily equivalent to object
. Because
-all transformations are pure, objects get a new identity, they also
-lose all symbols and non-enumerable properties, as well as the
-[[Prototype]]
field.
Currently Core.Object provides operations for converting from and to -objects, and transforming objects. These operations are categorised -as follows:
-Converting: Operations that convert the data in the object -to other types.
-Transforming: Operations that transform the data in the -object, giving you a new object.
-Constructs an object from an array of (key, value) pairs.
-Returns pairs of (key, value)
for all own enumerable properties in an object.
Returns the values for all own enumerable properties in an object.
-Transforms own properties of an object using a mapping function.
-Transforms values of an object with an unary function.
-{
- mapEntries: require('./map-entries'),
- mapValues: require('./map-values'),
- values: require('./values'),
- toPairs: require('./to-pairs'),
- fromPairs: require('./from-pairs')
-}
Transforms own properties of an object using a mapping function.
-(
- object : Object 'a,
- transform : ((String, 'a)) => (String, 'b),
- define : (('x : Object 'b), String, 'b) => Object 'b :: mutates 'x
-) => Object 'b
Transforms own properties of an object using a mapping function.
-The transformation takes a [key, value]
pair, and is expected to return
-a new [key, value]
pair. The resulting object has not only its values
-transformed, but also its keys.
const mapEntries = require('folktale/core/object/map-entries');
-
-const pair = { x: 10, y: 20 };
-mapEntries(
- pair,
- ([key, value]) => [key.toUpperCase(), value * 2],
- (result, key, value) => {
- result[key] = value;
- return result;
- }
-);
-// ==> { X: 20, Y: 40 }
-
Since the mapping function returns a [key, value]
pair, it's possible
-that some of the returned keys collide with another. Since there's no
-single answer that is correct for all cases when handling these collisions,
-mapEntries expects an additional function that's used to define the
-properties in the resulting object, and this function is expected to
-deal with the collisions.
A definition function takes the result object, a property name, and -a value, and is expected to return a new object containing the provided -key/value pair, if it can be attached to the result object. This function -may mutate the object, but pure functions are also supported.
-Specialised forms of this function exist to cover common cases.
-mapEntries.overwrite
will have later key/value pairs overwrite earlier
-ones with the same key, while mapEntries.unique
will throw whenever
-a collision happens.
mapEntries
will not preserve the shape of the original object.
-It treats objects as plain maps from String to some value. It ignores
-things like prototypical delegation, symbols, and non-enumerable
-properties.
Transforms own properties of an object using a mapping function.
-Transforms own properties of an object using a mapping function.
-(object, transform, define) =>
- Object.keys(object).reduce((result, key) => {
- const [newKey, newValue] = transform([key, object[key]]);
- return define(result, newKey, newValue);
- }, {})
Transforms own properties of an object using a mapping function.
-(Object 'a, ((String, 'a)) => (String, 'b)) => Object 'b
Transforms own properties of an object using a mapping function.
-This function is a specialised form of mapEntries
that overwrites
-duplicated keys when a collision happens.
Because this function takes an object and maps over it, the result of a -transformation where keys collide is not defined in ECMAScript 5 and older, -as those engines don't define an ordering for key/value pairs in objects. -In ECMAScript 2015 properties that were inserted later will win over -properties that were inserted earlier.
-(object, transform) =>
- mapEntries(object, transform, (result, key, value) => {
- result[key] = value;
- return result;
- })
Transforms own properties of an object using a mapping function.
-(Object 'a, ((String, 'a)) => (String, 'b)) => Object 'b :: throws Error
Transforms own properties of an object using a mapping function.
-This function is a specialised form of mapEntries
that throws
-when a key collision happens. Throwing makes this function potentially
-unsafe to use, however it guarantees a consistent behaviour across
-different ECMAScript versions and VMs.
(object, transform) =>
- mapEntries(object, transform, (result, key, value) => {
- if (result::hasOwnProperty(key)) {
- throw new Error(`The property ${key} already exists in the resulting object.`);
- }
- result[key] = value;
- return result;
- })
Conveniently transforms values in an object using the This-Binding syntax.
-Conveniently transforms values in an object using the This-Binding syntax.
-This is a free-method version of mapValues
that applies the this
-argument first, then the function it takes as argument. It's meant to
-be used with the This-Binding Syntax proposal:
const map = require('folktale/core/object/map-values').infix;
-
-const pair = { x: 10, y: 20 };
-pair::map(x => x * 2);
-// ==> { x: 20, y: 40 }
-
function(transformation) {
- return mapValues(this, transformation);
-}
Transforms values of an object with an unary function.
-Transforms values of an object with an unary function.
-The transformation works on the values of each own, enumerable -property of the given object. Inherited and non-enumerable -properties are ignored by this function.
-const mapValues = require('folktale/core/object/map-values');
-
-const pair = { x: 10, y: 20 };
-mapValues(pair, x => x * 2);
-// ==> { x: 20, y: 40 }
-
mapValues
will not preserve the shape of the original object.
-It treats objects as plain maps from String to some value, and
-ignores things like prototypical delegation, symbols, and non-enumerable
-properties.
Conveniently transforms values in an object using the This-Binding syntax.
-(object, transformation) => {
- const keys = Object.keys(object);
- const result = {};
-
- for (let i = 0; i < keys.length; ++i) {
- const key = keys[i];
- result[key] = transformation(object[key]);
- }
-
- return result;
-}
Returns pairs of (key, value)
for all own enumerable properties in an object.
Returns pairs of (key, value)
for all own enumerable properties in an object.
const toPairs = require('folktale/core/object/to-pairs');
-
-const pair = { x: 10, y: 20 };
-toPairs(pair);
-// ==> [['x', 10], ['y', 20]]
-// (in ES5- VMs this may be [['y', 20], ['x', 10]])
-
Objects in JavaScript are commonly used as dictionaries, but natively
-there are no operations to work with them in that way. This function
-allows one to extract the (key, value)
pairs from an object:
const toPairs = require('folktale/core/object/to-pairs');
-
-const pair = { x: 10, y: 20 };
-toPairs(pair);
-// ==> [['x', 10], ['y', 20]]
-// or [['y', 20], ['x', 10]]
-
Inherited properties, and those that are not marked as enumerable, are -not returned in the resulting array:
-const p1 = { z: 2 };
-const pair2 = Object.create(p1);
-pair2.x = 10; pair2.y = 20;
-
-toPairs(pair2);
-// ==> [['x', 10], ['y', 20]]
-// or [['y', 20], ['x', 10]]
-
-// non-enumerable property x
-Object.defineProperty(p1, 'x', { value: 1 });
-
-toPairs(p1);
-// ==> [['z', 2]]
-
While ECMAScript 2015 specifies that objects are ordered using -insertion order, you're not guaranteed to get that behaviour in -any non-ES2015 engine, so for all effects it's better to treat -the result of this operation as an unordered collection.
-(object) => Object.keys(object).map(k => [k, object[k]])
Returns the values for all own enumerable properties in an object.
-Returns the values for all own enumerable properties in an object.
-const values = require('folktale/core/object/values');
-
-const pair = { x: 10, y: 20 };
-values(pair);
-// ==> [10, 20]
-// (In ES5- VMs this may be [20, 10])
-
Objects in JavaScript are commonly used as dictionaries, but natively -there are no operations to work with them in that way. This function -allows one to extract the values from an object:
-const values = require('folktale/core/object/values');
-
-const pair = { x: 10, y: 20 };
-values(pair);
-// ==> [10, 20]
-// or [20, 10]
-
Inherited properties, and those that are not marked as enumerable, are -not returned in the resulting array:
-const p1 = { z: 2 };
-const pair2 = Object.create(p1);
-pair2.x = 10; pair2.y = 20;
-
-values(pair2);
-// ==> [10, 20]
-// or [20, 10]
-
-// non-enumerable property x
-Object.defineProperty(p1, 'x', { value: 1 });
-
-values(p1);
-// ==> [2]
-
While ECMAScript 2015 specifies that objects are ordered using -insertion order, you're not guaranteed to get that behaviour in -any non-ES2015 engine, so for all effects it's better to treat -the result of this operation as an unordered collection.
-(object) => Object.keys(object).map(k => object[k])
Provides functions to convert from and to different data -structures.
-Provides functions to convert from and to different data -structures.
-Converts a Maybe
to an Result
. Nothing
s map to Error
s, Just
s map to
-Ok
s.
Converts a Maybe
to a Validation
. Nothing
s map to Failure
s, Just
s map
-to Success
es.
Converts an Result
structure to a Maybe structure. Error
s map to Nothing
s,
-Ok
s map to Just
s.
Converts an Result
to a Validation
. Error
s map to Failure
s, Ok
s map
-to Success
es.
Converts a Validation
to a Maybe
. Failure
s map to Nothing
s,
-Success
es map to Just
s.
Converts a Validation
to an Result
. Failure
s map to Error
s,
-Success
es map to Ok
s.
Converts a nullable value to a maybe. null
and undefined
map to
-Nothing
, any other value maps to Just
s.
Converts a nullable value to a Result
. null
and undefined
map to
-Error
s, any other value maps to Ok
s.
Converts a nullable value to a Validation
. null
and undefined
-map to Failure
s, any other type maps to Success
es.
{
- resultToValidation: require('./result-to-validation'),
- resultToMaybe: require('./result-to-maybe'),
- validationToResult: require('./validation-to-result'),
- validationToMaybe: require('./validation-to-maybe'),
- maybeToValidation: require('./maybe-to-validation'),
- maybeToResult: require('./maybe-to-result'),
- nullableToValidation: require('./nullable-to-validation'),
- nullableToResult: require('./nullable-to-result'),
- nullableToMaybe: require('./nullable-to-maybe')
-}
Converts a Maybe
to an Result
. Nothing
s map to Error
s, Just
s map to
-Ok
s.
forall a, b:
- (Maybe a, b) => Result b a
Converts a Maybe
to an Result
. Nothing
s map to Error
s, Just
s map to
-Ok
s.
Note that since Maybe
s don't hold a value for failures in the Nothing
tag,
-you must provide one to this function.
const maybeToResult = require('folktale/data/conversions/maybe-to-result');
-const { Error, Ok } = require('folktale/data/result');
-const { Nothing, Just } = require('folktale/data/maybe');
-
-maybeToResult(Nothing(), 2); // ==> Error(2)
-maybeToResult(Just(1), 2); // ==> Ok(1)
-
(aMaybe, failureValue) =>
- aMaybe.matchWith({
- Nothing: () => Error(failureValue),
- Just: ({ value }) => Ok(value)
- })
Converts a Maybe
to a Validation
. Nothing
s map to Failure
s, Just
s map
-to Success
es.
forall a, b:
- (Maybe a, b) => Validation b a
Converts a Maybe
to a Validation
. Nothing
s map to Failure
s, Just
s map
-to Success
es.
Note that since Maybe
failures can't hold a value in the Nothing
tag, you
-must provide one for the validation.
const maybeToValidation = require('folktale/data/conversions/maybe-to-validation');
-const { Failure, Success } = require('folktale/data/validation');
-const { Nothing, Just } = require('folktale/data/maybe');
-
-maybeToValidation(Nothing(), 2); // ==> Failure(2)
-maybeToValidation(Just(1), 2); // ==> Success(1)
-
(aMaybe, failureValue) =>
- aMaybe.matchWith({
- Nothing: () => Failure(failureValue),
- Just: ({ value }) => Success(value)
- })
Converts a nullable value to a maybe. null
and undefined
map to
-Nothing
, any other value maps to Just
s.
Converts a nullable value to a maybe. null
and undefined
map to
-Nothing
, any other value maps to Just
s.
A nullable is a value that may be any type, or null
/undefined
. Since
-Nothing
can't hold values, it's not possible to differentiate whether
-the original value was null
or undefined
after the conversion.
const nullableToMaybe = require('folktale/data/conversions/nullable-to-maybe');
-const { Nothing, Just } = require('folktale/data/maybe');
-
-nullableToMaybe(undefined); // ==> Nothing()
-nullableToMaybe(null); // ==> Nothing()
-nullableToMaybe(1); // ==> Just(1)
-
(a) =>
- a != null ? Just(a)
- :/*else*/ Nothing()
Converts a nullable value to a Result
. null
and undefined
map to
-Error
s, any other value maps to Ok
s.
Converts a nullable value to a Result
. null
and undefined
map to
-Error
s, any other value maps to Ok
s.
A nullable is a value that may be any type, or null
/undefined
.
const nullableToResult = require('folktale/data/conversions/nullable-to-result');
-const { Error, Ok } = require('folktale/data/result');
-
-nullableToResult(undefined); // ==> Error(undefined)
-nullableToResult(null); // ==> Error(null)
-nullableToResult(1); // ==> Ok(1)
-
(a) =>
- a != null ? Ok(a)
- :/*else*/ Error(a)
Converts a nullable value to a Validation
. null
and undefined
-map to Failure
s, any other type maps to Success
es.
forall a, b:
- (a or None, b) => Validation b a
Converts a nullable value to a Validation
. null
and undefined
-map to Failure
s, any other type maps to Success
es.
A nullable is a value that may be any type, or null
/undefined
.
const nullableToValidation = require('folktale/data/conversions/nullable-to-validation');
-const { Failure, Success } = require('folktale/data/validation');
-
-nullableToValidation(undefined, 'error');
-// ==> Failure('error')
-
-nullableToValidation(null, 'error');
-// ==> Failure('error')
-
-nullableToValidation(1, 'error');
-// ==> Success(1)
-
(a, fallbackValue) =>
- a != null ? Success(a)
- :/*else*/ Failure(fallbackValue)
Converts an Result
structure to a Maybe structure. Error
s map to Nothing
s,
-Ok
s map to Just
s.
Converts an Result
structure to a Maybe structure. Error
s map to Nothing
s,
-Ok
s map to Just
s.
Not that Error
values are lost in the conversion process, since failures
-in Maybe
(the Nothing
tag) don't have a value.
const resultToMaybe = require('folktale/data/conversions/result-to-maybe');
-const { Error, Ok } = require('folktale/data/result');
-const { Just, Nothing } = require('folktale/data/maybe');
-
-resultToMaybe(Error(1)); // ==> Nothing()
-resultToMaybe(Ok(1)); // ==> Just(1)
-
(aResult) =>
- aResult.matchWith({
- Error: ({ value: _ }) => Nothing(),
- Ok: ({ value }) => Just(value)
- })
Converts an Result
to a Validation
. Error
s map to Failure
s, Ok
s map
-to Success
es.
forall a, b:
- (Result a b) => Validation a b
Converts an Result
to a Validation
. Error
s map to Failure
s, Ok
s map
-to Success
es.
const resultToValidation = require('folktale/data/conversions/result-to-validation');
-const { Error, Ok } = require('folktale/data/result');
-const { Failure, Success } = require('folktale/data/validation');
-
-resultToValidation(Error(1)); // ==> Failure(1)
-resultToValidation(Ok(1)); // ==> Success(1)
-
(aResult) =>
- aResult.matchWith({
- Error: ({ value }) => Failure(value),
- Ok: ({ value }) => Success(value)
- })
Converts a Validation
to a Maybe
. Failure
s map to Nothing
s,
-Success
es map to Just
s.
forall a, b:
- (Validation a b) => Maybe b
Converts a Validation
to a Maybe
. Failure
s map to Nothing
s,
-Success
es map to Just
s.
Failure
values are lost in the process, since the Nothing
tag can't
-hold any values.
const validationToMaybe = require('folktale/data/conversions/validation-to-maybe');
-const { Failure, Success } = require('folktale/data/validation');
-const { Nothing, Just } = require('folktale/data/maybe');
-
-validationToMaybe(Failure(1)); // ==> Nothing()
-validationToMaybe(Success(1)); // ==> Just(1)
-
(aValidation) =>
- aValidation.matchWith({
- Failure: () => Nothing(),
- Success: ({ value }) => Just(value)
- })
Converts a Validation
to an Result
. Failure
s map to Error
s,
-Success
es map to Ok
s.
forall a, b:
- (Validation a b) => Result a b
Converts a Validation
to an Result
. Failure
s map to Error
s,
-Success
es map to Ok
s.
const validationToResult = require('folktale/data/conversions/validation-to-result');
-const { Error, Ok } = require('folktale/data/result');
-const { Failure, Success } = require('folktale/data/validation');
-
-validationToResult(Failure(1)); // ==> Error(1)
-validationToResult(Success(1)); // ==> Ok(1)
-
(aValidation) =>
- aValidation.matchWith({
- Failure: ({ value }) => Error(value),
- Success: ({ value }) => Ok(value)
- })
Implements common functional data structures in JavaScript.
-Implements common functional data structures in JavaScript.
-A data structure that models asynchronous actions, supporting safe cancellation and automatic resource handling.
-Provides functions to convert from and to different data -structures.
-A data structure that models the presence or absence of a value.
-A data structure that models the result of operations that may fail. A Result
-helps with representing errors and propagating them, giving users a more
-controllable form of sequencing operations than that offered by constructs like
-try/catch
.
A data structure that typically models form validations, and other scenarios where
-you want to aggregate all failures, rather than short-circuit if an error
-happens (for which Result
is better suited).
{
- conversions: require('./conversions'),
- maybe: require('./maybe'),
- result: require('./result'),
- validation: require('./validation'),
- future: require('./future'),
- task: require('./task')
-}
A convenience method for the folktale/data/conversions/nullable-to-maybe
module.
A convenience method for the folktale/data/conversions/nullable-to-maybe
module.
const Maybe = require('folktale/data/maybe');
-
-Maybe.fromNullable(1);
-// ==> Maybe.Just(1)
-
-Maybe.fromNullable(null);
-// ==> Maybe.Nothing()
-
-Maybe.fromNullable(undefined);
-// ==> Maybe.Nothing()
-
fromNullable(aNullable) {
- return require('folktale/data/conversions/nullable-to-maybe')(aNullable);
- }
A convenience method for the folktale/data/conversions/result-to-maybe
module.
A convenience method for the folktale/data/conversions/result-to-maybe
module.
Note that Error
values are discarded, since Nothing
can't hold a value.
const Maybe = require('folktale/data/maybe');
-const Result = require('folktale/data/result');
-
-Maybe.fromResult(Result.Ok(1));
-// ==> Maybe.Just(1)
-
-Maybe.fromResult(Result.Error(1));
-// ==> Maybe.Nothing()
-
fromResult(aResult) {
- return require('folktale/data/conversions/result-to-maybe')(aResult);
- }
A convenience method for the folktale/data/conversions/validation-to-maybe
module.
A convenience method for the folktale/data/conversions/validation-to-maybe
module.
Note that Failure
values are discarded, since Nothing
can't hold a value.
const Maybe = require('folktale/data/maybe');
-const Validation = require('folktale/data/validation');
-
-Maybe.fromValidation(Validation.Success(1));
-// ==> Maybe.Just(1)
-
-Maybe.fromValidation(Validation.Failure(1));
-// ==> Maybe.Nothing()
-
fromValidation(aValidation) {
- return require('folktale/data/conversions/validation-to-maybe')(aValidation);
- }
A data structure that models the presence or absence of a value.
-A data structure that models the presence or absence of a value.
-const Maybe = require('folktale/data/maybe');
-
-const find = (list, predicate) => {
- for (var i = 0; i < list.length; ++i) {
- const item = list[i];
- if (predicate(item)) {
- return Maybe.Just(item);
- }
- }
- return Maybe.Nothing();
-};
-
-find([1, 2, 3], (x) => x > 2); // ==> Maybe.Just(3)
-find([1, 2, 3], (x) => x > 3); // ==> Maybe.Nothing()
-
Some functions can always return a sensible result for all arguments that
-they're given. For example, the successor
function on natural numbers can
-always give back a valid result, regardless of which natural number we give it.
-These functions are easier to understand because their results are more
-predictable, and we don't have to worry about errors.
Not all functions have this property (of being total), though. Functions like -“find an item in this list” or “look up this key in that hashtable” don't always -have an answer, and so one has to think about how they deal with the cases where -the answer is not there. We have to be able to provide some kind of answer -to the programmer, otherwise the program can't continue — that is, not -providing an answer is the equivalent of throwing an exception.
-In most languages, things like “find an item in this list” will return null
-(or the equivalent “not an object”) when the item can't be found, but what if
-you had a null
in th list? In others, you can only ask the question “find me
-the index of this item in that list”, and when one index can't be found it
-answers -1
, assuming a 0-based indexed structure. But, again, what if I have
-an indexed structure where -1
is a valid index?
Really these questions require two answers: “is the item there?”, and if so, -“what is the item?”, and we often need to test for those answers separately. -Maybe is a data structure that helps answering these questions. A Maybe -structure has two cases:
-Just(value)
— represents the presence of an answer, and what the answer
-is.Nothing()
— represents the absence of an answer.If we have maybe, we can change our code from:
-const find1 = (list, predicate) => {
- for (var i = 0; i < list.length; ++i) {
- const item = list[i];
- if (predicate(item)) {
- return item;
- }
- }
- return null;
-};
-
-find1([1, 2, 3], (x) => x > 2); // ==> 3
-find1([1, 2, 3], (x) => x > 3); // ==> null
-
To:
-const Maybe = require('folktale/data/maybe');
-
-const find2 = (list, predicate) => {
- for (var i = 0; i < list.length; ++i) {
- const item = list[i];
- if (predicate(item)) {
- return Maybe.Just(item);
- }
- }
- return Maybe.Nothing();
-};
-
-find2([1, 2, 3], (x) => x > 2); // ==> Maybe.Just(3)
-find2([1, 2, 3], (x) => x > 3); // ==> Maybe.Nothing()
-
This has the advantage that it's always possible to determine whether a function
-failed or not. For example, if we run find1([null], x => true)
, then it'll
-return null
, but if we run find1([null], x => false)
it'll also return
-null
! On the other hand, running find2([null], x => true)
returns
-Maybe.Just(null)
, and find2([null], x => false)
returns Maybe.Nothing()
.
-They're different values that can be tested.
Another advantage of using a maybe value for these situations is that, since the -return value is wrapped, the user of that function is forced to acknowledge the -possibility of an error, as the value can't be used directly.
-Last section shows how to create Maybe values, but how do we use them? A value -wrapped in a Maybe can't be used directly, so using these values is a bit more -of work. Folktale's Maybe structure provides methods to help with this, and they -can be divided roughly into 3 categories:
-Extracting values: Sometimes we need to pass the value into things that -don't really know what a Maybe is, so we have to somehow extract the value -out of the structure. These methods help with that.
-Transforming values: Sometimes we get a Maybe value that doesn't quite -have the value we're looking for. We don't really want to change the status -of the computation (failures should continue to be failures, successes -should continue to be successes), but we'd like to tweak the resulting -value a bit. This is the equivalent of applying functions in an expression.
-Sequencing computations: A Maybe is the result of a computation that can
-fail. Sometimes we want to run several computations that may fail in
-sequence, and these methods help with that. This is roughly the equivalent
-of ;
in imperative programming, where the next instruction is only
-executed if the previous instruction succeeds.
We'll see each of these categories in more details below.
-If we're wrapping a value in a Maybe, then we can use the value by extracting it
-from that container. Folktale lets you do this through the getOrElse(default)
-method:
const Maybe = require('folktale/data/maybe');
-
-function get(object, key) {
- return key in object ? Maybe.Just(object[key])
- : /* otherwise */ Maybe.Nothing();
-}
-
-const config = {
- host: '0.0.0.0'
-};
-
-const host = get(config, 'host').getOrElse('localhost');
-const port = get(config, 'port').getOrElse(8080);
-
-`${host}:${port}`; // ==> '0.0.0.0:8080'
-
This works well if the only error handling we need to do is providing a default -value, which is a fairly common scenario when working with Maybe values. For -more advanced error handling Folktale provides more powerful methods that are -described later in this document.
-Sometimes we want to keep the context of the computation (whether it has failed
-or succeeded), but we want to tweak the value a little bit. For example, suppose
-you're trying to render the first item of a list, which involves generating some
-UI elements with the data from that object, but the list can be empty so you
-have to handle that error first. We can't use getOrElse()
here because if we
-have an error, we don't want to render that error in the same way. Instead, we
-can use map(transformation)
to apply our rendering logic only to successful
-values:
const Maybe = require('folktale/data/maybe');
-
-function first(list) {
- return list.length > 0 ? Maybe.Just(list[0])
- : /* otherwise */ Maybe.Nothing();
-}
-
-function render(item) {
- return ['item', ['title', item.title]];
-}
-
-first([{ title: 'Hello' }]).map(render);
-// ==> Maybe.Just(['item', ['title', 'Hello']])
-
-first([]).map(render);
-// ==> Maybe.Nothing()
-
Sometimes the functions we want to use to transform the value can also fail. We
-can't just use .map()
here since that'd put the resulting Maybe inside of
-another Maybe value:
const Maybe = require('folktale/data/maybe');
-
-function first(list) {
- return list.length > 0 ? Maybe.Just(list[0])
- : /* otherwise */ Maybe.Nothing();
-}
-
-function second(list) {
- return list.length > 1 ? Maybe.Just(list[1])
- : /* otherwise */ Maybe.Nothing();
-}
-
-function render(item) {
- return ['item', ['title', item.title]];
-}
-
-first([{ title: 'Hello' }]).map(render);
-// ==> Maybe.Just(['item', ['title', 'Hello']])
-
-first([{ title: 'Hello' }]).map(render)
- .map(second);
-// ==> Maybe.Just(Maybe.Just(['title', 'Hello']))
-
Ideally we'd like to get back Maybe.Just(['title', 'Hello'])
, but .map()
-isn't the method for that. Instead, we can use the .chain()
method. .chain()
-is a method that operates on Maybe values, and expects a function that also
-returns a Maybe value. This return value is then considered the whole result of
-the operation. Like .map()
, .chain()
only applies its function argument to
-Just
cases:
first([{ title: 'Hello' }]).map(render)
- .chain(second);
-// ==> Maybe.Just(['title', 'Hello'])
-
-first([]).map(render).chain(second);
-// ==> Maybe.Nothing()
-
So far we've seen how to use values that are wrapped in a Maybe, but if the -purpose of this structure is to represent something that might have failed, how -do we handle those failures?
-Well, a simple form of error handling is the .getOrElse(default)
method,
-covered in the previous sections, which allows us to extract a value from the
-Maybe structure, if it exists, or get a default value otherwise.
This doesn't help much if we need to do something in response to a failure,
-though. So, instead, we have the .orElse(handler)
method, which behaves quite
-similarly to the .chain()
method covered previously, except it executes its
-handler on Nothing
s, rather than on Just
s. We can use this to recover from
-errors:
const Maybe = require('folktale/data/maybe');
-
-function first(list) {
- return list.length > 0 ? Maybe.Just(list[0])
- : /* otherwise */ Maybe.Nothing();
-}
-
-let nextId = 1;
-
-function issueError() {
- return Maybe.Just(`Error #${nextId++}`);
-}
-
-first([1]).orElse(issueError);
-// ==> Maybe.Just(1)
-
-first([]).orElse(issueError);
-// ==> Maybe.Just('Error #1')
-
Note that the major difference between this and .getOrElse()
is that the
-handler function only gets ran on failure, whereas the expression in
-.getOrElse()
is always executed:
nextId; // ==> 2
-
-first([1]).getOrElse(issueError());
-// ==> 1
-
-nextId; // ==> 3
-
As with other union structures in Folktale, Maybe provides a .matchWith()
-method to perform a limited form of pattern matching. Pattern matching allows
-one to specify a piece of code for each case in a structure, like an if/else
-or switch
, but specific to that structure.
We could use .matchWith()
to run different computations depending on whether a
-Maybe value represents a success or a failure, for example, without the
-requirement of having to return a Maybe:
const Maybe = require('folktale/data/maybe');
-
-function first(list) {
- return list.length > 0 ? Maybe.Just(list[0])
- : /* otherwise */ Maybe.Nothing();
-}
-
-first([1]).matchWith({
- Just: ({ value }) => `Found: ${value}`,
- Nothing: () => 'Nothing was found'
-});
-// ==> 'Found: 1'
-
-first([]).matchWith({
- Just: ({ value }) => `Found: ${value}`,
- Nothing: () => 'Nothing was found'
-});
-// ==> 'Nothing was found'
-
Tests if an arbitrary value is a Maybe instance.
-Constructs a Maybe value that represents a successful value (a Just
).
A convenience method for the folktale/data/conversions/nullable-to-maybe
module.
A convenience method for the folktale/data/conversions/result-to-maybe
module.
A convenience method for the folktale/data/conversions/validation-to-maybe
module.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Parses a JavaScript object previously serialised as aMaybe.toJSON()
into a proper Maybe structure.
Constructs a Maybe value that represents a successful value (a Just
).
Constructs a Maybe value that represents a failure (a Nothing
).
{
- Just: Maybe.Just,
- Nothing: Maybe.Nothing,
- hasInstance: Maybe.hasInstance,
- of: Maybe.of,
- fromJSON: Maybe.fromJSON,
- [typeSymbol]: Maybe[typeSymbol],
- ['fantasy-land/of']: Maybe['fantasy-land/of'],
-
- /*~
- * stability: stable
- * type: |
- * forall a: (a or void) => Maybe a
- */
- fromNullable(aNullable) {
- return require('folktale/data/conversions/nullable-to-maybe')(aNullable);
- },
-
- /*~
- * stability: stable
- * type: |
- * forall a, b: (Result a b) => Maybe b
- */
- fromResult(aResult) {
- return require('folktale/data/conversions/result-to-maybe')(aResult);
- },
-
- /*~
- * stability: stable
- * type: |
- * forall a, b: (Validation a b) => Maybe b
- */
- fromValidation(aValidation) {
- return require('folktale/data/conversions/validation-to-maybe')(aValidation);
- }
-}
Constructs a Maybe value that represents a failure (a Nothing
).
Constructs a Maybe value that represents a failure (a Nothing
).
See the documentation for the Maybe structure to understand how to use this.
-Tests if an arbitrary value is a Nothing case of a Maybe instance.
-A textual representation of the Nothing variant.
-A textual representation of the Nothing variant.
-The constructor function for this variant.
-The container for instance methods for Nothing variants of the Maybe structure.
-The tag for this variant, unique among the Maybe variants.
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-Performs a deep-comparison of two Maybe values for equality. See core/adt/derivations/equality
for details.
Tests if an arbitrary value is a Maybe instance.
-True if the value is a Nothing
instance.
Constructs a Maybe value that represents a successful value (a Just
).
A convenience method for the folktale/data/conversions/maybe-to-result
module.
A convenience method for the folktale/data/conversions/maybe-to-validation
module.
A textual representation for Maybe instances.
-A textual representation for Maybe instances.
-This method has been renamed to unsafeGet()
.
Extracts the value of a Maybe structure, if it exists (i.e.: is a Just
),
-otherwise returns the provided default value.
Extracts the value from a Just
structure.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Allows a function to provide functionality to variants in an ADT.
-This method has been replaced by matchWith(pattern)
. cata
(morphism) selects
-and executes a function for each variant of the Maybe structure.
Applies a function to each variant of the Maybe structure.
-Chooses and executes a function for each variant in the Maybe structure.
-Allows recovering from from failed Maybe values.
-Parses a JavaScript object previously serialised as aMaybe.toJSON()
into a proper Maybe structure.
Converts a Maybe value to a JavaScript object that may be stored as a JSON value.
-The constructor function for this variant.
-The variants in the Maybe structure.
-Transforms a Maybe value using a function contained in another Maybe. As with
-.map()
, the Maybe values are expected to be Just
, and no operation is
-performed if any of them is a Nothing
.
Transforms an entire Maybe structure with the provided function. As with
-.map()
, the transformation is only applied if the value is a Just
, but
-unlike .map()
the transformation is expected to return a new Maybe
value.
Documentation for both entities goes here~
-Constructs a Maybe value that represents a successful value (a Just
).
Constructs a Maybe value that represents a failure (a Nothing
).
Nothing() {
- }
Transforms a Maybe value using a function contained in another Maybe. As with
-.map()
, the Maybe values are expected to be Just
, and no operation is
-performed if any of them is a Nothing
.
forall a, b: (Maybe (a) => b).(Maybe a) => Maybe b
Transforms a Maybe value using a function contained in another Maybe. As with
-.map()
, the Maybe values are expected to be Just
, and no operation is
-performed if any of them is a Nothing
.
const Maybe = require('folktale/data/maybe');
-
-function increment(value) {
- return value + 1;
-}
-
-Maybe.Just(increment).apply(Maybe.Just(1));
-// ==> Maybe.Just(2)
-
-Maybe.Just(increment).apply(Maybe.Nothing());
-// ==> Maybe.Nothing()
-
-Maybe.Nothing().apply(Maybe.Just(1));
-// ==> Maybe.Nothing()
-
{
- /*~*/
- Nothing: function apply(aMaybe) {
- assertMaybe('Maybe.Nothing#apply', aMaybe);
- return this;
- },
-
- /*~*/
- Just: function apply(aMaybe) {
- assertMaybe('Maybe.Just#apply', aMaybe);
- return aMaybe.map(this.value);
- }
- }
This method has been replaced by matchWith(pattern)
. cata
(morphism) selects
-and executes a function for each variant of the Maybe structure.
The cata
(morphism) terminology is not very welcoming for people
-who are not familiar with some obscure jargon in functional programming.
-In addition to that, due to the design of Folktale's 2 ADT constructor,
-it's not possible to provide the same interface as Folktale 1's .cata()
-method, so changing the name while deprecating the old functionality
-allows people to move to Folktale 2 without breaking their code.
forall a, b:
- (Maybe a).({
- Nothing: () => b,
- Just: (a) => b
- }) => b
This method has been replaced by matchWith(pattern)
. cata
(morphism) selects
-and executes a function for each variant of the Maybe structure.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).cata({
- Nothing: () => 'nothing',
- Just: (value) => `got ${value}`
-});
-// ==> 'got 1'
-
-Maybe.Nothing().cata({
- Nothing: () => 'nothing',
- Just: (value) => `got ${value}`
-});
-// ==> 'nothing'
-
{
- /*~*/
- Nothing: function cata(pattern) {
- warnDeprecation('`.cata(pattern)` is deprecated. Use `.matchWith(pattern)` instead.');
- return pattern.Nothing();
- },
-
- /*~*/
- Just: function cata(pattern) {
- warnDeprecation('`.cata(pattern)` is deprecated. Use `.matchWith(pattern)` instead.');
- return pattern.Just(this.value);
- }
- }
Transforms an entire Maybe structure with the provided function. As with
-.map()
, the transformation is only applied if the value is a Just
, but
-unlike .map()
the transformation is expected to return a new Maybe
value.
forall a, b: (Maybe a).((a) => Maybe b) => Maybe b
Transforms an entire Maybe structure with the provided function. As with
-.map()
, the transformation is only applied if the value is a Just
, but
-unlike .map()
the transformation is expected to return a new Maybe
value.
Having the transformation function return a new Maybe value means that the
-transformation may fail, and the failure is appropriately propagated. In this
-sense, a.chain(f)
works similarly to the sequencing of statements done by the
-;
syntax in JavaScript — the next instruction only runs if the previous
-instruction succeeds, and either instructions may fail.
const Maybe = require('folktale/data/maybe');
-
-function first(list) {
- return list.length > 0 ? Maybe.Just(list[0])
- : /* otherwise */ Maybe.Nothing();
-}
-
-first([]).chain(first);
-// ==> Maybe.Nothing()
-
-first([[1]]).chain(first);
-// ==> Maybe.Just(1)
-
-first([[]]).chain(first);
-// ==> Maybe.Nothing()
-
{
- /*~*/
- Nothing: function chain(transformation) {
- assertFunction('Maybe.Nothing#chain', transformation);
- return this;
- },
-
- /*~*/
- Just: function chain(transformation) {
- assertFunction('Maybe.Just#chain', transformation);
- return transformation(this.value);
- }
- }
Applies a function to each variant of the Maybe structure.
-forall a, b: (Maybe a).(() => b, (a) => b) => b
Applies a function to each variant of the Maybe structure.
-const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).fold(
- (() => 'nothing'),
- ((v) => `got ${v}`)
-);
-// ==> 'got 1'
-
-Maybe.Nothing().fold(
- (() => 'nothing'),
- ((v) => `got ${v}`)
-);
-// ==> 'nothing'
-
{
- /*~*/
- Nothing: function(transformNothing, transformJust) {
- assertFunction('Maybe.Nothing#fold', transformNothing);
- assertFunction('Maybe.Nothing#fold', transformJust);
- return transformNothing();
- },
-
- /*~*/
- Just: function(transformNothing, transformJust) {
- assertFunction('Maybe.Just#fold', transformNothing);
- assertFunction('Maybe.Just#fold', transformJust);
- return transformJust(this.value);
- }
- }
This method has been renamed to unsafeGet()
.
We want to discourage the use of partial functions, and having short names -makes it easy for people to want to use them without thinking about the -problems.
-For more details see https://github.com/origamitower/folktale/issues/42
-This method has been renamed to unsafeGet()
.
'get'() {
- warnDeprecation('`.get()` is deprecated, and has been renamed to `.unsafeGet()`.');
- return this.unsafeGet();
- }
Extracts the value of a Maybe structure, if it exists (i.e.: is a Just
),
-otherwise returns the provided default value.
forall a: (Maybe a).(a) => a
Extracts the value of a Maybe structure, if it exists (i.e.: is a Just
),
-otherwise returns the provided default value.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).getOrElse(2); // ==> 1
-Maybe.Nothing().getOrElse(2); // ==> 2
-
{
- /*~*/
- Nothing: function getOrElse(_default) {
- return _default;
- },
-
- /*~*/
- Just: function getOrElse(_default) {
- return this.value;
- }
- }
A textual representation of the Nothing variant.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation of the Nothing variant.
-() => variantName
Constructs a Maybe value that represents a successful value (a Just
).
Constructs a Maybe value that represents a successful value (a Just
).
--NOTE:
-
The provided value is stored as-given in the structure. If you want to -convert a nullable value (a value that may be null/undefined) to a Maybe -value, use theMaybe.fromNullable(value)
function instead of -Maybe.Just(value)
.
See the documentation for the Maybe structure to understand how to use this.
-Tests if an arbitrary value is a Just case of a Maybe instance.
-A textual representation of the Just variant.
-A textual representation of the Just variant.
-The constructor function for this variant.
-The container for instance methods for Just variants of the Maybe structure.
-The tag for this variant, unique among the Maybe variants.
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-Performs a deep-comparison of two Maybe values for equality. See core/adt/derivations/equality
for details.
Tests if an arbitrary value is a Maybe instance.
-True if the value is a Just
instance.
Constructs a Maybe value that represents a successful value (a Just
).
A convenience method for the folktale/data/conversions/maybe-to-result
module.
A convenience method for the folktale/data/conversions/maybe-to-validation
module.
The value contained in a Just instance of the Maybe structure.
-A textual representation for Maybe instances.
-A textual representation for Maybe instances.
-This method has been renamed to unsafeGet()
.
Extracts the value of a Maybe structure, if it exists (i.e.: is a Just
),
-otherwise returns the provided default value.
Extracts the value from a Just
structure.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Allows a function to provide functionality to variants in an ADT.
-This method has been replaced by matchWith(pattern)
. cata
(morphism) selects
-and executes a function for each variant of the Maybe structure.
Applies a function to each variant of the Maybe structure.
-Chooses and executes a function for each variant in the Maybe structure.
-Allows recovering from from failed Maybe values.
-Parses a JavaScript object previously serialised as aMaybe.toJSON()
into a proper Maybe structure.
Converts a Maybe value to a JavaScript object that may be stored as a JSON value.
-The constructor function for this variant.
-The variants in the Maybe structure.
-Transforms a Maybe value using a function contained in another Maybe. As with
-.map()
, the Maybe values are expected to be Just
, and no operation is
-performed if any of them is a Nothing
.
Transforms an entire Maybe structure with the provided function. As with
-.map()
, the transformation is only applied if the value is a Just
, but
-unlike .map()
the transformation is expected to return a new Maybe
value.
Documentation for both entities goes here~
-Constructs a Maybe value that represents a successful value (a Just
).
Constructs a Maybe value that represents a failure (a Nothing
).
Just(value) {
- return { value };
- }
Documentation for both entities goes here~
-forall a, b: (Maybe a).((a) => b) => Maybe b
Documentation for both entities goes here~
-
-[JavaScript examples use a special syntax](#writing-testable-examples) to allow them to be tested. Writing testable examples means that we can make sure that the examples in the documentation work with the latest version of the library.
-
-
-## Improving existing documentation
-
-In order to improve existing documentation, you need to find the file where it's defined inside `docs/source` and edit it. The documentation folder tries to follow the same layout the source folder has, and additionally each method/function gets its own documentation file, that matches the name of that method/function.
-
-A documentation file is consists generally of one or more annotation lines (`@annotate: ...`), followed by an [YAML](http://yaml.org/) document defining metadata, and then a portion of Markdown text that's the textual documentation for that entity. Examples inside should ideally [marked to be testable](#writing-testable-examples).
-
-
-## Writing testable examples
-
-Documentation examples should, ideally, be runnable. This way we ensure that
-all the examples contained in the documentation work with the code they're
-documenting.
-
-To mark an example as runnable, just write a regular Markdown codeblock and
-end the previous paragraph or heading with two colons (`::`). In the code block,
-you indicate the result of expressions using [arrow comments](https://github.com/origamitower/metamagical/tree/master/packages/babel-plugin-assertion-comments),
-which support structural (deep) equality.
-
-That is, you could use:
-
-```md
-This is a runnable example:
-
- 1 + 1;
- // ==> 2
-
Or:
-# This is a runnable example:
-
- [1].concat([2]);
- // ==> [1, 2]
-
-{
- /*~*/
- Nothing: function map(transformation) {
- assertFunction('Maybe.Nothing#map', transformation);
- return this;
- },
-
- /*~*/
- Just: function map(transformation) {
- assertFunction('Maybe.Just#map', transformation);
- return Just(transformation(this.value));
- }
- }
Constructs a Maybe value that represents a successful value (a Just
).
Constructs a Maybe value that represents a successful value (a Just
).
--NOTE:
-
The provided value is stored as-given in the structure. If you want to -convert a nullable value (a value that may be null/undefined) to a Maybe -value, use theMaybe.fromNullable(value)
function instead of -Maybe.of(value)
.
See the documentation for the Maybe structure to understand how to use this.
-of(value) {
- return Just(value);
- }
Allows recovering from from failed Maybe values.
-forall a: (Maybe a).((a) => Maybe a) => Maybe a
Allows recovering from from failed Maybe values.
-While .chain()
allows one to sequence operations, such that the second
-operation is ran only if the first one succeeds, and their state is propagated,
-.orElse()
allows one to recover from a failed operation by providing a new
-state.
const Maybe = require('folktale/data/maybe');
-
-function first(list) {
- return list.length > 0 ? Maybe.Just(list[0])
- : /* otherwise */ Maybe.Nothing();
-}
-
-let failures = 0;
-function emitFailure() {
- failures += 1;
- return Maybe.Just('failed');
-}
-
-first(['one']).orElse(emitFailure);
-// ==> Maybe.Just('one')
-
-failures; // ==> 0
-
-first([]).orElse(emitFailure);
-// ==> Maybe.Just('failed')
-
-failures; // ==> 1
-
{
- /*~*/
- Nothing: function orElse(handler) {
- assertFunction('Maybe.Nothing#orElse', handler);
- return handler(this.value);
- },
-
- /*~*/
- Just: function orElse(handler) {
- assertFunction('Maybe.Nothing#orElse', handler);
- return this;
- }
- }
A convenience method for the folktale/data/conversions/maybe-to-result
module.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A convenience method for the folktale/data/conversions/maybe-to-result
module.
const Maybe = require('folktale/data/maybe');
-const Result = require('folktale/data/result');
-
-Maybe.Just(1).toResult(0);
-// ==> Result.Ok(1)
-
-Maybe.Nothing().toResult(0)
-// ==> Result.Error(0)
-
toResult(fallbackValue) {
- return require('folktale/data/conversions/maybe-to-result')(this, fallbackValue);
- }
A convenience method for the folktale/data/conversions/maybe-to-validation
module.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A convenience method for the folktale/data/conversions/maybe-to-validation
module.
const Maybe = require('folktale/data/maybe');
-const Validation = require('folktale/data/validation');
-
-Maybe.Just(1).toValidation(0);
-// ==> Validation.Success(1)
-
-Maybe.Nothing().toValidation(0)
-// ==> Validation.Failure(0)
-
toValidation(fallbackValue) {
- return require('folktale/data/conversions/maybe-to-validation')(this, fallbackValue);
- }
Extracts the value from a Just
structure.
forall a: (Maybe a).() => a :: (throws TypeError)
Extracts the value from a Just
structure.
--WARNING
-
This method is partial, which means that it will only work for -Just
structures, not forNothing
structures. It's recommended -to use.getOrElse()
or.matchWith()
instead.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).unsafeGet(); // ==> 1
-
-try {
- Maybe.Nothing().unsafeGet();
- // TypeError: Can't extract the value of a Nothing
-} catch (e) {
- e instanceof TypeError; // ==> true
-}
-
{
- /*~*/
- Nothing: function unsafeGet() {
- throw new TypeError(`Can't extract the value of a Nothing.
-
- Since Nothing holds no values, it's not possible to extract one from them.
- You might consider switching from Maybe#get to Maybe#getOrElse, or some other method
- that is not partial.
- `);
- },
-
- /*~*/
- Just: function unsafeGet() {
- return this.value;
- }
- }
The value contained in a Just instance of the Maybe structure.
-The value contained in a Just instance of the Maybe structure.
-This is usually used to destructure the instance in a .matchWith
call.
const Maybe = require('folktale/data/maybe');
-
-Maybe.Just(1).matchWith({
- Just: ({ value }) => value, // equivalent to (x) => x.value
- Nothing: () => 'nothing'
-});
-// ==> 1
-
get value() {
- throw new TypeError('`value` can’t be accessed in an abstract instance of Maybe.Just');
- }
A convenience method for the folktale/data/conversions/maybe-to-result
module.
A convenience method for the folktale/data/conversions/maybe-to-result
module.
const Result = require('folktale/data/result');
-const Maybe = require('folktale/data/maybe');
-
-Result.fromMaybe(Maybe.Just(1), 'error');
-// ==> Result.Ok(1)
-
-Result.fromMaybe(Maybe.Nothing(), 'error');
-// ==> Result.Error('error')
-
fromMaybe(aMaybe, failureValue) {
- return require('folktale/data/conversions/maybe-to-result')(aMaybe, failureValue);
- }
A convenience method for the folktale/data/conversions/nullable-to-result
-module.
A convenience method for the folktale/data/conversions/nullable-to-result
-module.
const Result = require('folktale/data/result');
-
-Result.fromNullable(1);
-// ==> Result.Ok(1)
-
-Result.fromNullable(null);
-// ==> Result.Error(null)
-
-Result.fromNullable(undefined);
-// ==> Result.Error(undefined)
-
fromNullable(aNullable) {
- return require('folktale/data/conversions/nullable-to-result')(aNullable);
- }
A convenience method for the folktale/data/conversions/validation-to-result
-module.
A convenience method for the folktale/data/conversions/validation-to-result
-module.
const Result = require('folktale/data/result');
-const Validation = require('folktale/data/validation');
-
-Result.fromValidation(Validation.Success(1));
-// ==> Result.Ok(1)
-
-Result.fromValidation(Validation.Failure(1));
-// ==> Result.Error(1)
-
fromValidation(aValidation) {
- return require('folktale/data/conversions/validation-to-result')(aValidation);
- }
A data structure that models the result of operations that may fail. A Result
-helps with representing errors and propagating them, giving users a more
-controllable form of sequencing operations than that offered by constructs like
-try/catch
.
A data structure that models the result of operations that may fail. A Result
-helps with representing errors and propagating them, giving users a more
-controllable form of sequencing operations than that offered by constructs like
-try/catch
.
A Result
may be either an Ok(value)
, which contains a successful value, or
-an Error(value)
, which contains an error.
const Result = require('folktale/data/result');
-const { data, derivations } = require('folktale/core/adt');
-
-const DivisionErrors = data('division-errors', {
- DivisionByZero(dividend) {
- return { dividend };
- }
-}).derive(
- derivations.equality,
- derivations.debugRepresentation
-);
-
-const { DivisionByZero } = DivisionErrors;
-
-
-const divideBy = (dividend, divisor) =>
- divisor === 0 ? Result.Error(DivisionByZero(dividend))
-: /* otherwise */ Result.Ok(Math.floor(dividend / divisor));
-
-divideBy(4, 2);
-// ==> Result.Ok(2)
-
-divideBy(4, 0);
-// ==> Result.Error(DivisionByZero(4))
-
Sometimes functions fail, for many reasons: someone might have provided an -unexpected value to it, the internet connection might have gone down in the -middle of an HTTP request, the database might have died. Regardless of which -reason, we have to handle these failures. And, of course, we'd like to handle -failures in the simplest way possible.
-In JavaScript you're often left with two major ways of dealing with these
-failures: a branching instruction (like if/else
), or throwing errors and
-catching them.
To see how Result
compares to these, we'll look at a function that needs to
-validate some information, and that incorporates some more complex validation
-rules. A person may sign-up for a service by providing the form they would
-prefer being contacted (email or phone), and the information related to that
-preference has to be provided, but any other info is optional:
// Functions to assert the format of each data
-const isValidName = (name) => name.trim() !== '';
-const isValidEmail = (email) => /(.+)@(.+)/.test(email);
-const isValidPhone = (phone) => /^\d+$/.test(phone);
-
-// Objects representing each possible failure in the validation
-const { data, derivations } = require('folktale/core/adt');
-
-const ValidationErrors = data('validation-errors', {
- Required(field) {
- return { field };
- },
-
- InvalidEmail(email) {
- return { email };
- },
-
- InvalidPhone(phone) {
- return { phone };
- },
-
- InvalidType(type) {
- return { type };
- },
-
- Optional(error) {
- return { error };
- }
-}).derive(derivations.equality);
-
-const {
- Required,
- InvalidEmail,
- InvalidPhone,
- InvalidType,
- Optional
-} = ValidationErrors;
-
Branching stops being a very feasible thing after a couple of cases. It's very -simple to forget to handle failures (often with catastrophic effects, as can be -seen in things like NullPointerException and the likes), and there's no error -propagation, so every part of the code has to handle the same error over and -over again:
-const validateBranching = ({ name, type, email, phone }) => {
- if (!isValidName(name)) {
- return Required('name');
- } else if (type === 'email') {
- if (!isValidEmail(email)) {
- return InvalidEmail(email);
- } else if (phone && !isValidPhone(phone)) {
- return Optional(InvalidPhone(phone));
- } else {
- return { type, name, email, phone };
- }
- } else if (type === 'phone') {
- if (!isValidPhone(phone)) {
- return InvalidPhone(phone);
- } else if (email && !isValidEmail(email)) {
- return Optional(InvalidEmail(email));
- } else {
- return { type, name, email, phone };
- }
- } else {
- return InvalidType(type);
- }
-};
-
-
-validateBranching({
- name: 'Max',
- type: 'email',
- phone: '11234456'
-});
-// ==> InvalidEmail(undefined)
-
-validateBranching({
- name: 'Alissa',
- type: 'email',
- email: 'alissa@somedomain'
-});
-// ==> { type: 'email', name: 'Alissa', email: 'alissa@somedomain', phone: undefined }
-
Exceptions (with the throw
and try/catch
constructs) alleviate this a bit.
-They don't solve the cases where you forget to handle a failure—although that
-often results in crashing the process, which is better than continuing but doing
-the wrong thing—, but they allow failures to propagate, so fewer places in the
-code need to really deal with the problem:
const id = (a) => a;
-
-const assertEmail = (email, wrapper=id) => {
- if (!isValidEmail(email)) {
- throw wrapper(InvalidEmail(email));
- }
-};
-
-const assertPhone = (phone, wrapper=id) => {
- if (!isValidPhone(phone)) {
- throw wrapper(InvalidEmail(email));
- }
-};
-
-const validateThrow = ({ name, type, email, phone }) => {
- if (!isValidName(name)) {
- throw Required('name');
- }
- switch (type) {
- case 'email':
- assertEmail(email);
- if (phone) assertPhone(phone, Optional);
- return { type, name, email, phone };
-
- case 'phone':
- assertPhone(phone);
- if (email) assertEmail(email, Optional);
- return { type, name, email, phone };
-
- default:
- throw InvalidType(type);
- }
-};
-
-
-try {
- validateThrow({
- name: 'Max',
- type: 'email',
- phone: '11234456'
- });
-} catch (e) {
- e; // ==> InvalidEmail(undefined)
-}
-
-validateThrow({
- name: 'Alissa',
- type: 'email',
- email: 'alissa@somedomain'
-});
-// ==> { type: 'email', name: 'Alissa', email: 'alissa@somedomain', phone: undefined }
-
On the other hand, the error propagation that we have with throw
doesn't tell
-us much about how much of the code has actually been executed, and this is
-particularly problematic when you have side-effects. How are you supposed to
-recover from a failure when you don't know in which state your application is?
Result
helps with both of these cases. With a Result
, the user is forced to
-be aware of the failure, since they're not able to use the value at all without
-unwrapping the value first. At the same time, using a Result
value will
-automatically propagate the errors when they're not handled, making error
-handling easier. Since Result
runs one operation at a time when you use the
-value, and does not do any dynamic stack unwinding (as throw
does), it's much
-easier to understand in which state your application should be.
Using Result
, the previous examples would look like this:
const Result = require('folktale/data/result');
-
-const checkName = (name) =>
- isValidName(name) ? Result.Ok(name)
-: /* otherwise */ Result.Error(Required('name'));
-
-const checkEmail = (email) =>
- isValidEmail(email) ? Result.Ok(email)
-: /* otherwise */ Result.Error(InvalidEmail(email));
-
-const checkPhone = (phone) =>
- isValidPhone(phone) ? Result.Ok(phone)
-: /* otherwise */ Result.Error(InvalidPhone(phone));
-
-const optional = (check) => (value) =>
- value ? check(value).mapError(Optional)
-: /* otherwise */ Result.Ok(value);
-
-const maybeCheckEmail = optional(checkEmail);
-const maybeCheckPhone = optional(checkPhone);
-
-
-const validateResult = ({ name, type, email, phone }) =>
- checkName(name).chain(_ =>
- type === 'email' ? checkEmail(email).chain(_ =>
- maybeCheckPhone(phone).map(_ => ({
- name, type, email, phone
- }))
- )
-
- : type === 'phone' ? checkPhone(phone).chain(_ =>
- maybeCheckEmail(email).map(_ => ({
- name, type, email, phone
- }))
- )
-
- : /* otherwise */ Result.Error(InvalidType(type))
- );
-
-
-validateResult({
- name: 'Max',
- type: 'email',
- phone: '11234456'
-});
-// ==> Result.Error(InvalidEmail(undefined))
-
-validateResult({
- name: 'Alissa',
- type: 'email',
- email: 'alissa@somedomain'
-});
-// ==> Result.Ok({ name: 'Alissa', type: 'email', phone: undefined, email: 'alissa@somedomain' })
-
So, Results give you simpler and more predictable forms of error handling, that -still allow error propagation and composition to happen, as with regular -JavaScript exceptions, at the cost of some additional syntactical overhead, -since JavaScript does not allow one to abstract over syntax.
-A Result value may be one of the following cases:
-Ok(value)
— represents a successful value (e.g.: the correct return value
-from a function).
Error(value)
— represents an unsuccessful value (e.g.: an error during the
-evaluation of a function).
Functions that may fail just return one of these two cases instead of failing. -That is, instead of writing something like this:
-//: (Number, Number) => Number throws DivisionByZero
-const divideBy1 = (dividend, divisor) => {
- if (divisor === 0) {
- throw new Error('Division by zero');
- } else {
- return dividend / divisor;
- }
-}
-
-divideBy1(6, 3); // ==> 2
-
One would write something like this:
-const Result = require('folktale/data/result');
-
-//: (Number, Number) => Result DivisionByZero Number
-const divideBy2 = (dividend, divisor) => {
- if (divisor === 0) {
- return Result.Error('Division by zero');
- } else {
- return Result.Ok(dividend / divisor);
- }
-}
-
-divideBy2(6, 3); // ==> Result.Ok(2)
-
The function returns a value of the type Result <error-type> <success-type>
,
-which is quite similar to the first version of the function, with the difference
-that the error is made into a real value that the function returns, and as such
-can be interacted with in the same way one interacts with any other object in
-the language.
Because the value is wrapped in the Result
structure, in order to use the
-value one must first unwrap it. Folktale's Result
provides methods to help
-with this, and they can be divided roughly into 3 categories:
Sequencing computations: A Result
is the result of a computation that
-can fail. Sometimes we want to run several computations that may fail in
-sequence, and these methods help with that. This is roughly the equivalent
-of ;
in imperative programming, where the next instruction is only
-executed if the previous instruction succeeds.
Transforming values: Sometimes we get a Result
value that isn't
-quite the value we're looking for. We don't really want to change the
-status of the computation (errors should continue to be errors, successes
-should continue to be successes), but we'd like to tweak the resulting
-value a bit. This is the equivalent of applying functions in an
-expression.
Extracting values: Sometimes we need to pass the value into things that
-don't really know what a Result
is, so we have to somehow extract the
-value out of the structure. These methods help with that.
We'll see each of these categories in more details below.
-Because Result
is used for partial functions which may fail in, possibly, many
-different ways a common use case for the structure is combining all of these
-functions such that only successful values flow through.
You can sequence computations in this manner with the Result
structure by
-using the .chain
method. The method takes as argument a single function that
-will receive the value in the Result
structure, and must return a new Result
-structure, which then becomes the result of the method. Only successful values
-flow through the function argument, errors are just propagated to the result
-immediately.
As an example, imagine we want to parse an integer. Integers are basically a -sequence of many digits, but we can start smaller and try figuring out how we -parse a single digit:
-const Result = require('folktale/data/result');
-
-const isDigit = (character) =>
- '0123456789'.split('').includes(character);
-
-const digit = (input) => {
- const character = input.slice(0, 1);
- const rest = input.slice(1);
-
- return isDigit(character) ? Result.Ok([character, rest])
- : /* otherwise */ Result.Error(`Expected a digit (0..9), got "${character}"`);
-};
-
-digit('012');
-// ==> Result.Ok(['0', '12'])
-
-digit('a12');
-// ==> Result.Error('Expected a digit (0..9), got "a"')
-
So far our parser correctly recognises the first digit in a sequence of -characters, but to parse integers we need it to recognise more than one digit. -We can also only proceed to the next character if the first one succeeds -(otherwise we already know it's not an integer!).
-If we have a fixed number of digits that would look like the following:
-const twoDigits = (input) =>
- digit(input).chain(([char1, rest]) =>
- digit(rest).map(([char2, rest]) =>
- [char1 + char2, rest]
- )
- );
-
-twoDigits('012');
-// ==> Result.Ok(['01', '2'])
-
-twoDigits('a12');
-// ==> Result.Error('Expected a digit (0..9), got "a"')
-
-twoDigits('0a2');
-// ==> Result.Error('Expected a digit (0..9), got "a"')
-
We can generalise this to arbitrary numbers of digits by writing a recursive -function:
-const digits = (input) =>
- input === '' ? Result.Error('Expected a digit (0..9), but there was nothing to parse')
-: /* otherwise */ digit(input).chain(([character, rest]) =>
- rest === '' ? Result.Ok(character)
- : /* else */ digits(rest).chain(characters =>
- Result.Ok(character + characters)
- )
- );
-
-digits('012');
-// ==> Result.Ok('012')
-
-digits('a12');
-// ==> Result.Error('Expected a digit (0..9), got "a"')
-
-digits('01a');
-// ==> Result.Error('Expected a digit (0..9), got "a"')
-
-digits('');
-// ==> Result.Error('Expected a digit (0..9), but there was nothing to parse')
-
--NOTE
-
While the recursive.chain
can be used when performing an arbitrary number -of computations, it should be noted that it may grow the stack of calls in a -JavaScript implementation beyond what that implementation supports, resulting -in aMax Call Stack Size Exceeded
error.
Finally, parsing should give us a number instead of a string, so that string
-needs to be converted. Since we already ensured that the resulting string only
-has digits, the conversion from String to Number can never fail, but we can
-still use .chain
if we always return a Result.Ok
:
const integer = (input) =>
- digits(input).chain(x => Result.Ok(Number(x)));
-
-integer('012');
-// ==> Result.Ok(12)
-
-integer('01a')
-// ==> Result.Error('Expected a digit (0..9), got "a"')
-
Sometimes we want to transform just the value that is inside of a Result
,
-without touching the context of that value (whether the Result
is a success or
-an error). In the previous section, the integer
transformation is a good
-example of this. For these cases, the .map
method can be used instead of the
-.chain
method:
const Result = require('folktale/data/result');
-
-Result.Ok('012').map(Number);
-// ==> Result.Ok(12)
-
Note that, like .chain
, the .map
method only runs on successful values, thus
-it propagates failures as well:
Result.Error('012').map(Number);
-// ==> Result.Error('012')
-
While one can always just put all the rest of a computation inside of a
-.chain
, sometimes it may be preferrable to extract the value out of a Result
-structure instead. For these cases there's a .getOrElse
method:
const Result = require('folktale/data/result');
-
-Result.Ok(1).getOrElse('not found');
-// ==> 1
-
-Result.Error(1).getOrElse('not found');
-// ==> 'not found'
-
Additionally, if one doesn't care about whether the value failed or succeeded,
-the .merge
method just returns whatever value is wrapped in the Result
:
Result.Ok(1).merge();
-// ==> 1
-
-Result.Error(1).merge();
-// ==> 1
-
Since the purpose of a Result
structure is to represent failures, we need to
-be able to handle these failures in some way. The .getOrElse
method gives us
-some very specific and limited form of error handling, but if we want to do
-something in order to recover from an error, this doesn't help us much.
For a more general form of error handling, the Result
structure provides the
-.orElse
method. This works pretty much in the same way .chain
does, except
-it invokes the function on errors (successes are propagated):
const Result = require('folktale/data/result');
-
-const parseNumber = (input) =>
- isNaN(input) ? Result.Error(`Not a number: ${input}`)
-: /* otherwise */ Result.Ok(Number(input));
-
-const parseBool = (input) =>
- input === 'true' ? Result.Ok(true)
-: input === 'false' ? Result.Ok(false)
-: /* otherwise */ Result.Error(`Not a boolean: ${input}`);
-
-
-const parseNumberOrBool = (input) =>
- parseNumber(input)
- .orElse(_ => parseBool(input));
-
-
-parseNumberOrBool('13');
-// ==> Result.Ok(13)
-
-parseNumberOrBool('true');
-// ==> Result.Ok(true)
-
-parseNumberOrBool('foo');
-// ==> Result.Error('Not a boolean: foo')
-
As with successes' .map
, one may also transform the failure value of a
-Result
without changing the context of the computation by using the
-.mapError
method:
const parseNumberOrBool2 = (input) =>
- parseNumber(input)
- .orElse(_ => parseBool(input))
- .mapError(_ => `Not a number or boolean: ${input}`);
-
-parseNumberOrBool2('foo');
-// ==> Result.Error('Not a number or boolean: foo')
-
As with other union structures in Folktale, Result provides a .matchWith()
-method to perform a limited form of pattern matching. Pattern matching allows
-one to specify a piece of code for each case in a structure, like an if/else
-or switch
, but specific to that structure.
We could use .matchWith()
to run different computations depending on whether a
-MResult value represents a success or a failure, for example, without the
-requirement of having to return a Result:
const Result = require('folktale/data/result');
-
-Result.Ok(1).matchWith({
- Ok: ({ value }) => `Ok: ${value}`,
- Error: ({ value }) => `Error: ${value}`
-});
-// ==> 'Ok: 1'
-
-Result.Error(1).matchWith({
- Ok: ({ value }) => `Ok: ${value}`,
- Error: ({ value }) => `Error: ${value}`
-});
-// ==> 'Error: 1'
-
Result and Validation are pretty close structures. They both try to represent
-whether a particular thing has failed or succeeded, and even their vocabulary is
-very similar (Error
vs. Failure
, Ok
vs. Success
). The major difference
-is in some of their methods.
A Result is a data structure that implements the Monad interface (.chain
).
-This makes Result a pretty good structure to model a sequence of computations
-that may fail, where a computation may only run if the previous computation
-succeeded. In this sense, a Result's .chain
method is very similar to
-JavaScript's ;
at the end of statements: the statement at the right of the
-semicolon only runs if the statement at the left did not throw an error.
A Validation is a data structure that implements the Applicative interface
-(.apply
), and does so in a way that if a failure is applied to another
-failure, then it results in a new validation that contains the failures of both
-validations. In other words, Validation is a data structure made for errors that
-can be aggregated, and it makes sense in the contexts of things like form
-validations, where you want to display to the user all of the fields that failed
-the validation rather than just stopping at the first failure.
Validations can't be as easily used for sequencing operations because the
-.apply
method takes two validations, so the operations that create them must
-have been executed already. While it is possible to use Validations in a
-sequential manner, it's better to leave the job to Result, a data structure made
-for that.
Tests if an arbitrary value is a Result instance.
-Constructs a Result holding an Ok value.
-A convenience method for the folktale/data/conversions/maybe-to-result
module.
A convenience method for the folktale/data/conversions/nullable-to-result
-module.
A convenience method for the folktale/data/conversions/validation-to-result
-module.
Runs a function that may raise an exception, trapping it. Returns an Ok
with
-the return value of the function, if it has finished successfully, or an Error
-with the raised exception.
Parses a JavaScript object previously serialised as aResult.toJSON()
into a proper Result structure.
Constructs a Result whose value represents a failure.
-Constructs a Result whose value represents a success.
-{
- Error: Result.Error,
- Ok: Result.Ok,
- hasInstance: Result.hasInstance,
- of: Result.of,
- fromJSON: Result.fromJSON,
- [typeSymbol]: Result[typeSymbol],
- try: require('./try'),
-
- /*~
- * type: |
- * forall a: (a or None) => Result None a
- */
- fromNullable(aNullable) {
- return require('folktale/data/conversions/nullable-to-result')(aNullable);
- },
-
- /*~
- * type: |
- * forall a, b: (Validation a b) => Result a b
- */
- fromValidation(aValidation) {
- return require('folktale/data/conversions/validation-to-result')(aValidation);
- },
-
- /*~
- * type: |
- * forall a, b: (Maybe b, a) => Result a b
- */
- fromMaybe(aMaybe, failureValue) {
- return require('folktale/data/conversions/maybe-to-result')(aMaybe, failureValue);
- }
-}
Constructs a Result whose value represents a success.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Result whose value represents a success.
-See the documentation for the Result structure to understand how to use this.
-Tests if an arbitrary value is a Ok case of a Result instance.
-A textual representation of the Ok variant.
-A textual representation of the Ok variant.
-The constructor for this variant.
-The container for instance methods for Ok variants of the Result structure.
-The tag for this variant, unique among the Result variants.
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-Performs a deep-comparison of two Result values for equality. See core/adt/derivations/equality
for details.
Tests if an arbitrary value is a Result instance.
-True if the value is a Ok
instance.
Constructs a Result holding an Ok value.
-Transforms a Result into a Maybe. Error values are lost in the process.
-Transforms a Result into a Validation.
-The value contained in an Ok instance of the Result structure.
-A textual representation for Result instances.
-A textual representation for Result instances.
-This method has been renamed to unsafeGet()
.
Extracts the value of a Result
structure, if it exists (i.e.: is an Ok
),
-otherwise returns the provided default value.
Returns the value inside of the Result structure, regardless of its state.
-Extracts the value from a Result
structure.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Allows a function to provide functionality to variants in an ADT.
-Applies a function to each case of a Result.
-Chooses and executes a function for each variant in the Result structure.
-Parses a JavaScript object previously serialised as aResult.toJSON()
into a proper Result structure.
Converts a Result value to a JavaScript object that may be stored as a JSON value.
-The constructor for this variant.
-The variants in the Result structure.
-Applies the function contained in one Result to the value in another Result.
-Application only occurs if both Results are Ok
, otherwise keeps the first
-Error
.
Transforms each side of a Result with a function, without changing the context -of the computation. That is, Errors will still be Errors, Oks will still be -Oks.
-Transforms the value and context of a Result computation with an unary function.
-As with .map()
, the transformation is only applied if the value is an Ok
,
-but the transformation is expected a new Result
value, which then becomes the
-result of the method.
Transforms the value inside of a Result structure with an unary function without
-changing the context of the computation. That is, Error
values continue to be
-Error
values, and Ok
values continue to be Ok
values.
Transforms the value inside an Error without changing the context of the -computation.
-Constructs a Result whose value represents a failure.
-Constructs a Result whose value represents a success.
-Ok(value) {
- return { value };
- }
Applies the function contained in one Result to the value in another Result.
-Application only occurs if both Results are Ok
, otherwise keeps the first
-Error
.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b, c:
- (Result a ((b) => c)).(Result a b) => Result a c
Applies the function contained in one Result to the value in another Result.
-Application only occurs if both Results are Ok
, otherwise keeps the first
-Error
.
Note that Result.Ok(x => x + 1).apply(Result.Ok(1))
is equivalent to
-Result.Ok(1).map(x => x + 1)
.
const Result = require('folktale/data/result');
-
-function increment(value) {
- return value + 1;
-}
-
-Result.Ok(increment).apply(Result.Ok(1));
-// ==> Result.Ok(2)
-
-Result.Ok(increment).apply(Result.Error(1));
-// ==> Result.Error(1)
-
-Result.Error(increment).apply(Result.Ok(1));
-// ==> Result.Error(increment)
-
-Result.Error(increment).apply(Result.Error(1));
-// ==> Result.Error(increment)
-
{
- /*~*/
- Error: function apply(anResult) {
- assertResult('Result.Error#apply', anResult);
- return this;
- },
-
- /*~*/
- Ok: function apply(anResult) {
- assertResult('Result.Ok#apply', anResult);
- return anResult.map(this.value);
- }
- }
Transforms each side of a Result with a function, without changing the context -of the computation. That is, Errors will still be Errors, Oks will still be -Oks.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
(Result a b).((a) => c, (b) => d) => Result c d
Transforms each side of a Result with a function, without changing the context -of the computation. That is, Errors will still be Errors, Oks will still be -Oks.
-const Result = require('folktale/data/result');
-
-const inc = (x) => x + 1;
-const dec = (x) => x - 1;
-
-
-Result.Ok(1).bimap(inc, dec);
-// ==> Result.Ok(dec(1))
-
-Result.Error(1).bimap(inc, dec);
-// ==> Result.Error(inc(1))
-
{
- /*~*/
- Error: function bimap(f, g) {
- assertFunction('Result.Error#bimap', f);
- assertFunction('Result.Error#bimap', g);
- return Error(f(this.value));
- },
-
- /*~*/
- Ok: function bimap(f, g) {
- assertFunction('Result.Ok#bimap', f);
- assertFunction('Result.Ok#bimap', g);
- return Ok(g(this.value));
- }
- }
Transforms the value and context of a Result computation with an unary function.
-As with .map()
, the transformation is only applied if the value is an Ok
,
-but the transformation is expected a new Result
value, which then becomes the
-result of the method.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b, c:
- (Result a b).((b) => Result a c) => Result a c
Transforms the value and context of a Result computation with an unary function.
-As with .map()
, the transformation is only applied if the value is an Ok
,
-but the transformation is expected a new Result
value, which then becomes the
-result of the method.
const Result = require('folktale/data/result');
-
-const divideBy = (a) => (b) =>
- a === 0 ? Result.Error('division by zero')
-: /* otherwise */ Result.Ok(b / a);
-
-
-Result.Ok(4).chain(divideBy(2));
-// ==> Result.Ok(2)
-
-Result.Error(4).chain(divideBy(2));
-// ==> Result.Error(4)
-
-Result.Ok(4).chain(divideBy(0));
-// ==> Result.Error('division by zero')
-
{
- /*~*/
- Error: function chain(f) {
- assertFunction('Result.Error#chain', f);
- return this;
- },
-
- /*~*/
- Ok: function chain(f) {
- assertFunction('Result.Ok#chain', f);
- return f(this.value);
- }
- }
Constructs a Result whose value represents a failure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Result whose value represents a failure.
-See the documentation for the Result structure to understand how to use this.
-Tests if an arbitrary value is a Error case of a Result instance.
-A textual representation of the Error variant.
-A textual representation of the Error variant.
-The constructor for this variant.
-The container for instance methods for Error variants of the Result structure.
-The tag for this variant, unique among the Result variants.
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-Performs a deep-comparison of two Result values for equality. See core/adt/derivations/equality
for details.
Tests if an arbitrary value is a Result instance.
-True if the value is a Error
instance.
Constructs a Result holding an Ok value.
-Transforms a Result into a Maybe. Error values are lost in the process.
-Transforms a Result into a Validation.
-The value contained in an Error instance of the Result structure.
-A textual representation for Result instances.
-A textual representation for Result instances.
-This method has been renamed to unsafeGet()
.
Extracts the value of a Result
structure, if it exists (i.e.: is an Ok
),
-otherwise returns the provided default value.
Returns the value inside of the Result structure, regardless of its state.
-Extracts the value from a Result
structure.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Allows a function to provide functionality to variants in an ADT.
-Applies a function to each case of a Result.
-Chooses and executes a function for each variant in the Result structure.
-Parses a JavaScript object previously serialised as aResult.toJSON()
into a proper Result structure.
Converts a Result value to a JavaScript object that may be stored as a JSON value.
-The constructor for this variant.
-The variants in the Result structure.
-Applies the function contained in one Result to the value in another Result.
-Application only occurs if both Results are Ok
, otherwise keeps the first
-Error
.
Transforms each side of a Result with a function, without changing the context -of the computation. That is, Errors will still be Errors, Oks will still be -Oks.
-Transforms the value and context of a Result computation with an unary function.
-As with .map()
, the transformation is only applied if the value is an Ok
,
-but the transformation is expected a new Result
value, which then becomes the
-result of the method.
Transforms the value inside of a Result structure with an unary function without
-changing the context of the computation. That is, Error
values continue to be
-Error
values, and Ok
values continue to be Ok
values.
Transforms the value inside an Error without changing the context of the -computation.
-Constructs a Result whose value represents a failure.
-Constructs a Result whose value represents a success.
-Error(value) {
- return { value };
- }
Applies a function to each case of a Result.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b, c:
- (Result a b).((a) => c, (b) => c) => c
Applies a function to each case of a Result.
-const Result = require('folktale/data/result');
-
-const inc = (x) => x + 1;
-const dec = (x) => x - 1;
-
-Result.Error(1).fold(inc, dec);
-// ==> inc(1)
-
-Result.Ok(1).fold(inc, dec);
-// ==> dec(1)
-
{
- /*~*/
- Error: function fold(f, g) {
- assertFunction('Result.Error#fold', f);
- assertFunction('Result.Error#fold', g);
- return f(this.value);
- },
-
- /*~*/
- Ok: function fold(f, g) {
- assertFunction('Result.Ok#fold', f);
- assertFunction('Result.Ok#fold', g);
- return g(this.value);
- }
- }
This method has been renamed to unsafeGet()
.
We want to discourage the use of partial functions, and having short names -makes it easy for people to want to use them without thinking about the -problems.
-For more details see https://github.com/origamitower/folktale/issues/42
-This method has been renamed to unsafeGet()
.
'get'() {
- warnDeprecation('`.get()` is deprecated, and has been renamed to `.unsafeGet()`.');
- return this.unsafeGet();
- }
Extracts the value of a Result
structure, if it exists (i.e.: is an Ok
),
-otherwise returns the provided default value.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b: (Result a b).(b) => b
Extracts the value of a Result
structure, if it exists (i.e.: is an Ok
),
-otherwise returns the provided default value.
const Result = require('folktale/data/result');
-
-Result.Ok(1).getOrElse(2);
-// ==> 1
-
-Result.Error(1).getOrElse(2);
-// ==> 2
-
{
- /*~*/
- Error: function getOrElse(_default) {
- return _default;
- },
-
- /*~*/
- Ok: function getOrElse(_default) {
- return this.value;
- }
- }
A textual representation of the Ok variant.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation of the Ok variant.
-() => variantName
Transforms the value inside of a Result structure with an unary function without
-changing the context of the computation. That is, Error
values continue to be
-Error
values, and Ok
values continue to be Ok
values.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b, c:
- (Result a b).((b) => c) => Result a c
Transforms the value inside of a Result structure with an unary function without
-changing the context of the computation. That is, Error
values continue to be
-Error
values, and Ok
values continue to be Ok
values.
const Result = require('folktale/data/result');
-
-function increment(value) {
- return value + 1;
-}
-
-Result.Ok(1).map(increment);
-// ==> Result.Ok(2)
-
-Result.Error(1).map(increment);
-// ==> Result.Error(1)
-
{
- /*~*/
- Error: function map(f) {
- assertFunction('Result.Error#map', f);
- return this;
- },
-
- /*~*/
- Ok: function map(f) {
- assertFunction('Result.Ok#map', f);
- return Ok(f(this.value));
- }
- }
Transforms the value inside an Error without changing the context of the -computation.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b, c:
- (Result a b).((a) => c) => Result c b
Transforms the value inside an Error without changing the context of the -computation.
-This is similar to .map
, except it operates on Errors instead of Oks.
const Result = require('folktale/data/result');
-
-Result.Error(1).mapError(x => x + 1);
-// ==> Result.Error(2)
-
-Result.Ok(1).mapError(x => x + 1);
-// ==> Result.Ok(1)
-
{
- /*~*/
- Error: function mapError(f) {
- assertFunction('Result.Error#mapError', f);
- return Error(f(this.value));
- },
-
- /*~*/
- Ok: function mapError(f) {
- assertFunction('Result.Ok#mapError', f);
- return this;
- }
- }
Returns the value inside of the Result structure, regardless of its state.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Returns the value inside of the Result structure, regardless of its state.
-const Result = require('folktale/data/result');
-
-Result.Ok(1).merge();
-// ==> 1
-
-Result.Error(1).merge();
-// ==> 1
-
merge() {
- return this.value;
- }
Constructs a Result holding an Ok value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Result holding an Ok value.
-const Result = require('folktale/data/result');
-
-Result.of(1);
-// ==> Result.Ok(1)
-
of(value) {
- return Ok(value);
- }
Allows recovering from Error
values with a handler function.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b, c:
- (Result a b).((a) => Result c b) => Result c b
Allows recovering from Error
values with a handler function.
While .chain()
allows one to sequence operations, such that the second
-operation is ran only if the first one succeeds, the .orElse()
method allows
-one to recover from an Error
by running a function that provides a new Result
-value.
const Result = require('folktale/data/result');
-
-Result.Ok(4).orElse(error => Result.Ok(error + 1));
-// ==> Result.Ok(4)
-
-Result.Error(4).orElse(error => Result.Ok(error + 1));
-// ==> Result.Ok(5)
-
-Result.Error(4).orElse(error => Result.Error(error - 1));
-// ==> Result.Error(3)
-
{
- /*~*/
- Error: function orElse(handler) {
- assertFunction('Result.Error#orElse', handler);
- return handler(this.value);
- },
-
- /*~*/
- Ok: function orElse(handler) {
- assertFunction('Result.Ok#orElse', handler);
- return this;
- }
- }
Inverts the context of a Result value such that Errors are transformed into Oks, -and Oks are transformed into Errors. Does not touch the value inside of the -Result.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b: (Result a b).() => Result b a
Inverts the context of a Result value such that Errors are transformed into Oks, -and Oks are transformed into Errors. Does not touch the value inside of the -Result.
-const Result = require('folktale/data/result');
-
-Result.Ok(1).swap();
-// ==> Result.Error(1)
-
-Result.Error(1).swap();
-// ==> Result.Ok(1)
-
{
- /*~*/
- Error: function swap() {
- return Ok(this.value);
- },
-
- /*~*/
- Ok: function swap() {
- return Error(this.value);
- }
- }
Transforms a Result into a Maybe. Error values are lost in the process.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Transforms a Result into a Maybe. Error values are lost in the process.
-const Result = require('folktale/data/result');
-const Maybe = require('folktale/data/maybe');
-
-Result.Ok(1).toMaybe();
-// ==> Maybe.Just(1)
-
-Result.Error(1).toMaybe();
-// ==> Maybe.Nothing()
-
toMaybe() {
- return require('folktale/data/conversions/result-to-maybe')(this);
- }
Transforms a Result into a Validation.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Transforms a Result into a Validation.
-Result's Ok
s are mapped into Validation's Success
es, and Result's Error
s
-are mapped into Validation's Failure
s.
const Result = require('folktale/data/result');
-const Validation = require('folktale/data/validation');
-
-Result.Ok(1).toValidation();
-// ==> Validation.Success(1)
-
-Result.Error(1).toValidation();
-// ==> Validation.Failure(1)
-
toValidation() {
- return require('folktale/data/conversions/result-to-validation')(this);
- }
Extracts the value from a Result
structure.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b: (Result a b).() => b :: throws TypeError
Extracts the value from a Result
structure.
--WARNING
-
This method is partial, which means that it will only work forOk
-structures, not forError
structures. It's recommended to use.getOrElse()
-or.matchWith()
instead.
const Result = require('folktale/data/result');
-
-Result.Ok(1).unsafeGet();
-// ==> 1
-
-
-try {
- Result.Error(1).unsafeGet();
- // TypeError: Can't extract the value of an Error
-} catch (e) {
- e instanceof TypeError; // ==> true
-}
-
{
- /*~*/
- Error: function unsafeGet() {
- throw new TypeError(`Can't extract the value of an Error.
-
-Error does not contain a normal value - it contains an error.
-You might consider switching from Result#unsafeGet to Result#getOrElse,
-or some other method that is not partial.
- `);
- },
-
- /*~*/
- Ok: function unsafeGet() {
- return this.value;
- }
- }
The value contained in an Ok instance of the Result structure.
-The value contained in an Ok instance of the Result structure.
-This is usually used to destructure the instance in a .matchWith
call.
const Result = require('folktale/data/result');
-
-Result.Ok(1).matchWith({
- Error: ({ value }) => 'nothing',
- Ok: ({ value }) => value // equivalent to (x) => x.value
-});
-// ==> 1
-
get value() {
- throw new TypeError('`value` can’t be accessed in an abstract instance of Result.Ok');
- }
Runs a function that may raise an exception, trapping it. Returns an Ok
with
-the return value of the function, if it has finished successfully, or an Error
-with the raised exception.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Runs a function that may raise an exception, trapping it. Returns an Ok
with
-the return value of the function, if it has finished successfully, or an Error
-with the raised exception.
function successor(natural) {
- if (natural < 0) {
- throw `Not a natural number: ${natural}`;
- } else {
- return natural + 1;
- }
-}
-
-const Result = require('folktale/data/result');
-
-Result.try(() => successor(-1));
-// ==> Result.Error('Not a natural number: -1')
-
-Result.try(() => successor(1));
-// ==> Result.Ok(2)
-
(f) => {
- try {
- return Ok(f());
- } catch (e) {
- return Error(e);
- }
-}
Represents the execution of a Task, with methods to cancel it, react to its
-results, and retrieve its eventual value. TaskExecution objects aren't created
-directly by users, but instead returned as a result from Task's run()
method.
-b
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Represents the execution of a Task, with methods to cancel it, react to its
-results, and retrieve its eventual value. TaskExecution objects aren't created
-directly by users, but instead returned as a result from Task's run()
method.
-b
The container for instance methods for the TaskExecution structure.
-Cancels a task execution. Does nothing if the task has already been resolved.
-Adds a functions to be called when the task settles for each possible state it can transition to.
-Represents the execution of a Task, with methods to cancel it, react to its
-results, and retrieve its eventual value. TaskExecution objects aren't created
-directly by users, but instead returned as a result from Task's run()
method.
-b
class TaskExecution {
- /*~*/
- constructor(task, deferred) {
- this._task = task;
- this._deferred = deferred;
- }
-
- /*~*/
- cancel() {
- this._deferred.maybeCancel();
- return this;
- }
-
- /*~*/
- listen(pattern) {
- this._deferred.listen(pattern);
- return this;
- }
-
- /*~*/
- promise() {
- return this._deferred.promise();
- }
-
- /*~*/
- future() {
- return this._deferred.future();
- }
-}
Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task
function.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall value, reason, resources:
- new (
- ({ resolve: (value) => Void, reject: (reason) => Void, cancel: () => Void }) => resources,
- (resources) => Void,
- (resources) => Void
- ) => Task value reason resources
Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task
function.
See the documentation for folktale/data/task
for more details.
Constructs a Task that resolves with a successful value.
-Constructs a Task that resolves with a rejected value.
-Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
The container for instance methods for the Task structure.
-Constructs a new task that awaits both tasks to resolve. The result of the new task is a tuple containing the results of the left and right tasks, if they all succeed, or the first failure if they fail.
-Combines two tasks such that the resulting task assimilates the result of the -first one to resolve.
-Executes a Task and returns a TaskExecution
object representing the execution.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
Part of the Monad instance for Fantasy Land 2.x+. See the chain
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Chooses and executes a function for each variant in a Task. The function must -return a new task, whose value and state will be assimilated.
-Transforms a failed task's value and state.
-Inverts the state of a Task. That is, turns successful tasks into failed ones, and failed tasks into successful ones.
-Applies the function in the left task to the value on the right task. The left -task is ran to completion before the right task is started.
-Transforms the rejected or resolved values of a Task with a function. The state of the task is not changed.
-Transforms the value and state of a Task.
-Transforms the value of a successful task.
-Transforms the value of a failed task.
-Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task
function.
class Task {
- /*~
- * stability: experimental
- * type: |
- * forall value, reason, resources:
- * new (
- * ({ resolve: (value) => Void, reject: (reason) => Void, cancel: () => Void }) => resources,
- * (resources) => Void,
- * (resources) => Void
- * ) => Task value reason resources
- */
- constructor(computation, onCancel, cleanup) {
- this._computation = computation;
- this._onCancel = onCancel || noop;
- this._cleanup = cleanup || noop;
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e, v1, v2, r:
- * (Task e v1 r).((v1) => Task e v2 r) => Task e v2 r
- */
- chain(transformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: value => {
- transformation(value).run().listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: resolver.resolve
- });
- }
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e, v1, v2, r:
- * (Task e v1 r).((v1) => v2) => Task e v2 r
- */
- map(transformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: value => resolver.resolve(transformation(value))
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e1, e2, v, r:
- * (Task e1 v r).((e1) => e2) => Task e2 v r
- */
- mapRejected(transformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: reason => resolver.reject(transformation(reason)),
- onResolved: resolver.resolve
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e, v1, v2, r:
- * (Task e ((v1) => v2) r).(Task e v1 r) => Task e v2 r
- */
- apply(task) {
- return this.chain(f => task.map(f));
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e1, e2, v1, v2, r:
- * (Task e1 v1 r).((e1) => e2, (v1) => v2) => Task e2 v2 r
- */
- bimap(rejectionTransformation, successTransformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: reason => resolver.reject(rejectionTransformation(reason)),
- onResolved: value => resolver.resolve(successTransformation(value))
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e1, e2, v1, v2, r:
- * type Pattern = { row |
- * Cancelled: () => Task e2 v2 r,
- * Resolved: (b) => Task e2 v2 r,
- * Rejected: (a) => Task e2 v2 r
- * }
- *
- * (Task e1 v1 r).(Pattern) => Task e2 v2 r
- */
- willMatchWith(pattern) {
- return new Task(
- resolver => {
- const execution = this.run();
- const resolve = (handler) => (value) => handler(value).run().listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: resolver.resolve
- });
- execution.listen({
- onCancelled: resolve(_ => pattern.Cancelled()),
- onRejected: resolve(pattern.Rejected),
- onResolved: resolve(pattern.Resolved)
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e, v, r: (Task e v r).() => Task v e r
- */
- swap() {
- return new Task(
- resolver => {
- let execution = this.run(); // eslint-disable-line prefer-const
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.resolve,
- onResolved: resolver.reject
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e, e2, v, r1, r2:
- * (Task e v r1).((e) => Task e2 v r2) => Task e2 v r2
- */
- orElse(handler) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onResolved: resolver.resolve,
- onRejected: reason => {
- handler(reason).run().listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: resolver.resolve
- });
- }
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
-
-
- /*~
- * stability: experimental
- * type: |
- * forall e, v, r1, r2:
- * (Task e v r1).(Task e v r2) => Task e v (r1 and r2)
- */
- or(that) {
- return new Task(
- resolver => {
- let thisExecution = this.run(); // eslint-disable-line prefer-const
- let thatExecution = that.run(); // eslint-disable-line prefer-const
- let done = false;
-
- const guard = (fn, execution) => (value) => {
- if (!done) {
- done = true;
- execution.cancel();
- fn(value);
- }
- };
-
- thisExecution.listen({
- onRejected: guard(resolver.reject, thatExecution),
- onCancelled: guard(resolver.cancel, thatExecution),
- onResolved: guard(resolver.resolve, thatExecution)
- });
-
- thatExecution.listen({
- onRejected: guard(resolver.reject, thisExecution),
- onCancelled: guard(resolver.cancel, thisExecution),
- onResolved: guard(resolver.resolve, thisExecution)
- });
-
- return [thisExecution, thatExecution];
- },
- ([thisExecution, thatExecution]) => {
- thisExecution.cancel();
- thatExecution.cancel();
- }
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e, v1, v2, r1, r2:
- * (Task e v1 r1).(Task e v2 r2) => Task e (v1, v2) (r1 and r2)
- */
- and(that) {
- return new Task(
- resolver => { // eslint-disable-line max-statements
- let thisExecution = this.run(); // eslint-disable-line prefer-const
- let thatExecution = that.run(); // eslint-disable-line prefer-const
- let valueLeft = null;
- let valueRight = null;
- let doneLeft = false;
- let doneRight = false;
- let cancelled = false;
-
- const guardResolve = (setter) => (value) => {
- if (cancelled) return;
-
- setter(value);
- if (doneLeft && doneRight) {
- resolver.resolve([valueLeft, valueRight]);
- }
- };
-
- const guardRejection = (fn, execution) => (value) => {
- if (cancelled) return;
-
- cancelled = true;
- execution.cancel();
- fn(value);
- };
-
- thisExecution.listen({
- onRejected: guardRejection(resolver.reject, thatExecution),
- onCancelled: guardRejection(resolver.cancel, thatExecution),
- onResolved: guardResolve(x => {
- valueLeft = x;
- doneLeft = true;
- })
- });
-
- thatExecution.listen({
- onRejected: guardRejection(resolver.reject, thisExecution),
- onCancelled: guardRejection(resolver.cancel, thisExecution),
- onResolved: guardResolve(x => {
- valueRight = x;
- doneRight = true;
- })
- });
-
- return [thisExecution, thatExecution];
- },
- ([thisExecution, thatExecution]) => {
- thisExecution.cancel();
- thatExecution.cancel();
- }
- );
- }
-
- /*~
- * stability: experimental
- * type: |
- * forall e, v, r: (Task e v r).() => TaskExecution e v r
- */
- run() {
- let deferred = new Deferred(); // eslint-disable-line prefer-const
- deferred.listen({
- onCancelled: _ => {
- defer(_ => {
- this._onCancel(resources);
- this._cleanup(resources);
- });
- },
-
- onResolved: _value => {
- defer(_ => {
- this._cleanup(resources);
- });
- },
-
- onRejected: _reason => {
- defer(_ => {
- this._cleanup(resources);
- });
- }
- });
-
- const resources = this._computation({
- reject: error => { deferred.reject(error) },
- resolve: value => { deferred.resolve(value) },
- cancel: _ => { deferred.maybeCancel() }
- });
-
- return new TaskExecution(this, deferred);
- }
-}
Constructs a new task that awaits both tasks to resolve. The result of the new task is a tuple containing the results of the left and right tasks, if they all succeed, or the first failure if they fail.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e, v1, v2, r1, r2:
- (Task e v1 r1).(Task e v2 r2) => Task e (v1, v2) (r1 and r2)
Constructs a new task that awaits both tasks to resolve. The result of the new task is a tuple containing the results of the left and right tasks, if they all succeed, or the first failure if they fail.
-While the tasks are started from left-to-right, the method will not wait one task to finish before starting another. For asynchronous tasks this effectively gives you concurrent execution.
-Note that cancelling one of the input tasks will cancel the combined tasks as well. Cancelling the combined tasks will cancell both input tasks. If any of the input tasks fail, the other input task will be cancelled as well.
-If you need to combine more than two tasks concurrently, take a look at the waitAll
function in the data/task
module.
const { task } = require('folktale/data/task');
-
-const delay = (ms) => task(
- resolver => setTimeout(() => resolver.resolve(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const result = await delay(30).and(delay(40)).run().promise();
-$ASSERT(result == [30, 40]);
-
and(that) {
- return new Task(
- resolver => { // eslint-disable-line max-statements
- let thisExecution = this.run(); // eslint-disable-line prefer-const
- let thatExecution = that.run(); // eslint-disable-line prefer-const
- let valueLeft = null;
- let valueRight = null;
- let doneLeft = false;
- let doneRight = false;
- let cancelled = false;
-
- const guardResolve = (setter) => (value) => {
- if (cancelled) return;
-
- setter(value);
- if (doneLeft && doneRight) {
- resolver.resolve([valueLeft, valueRight]);
- }
- };
-
- const guardRejection = (fn, execution) => (value) => {
- if (cancelled) return;
-
- cancelled = true;
- execution.cancel();
- fn(value);
- };
-
- thisExecution.listen({
- onRejected: guardRejection(resolver.reject, thatExecution),
- onCancelled: guardRejection(resolver.cancel, thatExecution),
- onResolved: guardResolve(x => {
- valueLeft = x;
- doneLeft = true;
- })
- });
-
- thatExecution.listen({
- onRejected: guardRejection(resolver.reject, thisExecution),
- onCancelled: guardRejection(resolver.cancel, thisExecution),
- onResolved: guardResolve(x => {
- valueRight = x;
- doneRight = true;
- })
- });
-
- return [thisExecution, thatExecution];
- },
- ([thisExecution, thatExecution]) => {
- thisExecution.cancel();
- thatExecution.cancel();
- }
- );
- }
Applies the function in the left task to the value on the right task. The left -task is ran to completion before the right task is started.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e, v1, v2, r:
- (Task e ((v1) => v2) r).(Task e v1 r) => Task e v2 r
Applies the function in the left task to the value on the right task. The left -task is ran to completion before the right task is started.
-Note that the right task isn't ran if the left task contains a failure.
-If any of the tasks fail, the result will be that failure. Likewise, if any of -the tasks is cancelled, the result will be that cancellation.
-const { of, rejected } = require('folktale/data/task');
-
-const result1 = await of(x => x + 1).apply(of(1)).run().promise();
-$ASSERT(result1 == 2);
-
-try {
- const result2 = await of(x => x + 1).apply(rejected(1)).run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 1);
-}
-
apply(task) {
- return this.chain(f => task.map(f));
- }
Transforms the rejected or resolved values of a Task with a function. The state of the task is not changed.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e1, e2, v1, v2, r:
- (Task e1 v1 r).((e1) => e2, (v1) => v2) => Task e2 v2 r
Transforms the rejected or resolved values of a Task with a function. The state of the task is not changed.
-const { of, rejected } = require('folktale/data/task');
-
-const result1 = await of(1).bimap(
- (error) => error + 1,
- (success) => success - 1
-).run().promise();
-$ASSERT(result1 == 0);
-
-try {
- const result2 = await rejected(1).bimap(
- (error) => error + 1,
- (success) => success - 1
- ).run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 2);
-}
-
bimap(rejectionTransformation, successTransformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: reason => resolver.reject(rejectionTransformation(reason)),
- onResolved: value => resolver.resolve(successTransformation(value))
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
Transforms the value and state of a Task.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e, v1, v2, r:
- (Task e v1 r).((v1) => Task e v2 r) => Task e v2 r
Transforms the value and state of a Task.
-As with .map()
the transformation only happens if the original task resolves successfully, but instead of incorporating just the value, .chain()
assimilates the value and state of the returned Task. This allows .chain()
to be used for sequencing computations involving Task.
const { of, rejected } = require('folktale/data/task');
-
-const helloWorld = of('hello').chain(v => of(v + ' world'));
-const result1 = await helloWorld.run().promise();
-$ASSERT(result1 == 'hello world');
-
-const world = of('hello').chain(v => rejected('world'));
-try {
- const result2 = await world.run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 'world');
-}
-
-const hello = rejected('hello').chain(v => of('world'));
-try {
- const result3 = await hello.run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 'hello');
-}
-
chain(transformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: value => {
- transformation(value).run().listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: resolver.resolve
- });
- }
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
Transforms the value of a successful task.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e, v1, v2, r:
- (Task e v1 r).((v1) => v2) => Task e v2 r
Transforms the value of a successful task.
-const { of, rejected } = require('folktale/data/task');
-
-const hello = of('hello').map(v => v.toUpperCase());
-const result1 = await hello.run().promise();
-$ASSERT(result1 == 'HELLO');
-
-const hello2 = rejected('hello').map(v => v.toUpperCase());
-try {
- const result2 = await hello2.run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 'hello');
-}
-
map(transformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: value => resolver.resolve(transformation(value))
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
Transforms the value of a failed task.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e1, e2, v, r:
- (Task e1 v r).((e1) => e2) => Task e2 v r
Transforms the value of a failed task.
-const { of, rejected } = require('folktale/data/task');
-
-const hello = of('hello').mapRejected(v => v.toUpperCase());
-const result1 = await hello.run().promise();
-$ASSERT(result1 == 'hello');
-
-const hello2 = rejected('hello').mapRejected(v => v.toUpperCase());
-try {
- const result2 = await hello2.run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 'HELLO');
-}
-
mapRejected(transformation) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: reason => resolver.reject(transformation(reason)),
- onResolved: resolver.resolve
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
Constructs a Task that resolves with a successful value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Task that resolves with a successful value.
-The value is computed eagerly. If you need the value to be computed only when the task is ran you'll have to use the task
function.
const { of } = require('folktale/data/task');
-
-const result = await of('hello').run().promise();
-$ASSERT(result == 'hello');
-
of(value) {
- return new Task(resolver => resolver.resolve(value));
- }
Combines two tasks such that the resulting task assimilates the result of the -first one to resolve.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e, v, r1, r2:
- (Task e v r1).(Task e v r2) => Task e v (r1 and r2)
Combines two tasks such that the resulting task assimilates the result of the -first one to resolve.
-Note that once a task finishes, the other task is cancelled. If the combined -task is cancelled, both tasks are also cancelled.
-As a convenience for combining a large or unknown amount of tasks, the
-waitAny()
function in the data/task
module preceives an array of Tasks to
-"or" together.
const { task } = require('folktale/data/task');
-
-const delay = (ms) => task(
- resolver => setTimeout(() => resolver.resolve(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const timeout = (ms) => task(
- resolver => setTimeout(() => resolver.reject(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const result = await delay(20).or(timeout(300))
- .run().promise();
-$ASSERT(result == 20);
-
-const result2 = await delay(200).or(timeout(100))
- .run().promise().catch(e => `timeout ${e}`);
-$ASSERT(result2 == 'timeout 100');
-
or(that) {
- return new Task(
- resolver => {
- let thisExecution = this.run(); // eslint-disable-line prefer-const
- let thatExecution = that.run(); // eslint-disable-line prefer-const
- let done = false;
-
- const guard = (fn, execution) => (value) => {
- if (!done) {
- done = true;
- execution.cancel();
- fn(value);
- }
- };
-
- thisExecution.listen({
- onRejected: guard(resolver.reject, thatExecution),
- onCancelled: guard(resolver.cancel, thatExecution),
- onResolved: guard(resolver.resolve, thatExecution)
- });
-
- thatExecution.listen({
- onRejected: guard(resolver.reject, thisExecution),
- onCancelled: guard(resolver.cancel, thisExecution),
- onResolved: guard(resolver.resolve, thisExecution)
- });
-
- return [thisExecution, thatExecution];
- },
- ([thisExecution, thatExecution]) => {
- thisExecution.cancel();
- thatExecution.cancel();
- }
- );
- }
Transforms a failed task's value and state.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e, e2, v, r1, r2:
- (Task e v r1).((e) => Task e2 v r2) => Task e2 v r2
Transforms a failed task's value and state.
-const { of, rejected } = require('folktale/data/task');
-
-const hello = of('hello').orElse(error => of('world'));
-const result1 = await hello.run().promise();
-$ASSERT(result1 == 'hello');
-
-const world = rejected('hello').orElse(error => of('world'));
-const result2 = await world.run().promise();
-$ASSERT(result2 == 'world');
-
-const helloWorld = rejected('hello').orElse(error => rejected(error + ' world'));
-try {
- const result3 = await helloWorld.run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 'hello world');
-}
-
orElse(handler) {
- return new Task(
- resolver => {
- const execution = this.run();
- execution.listen({
- onCancelled: resolver.cancel,
- onResolved: resolver.resolve,
- onRejected: reason => {
- handler(reason).run().listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: resolver.resolve
- });
- }
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
Constructs a Task that resolves with a rejected value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Task that resolves with a rejected value.
-The value is computed eagerly. If you need the value to be computed only when the task is ran you'll have to use the task
function.
const { rejected } = require('folktale/data/task');
-
-try {
- const result = await rejected('hello').run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 'hello');
-}
-
rejected(reason) {
- return new Task(resolver => resolver.reject(reason));
- }
Executes a Task and returns a TaskExecution
object representing the execution.
This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Executes a Task and returns a TaskExecution
object representing the execution.
const { task } = require('folktale/data/task');
-
-let message = '';
-const sayHello = task(resolver => {
- message = 'hello';
- resolver.resolve();
-});
-
-$ASSERT(message == '');
-
-await sayHello.run().promise();
-
-$ASSERT(message == 'hello');
-
run() {
- let deferred = new Deferred(); // eslint-disable-line prefer-const
- deferred.listen({
- onCancelled: _ => {
- defer(_ => {
- this._onCancel(resources);
- this._cleanup(resources);
- });
- },
-
- onResolved: _value => {
- defer(_ => {
- this._cleanup(resources);
- });
- },
-
- onRejected: _reason => {
- defer(_ => {
- this._cleanup(resources);
- });
- }
- });
-
- const resources = this._computation({
- reject: error => { deferred.reject(error) },
- resolve: value => { deferred.resolve(value) },
- cancel: _ => { deferred.maybeCancel() }
- });
-
- return new TaskExecution(this, deferred);
- }
Inverts the state of a Task. That is, turns successful tasks into failed ones, and failed tasks into successful ones.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Inverts the state of a Task. That is, turns successful tasks into failed ones, and failed tasks into successful ones.
-const { of, rejected } = require('folktale/data/task');
-
-try {
- const result1 = await of(1).swap().run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 1);
-}
-
-const result2 = await rejected(1).swap().run().promise();
-$ASSERT(result2 == 1);
-
swap() {
- return new Task(
- resolver => {
- let execution = this.run(); // eslint-disable-line prefer-const
- execution.listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.resolve,
- onResolved: resolver.reject
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
Chooses and executes a function for each variant in a Task. The function must -return a new task, whose value and state will be assimilated.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall e1, e2, v1, v2, r:
- type Pattern = { row |
- Cancelled: () => Task e2 v2 r,
- Resolved: (b) => Task e2 v2 r,
- Rejected: (a) => Task e2 v2 r
- }
-
- (Task e1 v1 r).(Pattern) => Task e2 v2 r
Chooses and executes a function for each variant in a Task. The function must -return a new task, whose value and state will be assimilated.
-const { task, of, rejected } = require('folktale/data/task');
-
-const result1 = await of('hello').willMatchWith({
- Cancelled: () => of('cancelled'),
- Rejected: (error) => of(`rejected ${error}`),
- Resolved: (value) => of(`resolved ${value}`)
-}).run().promise();
-$ASSERT(result1 == 'resolved hello');
-
-const result2 = await rejected('hello').willMatchWith({
- Cancelled: () => of('cancelled'),
- Rejected: (error) => of(`rejected ${error}`),
- Resolved: (value) => of(`resolved ${value}`)
-}).run().promise();
-$ASSERT(result2 == 'rejected hello');
-
-const result3 = await task(r => r.cancel()).willMatchWith({
- Cancelled: () => of('cancelled'),
- Rejected: (error) => of(`rejected ${error}`),
- Resolved: (value) => of(`resolved ${value}`)
-}).run().promise();
-$ASSERT(result3 == 'cancelled');
-
willMatchWith(pattern) {
- return new Task(
- resolver => {
- const execution = this.run();
- const resolve = (handler) => (value) => handler(value).run().listen({
- onCancelled: resolver.cancel,
- onRejected: resolver.reject,
- onResolved: resolver.resolve
- });
- execution.listen({
- onCancelled: resolve(_ => pattern.Cancelled()),
- onRejected: resolve(pattern.Rejected),
- onResolved: resolve(pattern.Resolved)
- });
- return execution;
- },
- execution => execution.cancel()
- );
- }
A data structure that models asynchronous actions, supporting safe cancellation and automatic resource handling.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A data structure that models asynchronous actions, supporting safe cancellation and automatic resource handling.
-const { task } = require('folktale/data/task');
-
-const delay = (ms) => task(
- (resolver) => setTimeout(() => resolver.resolve(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-// waits 100ms
-const result = await delay(100).or(delay(2000)).run().promise();
-$ASSERT(result == 100);
-
Because JavaScript implementations are usually single-threaded, and there's no coroutine support, concurrent applications tend to use either callbacks (continuation-passing style) or Promise.
-Callbacks aren't very composable. In order to combine callbacks, a user has to write code specific to each place that will use them. While you can make code written using callbacks maintainable, their low-level nature forces you to deal with a fair amount of detail that could be resolved by a library, including optimal concurrency:
-const map = (list, fn, done) => {
- let result = [];
- let pending = list.length;
- let resolved = false;
-
- list.forEach((item, index) => {
- fn(item, (error, value) => {
- if (!resolved) {
- if (error) {
- resolved = true;
- done(error, null);
- } else {
- pending -= 1;
- result[index] = value;
- if (pending === 0) {
- done(null, result);
- }
- }
- }
- });
- });
-};
-
-map([1, 2], (x, c) => c(null, x + 1), (e, v) => {
- $ASSERT(e == null);
- $ASSERT(v == []);
-});
-
-map([1, 2], (x, c) => c(x), (e, v) => {
- $ASSERT(e == 1);
- $ASSERT(v == null);
-});
-
Because functions using callbacks don't return a value to the caller, they're not composable. They are also, of course, not usable with JavaScript control-flow constructs either. So it's not possible to write something like:
-if (someAsyncPredicate(...)) {
- ...
-}
-
Instead of returning a value, someAsyncPredicate
passes the result of its computation to another function (the callback). Because of this, there's no value for the if
statement to work with.
Promises alleviate this a bit. Promises are first-class values, so regular synchronous functions may invoke functions yielding promises and get a value back. In some cases, that's not going to be the right value, but with async/await you get a lot of the compositionality back, as you can mix promises and regular synchronous constructs freely in special (async
) functions.
Promises, however, do not support cancellations. Since they represent values, not computations, a Promise by itself has no concept of "what to cancel". It only waits for an external process to provide a value to it. In JavaScript, promises also suffer from not being able to nest. This is not a problem for most common cases, but it makes writing some data structures much less convenient and more error-prone.
-Task, on the other hand, works at the computation level, so it knows which resources a computation has allocated to do the work, and can safely collect those resources automatically when the computation is cancelled. Very similar to how killing a thread or process allows you to clean things up. Because Tasks abstract computations, and not values, things that aren't possible with Promises, like running operations sequentially, is supported natively by the Task API.
-The task
function is how Tasks are generally created. It takes a computation (a function that will perform all of the work), and optionally an object defining handlers for how to clean up the resources allocated by the computation, and what to do if the task is cancelled.
A task that simply resolves after a certain amount of time would look like this:
-const { task } = require('folktale/data/task');
-
-const delay = (time) => task(
- (resolver) => {
- return setTimeout(() => resolver.resolve(time), time);
- },
- {
- cleanup(timerId) {
- clearTimeout(timerId);
- },
- onCancelled(timerId) {
- /* does nothing */
- }
- }
-);
-
-const result = await delay(100).run().promise();
-$ASSERT(result == 100);
-
Here the computation takes a resolver
argument, which contains methods to change the state of the task execution. resolver.resolve(value)
signals that the execution succeeded, and provides a return value for it. resolver.reject(reason)
signals that the execution failed, and provides the reason of its failure. resolver.cancel()
cancels the exection of the task.
--NOTE
-
While.cancel()
will cancel the execution of the Task, the processes started by the task computation will not be automatically stopped. The task computation must stop those itself, as we'll see later in the section about cancelling tasks.
The onCancelled
and cleanup
functions will receive any value returned by the task computation. Typically, the computation will allocate some resources, and return a handle to those resources such that cleanup
and onCancelled
may free those as they see fit. cleanup
is always called once a Task finishes its execution, regardless of what state it ends up in (cancelled, rejected, or resolved). If not provided, Folktale just does nothing in response to those events.
Sometimes Task functions expect a Task as input or result value, but you already have the value that should be computed. While you can always resolve a Task synchronously, like so:
-const one = task(resolver => resolver.resolve(1));
-
It's practical to use the of()
and rejected()
methods instead. The first creates a task that resolves successfuly with a value, whereas rejected()
creates a task that resolves with a failure:
const { of, rejected } = require('folktale/data/task');
-
-const one_ = of(1);
-const two_ = rejected(2);
-
Creating a Task does not start any computation, it only provides a description for how to do something. In a sense, they are similar to a function definition. In order to execute the operations a Task defines, one must run it:
-const { task } = require('folktale/data/task');
-
-const hello = task(resolver => resolver.resolve('hello'));
-
-const helloExecution = hello.run();
-
Running a Task with the .run()
method returns a TaskExecution
object. This object allows one to cancel the execution of the task, or query its eventual value either as JavaScript's Promise, or a Folktale's Future:
const value = await helloExecution.promise();
-$ASSERT(value === 'hello');
-
-helloExecution.future().map(value => {
- $ASSERT(value === 'hello');
-});
-
--NOTE
-
While Promises let you use JavaScript'sasync/await
feature, it does not support nested promises, and cancellations are handled as rejections. Future is a simpler structure, which models all three states of a Task's eventual value, but does not supportasync/await
.
TaskExecution also allows one to react to the result of running a task with the listen()
method. This is useful for handling cancellations or rejections at the top level, where one doesn't need to combine the task with anything else:
helloExecution.listen({
- onCancelled: () => 'task was cancelled',
- onRejected: (reason) => 'task was rejected',
- onResolved: (value) => $ASSERT(value == 'hello')
-});
-
Task's primary goal is helping with concurrency, or the ordering of independent processes within an application. There are three primary categories of operations for this in Folktale:
-One task models and independent process that eventually computes a value. Sometimes one task depends on the result of another task, and as thus may only run if that task resolves successfully. In order to sequence tasks we use the .chain()
method:
const { task, of } = require('folktale/data/task');
-
-const concat = (a, b) => task(resolver => resolver.resolve(a + b));
-
-const taskA = of('hello');
-const taskB = of('world');
-
-const theTask = taskA.chain(x => taskB.chain(y => concat(x, y)));
-
-const result = await theTask.run().promise();
-$ASSERT(result == 'helloworld');
-
In this case, taskB
only starts after taskA
finishes executing successfully, and concat
only starts after both taskA
and taskB
finish executing. It makes sense for concat
to wait on both taskA
and taskB
, as it needs the two tasks to finish successfully before it can be executed, but there's no reason for taskA
and taskB
to wait for each other.
Suppose you send a request to a server, but if you don't get a response in a couple of seconds the program should just give up. This scenario can be modelled as two independent processes: a request to a server, and a timer that fires after a couple of seconds. The program should pick whichever process resolves first. With Folktale's Task, this is done with the .or()
method.
The .or()
method combines two tasks such that the resulting task assimilates the result of the first one to resolve:
const { task } = require('folktale/data/task');
-
-const delay = (ms) => task(
- resolver => setTimeout(() => resolver.resolve(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const timeout = (ms) => task(
- resolver => setTimeout(() => resolver.reject(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const result = await delay(20).or(timeout(300))
- .run().promise();
-$ASSERT(result == 20);
-
-const result2 = await delay(200).or(timeout(100))
- .run().promise().catch(e => `timeout ${e}`);
-$ASSERT(result2 == 'timeout 100');
-
As a convenience for combining a large or unknown amount of tasks, the waitAny()
function receives an array of Tasks to "or" together:
const { waitAny } = require('folktale/data/task');
-
-const result3 = await waitAny([
- delay(10),
- delay(20),
- delay(30)
-]).run().promise(); // equivalent to `delay(10).or(delay(20).or(delay(30)))`
-$ASSERT(result3 == 10);
-
If some computation depends on the results of more than one process you could use a nested sequence of .chain()
calls to describe these dependencies, but that could be inefficient. If you don't care about the ordering of these processes, .chain()
would impose an order on them. In essence, you wouldn't be getting any concurrency performance out of it.
Instead of sequencing unrelated tasks, you can combine them with the .and()
operation. a.and(b)
combines two tasks concurrently. That is, when you run this result, it'll start both a
and b
concurrently, and wait for their return without imposing any ordering on it. The result of the task will be a tuple containing the values of a
and b
:
const { task } = require('folktale/data/task');
-
-const delay = (ms) => task(
- resolver => setTimeout(() => resolver.resolve(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-// This takes 100ms
-const result = await delay(60).chain(x => delay(40).map(y => [x, y])).run().promise();
-$ASSERT(result == [60, 40]);
-
-// This takes 60ms
-const result2 = await delay(60).and(delay(40)).run().promise();
-$ASSERT(result == [60, 40]);
-
Because the tasks are started concurrently, and no ordering is imposed on them, the entire computation takes as long as the slowest of its processes. If you were to use .chain()
to combine them, it would take the sum of all processes' times.
As a convenience for combining a large or unknown amount of tasks, the waitAll()
function receives an array of Tasks to "and" together. waitAll()
returns a normalised array of the results instead of nested tuples:
const { waitAll } = require('folktale/data/task');
-
-const result3 = await delay(10).and(delay(20).and(delay(30))).run().promise();
-$ASSERT(result3 == [10, [20, 30]]);
-
-const result4 = await waitAll([
- delay(10),
- delay(20),
- delay(30)
-]).run().promise();
-$ASSERT(result4 == [10, 20, 30]);
-
Sometimes processes will fail. You can recover from such failures using the .orElse()
method. The method takes a function, passes to it the error value, if one happened, and expects it to return a new Task, whose state will be assimilated. In order to recover from the error you'd return a successful task, so computations that depend on it may proceed.
For example, this could be used to retry a particular computation:
-const { task, of, rejected } = require('folktale/data/task');
-
-let errors = [];
-
-
-const result = await rejected('nope').orElse(reason => {
- errors.push(reason);
- return of('yay');
-}).run().promise();
-
-$ASSERT(result == 'yay');
-$ASSERT(errors == ['nope']);
-
.orElse()
can also return rejected or cancelled tasks, and their state will be assimilated likewise:
errors = [];
-const retry = (task, times) => {
- return task.orElse(reason => {
- errors.push(reason);
- if (times > 1) {
- return retry(task, times - 1)
- } else {
- return rejected('I give up');
- }
- });
-};
-
-let runs = 0;
-const ohNoes = task(r => {
- runs += 1;
- r.reject('fail');
-});
-
-try {
- const result2 = await retry(ohNoes, 3).run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(runs == 3);
- $ASSERT(errors == ['fail', 'fail', 'fail']);
- $ASSERT(error == 'I give up');
-}
-
Constructs a new task that waits all given tasks resolve. The result of the new -task is an array with the results of the input tasks, if they all succeed, or -the first failure if they fail.
-Constructs a new task that waits any of the given tasks resolve. The result of -the first task to resolve is assimilated by the new task.
-Constructs a Task that resolves with a successful value.
-Constructs a Task that resolves with a rejected value.
-Constructs a Task and associates a computation to it. The computation is executed every time the Task is ran, and should provide the result of the task: a success or failure along with its value.
-Tasks model asynchronous processes with automatic resource handling. They are generally constructed with the task
function.
Represents the execution of a Task, with methods to cancel it, react to its
-results, and retrieve its eventual value. TaskExecution objects aren't created
-directly by users, but instead returned as a result from Task's run()
method.
-b
{
- of: Task.of,
- rejected: Task.rejected,
- task: require('./task'),
- waitAny: require('./wait-any'),
- waitAll: require('./wait-all'),
- _Task: Task,
- _TaskExecution: require('./_task-execution')
-}
Constructs a Task and associates a computation to it. The computation is executed every time the Task is ran, and should provide the result of the task: a success or failure along with its value.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Task and associates a computation to it. The computation is executed every time the Task is ran, and should provide the result of the task: a success or failure along with its value.
-A computation is required. For task, this is a function that receives one argument, the resolver, and does some work that may be asynchronous. Once done, the computation uses the resolver to resolve()
a task succesfully, or reject()
it with an error.
Asynchronous computations that allocate some external resource (for example, starting an HTTP request) can return a handler for those resources and define a cleanup function. The cleanup
function will be called with the resources returned, and will have the chance of disposing of them properly once the Task is finished, either properly, as a success or rejection, or eagerly, as a cancellation.
Optionally, a Task may define an onCancelled
function that will be called if the Task is cancelled, before the cleanup.
const { task } = require('folktale/data/task');
-
A simple, successful task:
-const hello = task(resolver => resolver.resolve('hello'));
-
-const result1 = await hello.run().promise();
-$ASSERT(result1 == 'hello');
-
A simple, failed task:
-const nope = task(resolver => resolver.reject('hello'));
-
-try {
- await nope.run().promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(error == 'hello');
-}
-
An asynchronous task that allocates and frees resources:
-const delay50 = task(
- resolver => {
- return setTimeout(() => resolver.resolve('yay'), 50);
- },
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const result2 = await delay50.run().promise();
-$ASSERT(result2 == 'yay');
-
A task with a cancellation handler:
-let resolved = false;
-const cancelMe = task(
- resolver => {
- return setTimeout(() => {
- resolved = true;
- resolver.resolve('yay');
- }, 50);
- },
- {
- cleanup: (timer) => clearTimeout(timer),
- onCancelled: (timer) => {
- 'task was cancelled';
- }
- }
-);
-
-try {
- const execution = cancelMe.run();
- execution.cancel();
- const result3 = await execution.promise();
- throw 'never happens';
-} catch (error) {
- $ASSERT(resolved == false);
-}
-
(computation, handlers = { onCancelled: noop, cleanup: noop }) =>
- new Task(computation, handlers.onCancelled, handlers.cleanup)
Constructs a new task that waits all given tasks resolve. The result of the new -task is an array with the results of the input tasks, if they all succeed, or -the first failure if they fail.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a new task that waits all given tasks resolve. The result of the new -task is an array with the results of the input tasks, if they all succeed, or -the first failure if they fail.
-While the tasks are started in the order they appear in the array, the function -will not wait one task to finish before starting another. For asynchronous -tasks this effectively gives you concurrent execution.
-Note that cancelling one of the input tasks will cancel the combined task as -well. Cancelling the combined task will cancel all of the input tasks. If any of -the input tasks fails, all of the other input tasks will be cancelled as well.
-const { of, waitAll } = require('folktale/data/task');
-
-const result = await waitAll([
- of(1),
- of(2),
- of(3)
-]).run().promise();
-
-$ASSERT(result == [1, 2, 3]);
-
(tasks) => {
- if (tasks.length === 0) {
- throw new Error('Task.waitAll() requires a non-empty array of tasks.');
- }
-
- return tasks.reduce(
- (a, b) => a.and(b).map(([xs, x]) => [...xs, x]),
- of([])
- )
-}
Constructs a new task that waits any of the given tasks resolve. The result of -the first task to resolve is assimilated by the new task.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a new task that waits any of the given tasks resolve. The result of -the first task to resolve is assimilated by the new task.
-While the tasks are started in the order they appear in the array, the function -will not wait one task to finish before starting another. For asynchronous -tasks this effectively gives you concurrent execution.
-Note that cancelling the combined task will cancel all of the input tasks, and -any of the input tasks resolving will also cancel all of the other input tasks.
-const { task, waitAny } = require('folktale/data/task');
-
-const delay = (ms) => task(
- resolver => setTimeout(() => resolver.resolve(ms), ms),
- {
- cleanup: (timer) => clearTimeout(timer)
- }
-);
-
-const result = await waitAny([
- delay(60), // cancelled after 30ms
- delay(30)
-]).run().promise();
-
-$ASSERT(result == 30);
-
(tasks) => {
- if (tasks.length === 0) {
- throw new Error(`Task.waitAny() requires a non-empty array of tasks.`);
- }
-
- return tasks.reduce((a, b) => a.or(b));
-}
Combines all validation values from an array of them.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
forall a, b: (Array (Validation a b)) => Validation a b
-where a is Semigroup
Combines all validation values from an array of them.
-The function is a convenient way of concatening validations one by one, and will concatenate failures together, but will only maintain the last successful value if they are all successful.
-const { Success, Failure, collect } = require('folktale/data/validation');
-
-collect([
- Success('a'),
- Success('b'),
- Success('c')
-]);
-// ==> Success('c')
-
-collect([
- Failure('a'),
- Success('b'),
- Failure('c')
-]);
-// ==> Failure('ac')
-
-collect([
- Failure('a'),
- Failure('b'),
- Failure('c')
-]);
-// ==> Failure('abc')
-
(validations) =>
- validations.reduce((a, b) => a.concat(b), Success())
A convenience method for the folktale/data/conversions/maybe-to-validation
module.
forall a, b: (Maybe b, a) => Validation a b
A convenience method for the folktale/data/conversions/maybe-to-validation
module.
const Validation = require('folktale/data/validation');
-const Maybe = require('folktale/data/maybe');
-
-Validation.fromMaybe(Maybe.Just(1), 'error');
-// ==> Validation.Success(1)
-
-Validation.fromMaybe(Maybe.Nothing(), 'error');
-// ==> Validation.Failure('error')
-
fromMaybe(aMaybe, fallbackValue) {
- return require('folktale/data/conversions/maybe-to-validation')(aMaybe, fallbackValue);
- }
A convenience method for the folktale/data/conversions/nullable-to-validation
module.
forall a, b: (a or None, b) => Validation b a
A convenience method for the folktale/data/conversions/nullable-to-validation
module.
const Validation = require('folktale/data/validation');
-
-Validation.fromNullable(1, 'error');
-// ==> Validation.Success(1)
-
-Validation.fromNullable(null, 'error');
-// ==> Validation.Failure('error')
-
-Validation.fromNullable(undefined, 'error');
-// ==> Validation.Failure('error')
-
fromNullable(aNullable, fallbackValue) {
- return require('folktale/data/conversions/nullable-to-validation')(aNullable, fallbackValue);
- }
A convenience method for the folktale/data/conversions/result-to-validation
module.
A convenience method for the folktale/data/conversions/result-to-validation
module.
const Result = require('folktale/data/result');
-const Validation = require('folktale/data/validation');
-
-Validation.fromResult(Result.Ok(1));
-// ==> Validation.Success(1)
-
-Validation.fromResult(Result.Error('error'));
-// ==> Validation.Failure('error')
-
fromResult(aResult) {
- return require('folktale/data/conversions/result-to-validation')(aResult);
- }
A data structure that typically models form validations, and other scenarios where
-you want to aggregate all failures, rather than short-circuit if an error
-happens (for which Result
is better suited).
A data structure that typically models form validations, and other scenarios where
-you want to aggregate all failures, rather than short-circuit if an error
-happens (for which Result
is better suited).
A Validation
may either be a Success(value)
, which contains a successful value,
-or a Failure(value)
, which contains an error.
const Validation = require('folktale/data/validation');
-const { Success, Failure } = Validation;
-
-const isPasswordLongEnough = (password) =>
- password.length > 6 ? Success(password)
-: /* otherwise */ Failure(['Password must have more than 6 characters.']);
-
-const isPasswordStrongEnough = (password) =>
- /[\W]/.test(password) ? Success(password)
-: /* otherwise */ Failure(['Password must contain a special character.']);
-
-const isPasswordValid = (password) =>
- Success().concat(isPasswordLongEnough(password))
- .concat(isPasswordStrongEnough(password))
- .map(_ => password);
-
-isPasswordValid('foo');
-// ==> Failure(['Password must have more than 6 characters.', 'Password must contain a special character.'])
-
-isPasswordValid('rosesarered');
-// ==> Failure(['Password must contain a special character.'])
-
-isPasswordValid('rosesarered$andstuff')
-// ==> Success('rosesarered$andstuff')
-
Things like form and schema validation are pretty common in programming, but we end up either using branching or designing very specific solutions for each case.
-With branching, things get quickly out of hand, because it's difficult to abstract over it, and it's thus difficult to understand the resulting program:
-function validateForm(data) {
- const errors = [];
-
- if (!data.name.trim()) {
- errors.push('Name is required');
- }
- if (data.password.length < 6) {
- errors.push('Password must have at least 6 characters');
- }
- if (!/\W/.test(data.password)) {
- errors.push('Password must contain a special character');
- }
-
- return errors;
-}
-
-validateForm({
- name: '',
- password: 'roses$are$red'
-});
-// ==> ['Name is required']
-
-validateForm({
- name: 'Alissa',
- password: 'alis'
-});
-// ==> ['Password must have at least 6 characters', 'Password must contain a special character']
-
-
-validateForm({
- name: 'Alissa',
- password: 'roses$are$red'
-});
-// ==> []
-
Because this function uses if
conditions and modifies a local variable it's not very modular. This means it's not possible to split these checks in smaller pieces that can be entirely understood by themselves — they modify something, and so you have to understand how they modify that thing, in which context, etc. For very simple things it's not too bad, but as complexity grows it becomes unmanageable.
You can manage this complexity by designing a special function for verifying if an object passes some tests. This is common in validation libraries (like jquery-validation) and schema libraries (like jsonschema), but they're ultimately incompatible with other validation routines, and uncomposable:
-const validators = {
- notEmpty(object, { property }) {
- return !object[property].trim() ? [`${property} can't be empty`]
- : /* else */ [];
- },
-
- minLength(object, { property, min }) {
- const value = object[property];
- return value.length < min ? [`${property} must have at least ${min} characters`]
- : /* else */ [];
- },
-
- regexp(object, { property, regexp, message }) {
- return !regexp.test(object[property]) ? [message]
- : /* else */ [];
- }
-};
-
-const validate = (rules) => (object) =>
- rules.reduce((result, rule) =>
- [...result, ...validators[rule.rule](object, rule)],
- []
- );
-
-function validateForm2(data) {
- return validate([
- {
- rule: 'notEmpty',
- property: 'name'
- },
- {
- rule: 'minLength',
- property: 'password',
- min: 6
- },
- {
- rule: 'regexp',
- property: 'password',
- regexp: /\W/,
- message: 'password must contain a special character'
- }
- ])(data);
-}
-
-validateForm2({
- name: '',
- password: 'roses$are$red'
-});
-// ==> ['name can\'t be empty']
-
-validateForm2({
- name: 'Alissa',
- password: 'alis'
-});
-// ==> ['password must have at least 6 characters', 'password must contain a special character']
-
-validateForm2({
- name: 'Alissa',
- password: 'roses$are$red'
-});
-// ==> []
-
Now, while you can understand validations on their own, and share validations between different methods (if they fit the specific use case of the validation library), it's hard to extend it to support new validations (as you can't reuse existing validations), and it's hard to compose validations, because the library defines its own little language for that.
-Neither of those are very compelling. The Validation structure gives you a tool for basing validation libraries and functions on in a way that's reusable and composable:
-const Validation = require('folktale/data/validation');
-const { Success, Failure } = Validation;
-
-const notEmpty = (field, value) =>
- value.trim() ? Success(field)
-: /* else */ Failure([`${field} can't be empty`]);
-
-const minLength = (field, min, value) =>
- value.length > min ? Success(value)
-: /* otherwise */ Failure([`${field} must have at least ${min} characters`]);
-
-const matches = (field, regexp, value, message = '') =>
- regexp.test(value) ? Success(value)
-: /* otherwise */ Failure([message || `${field} must match ${regexp}`]);
-
-const isPasswordValid = (password) =>
- Success().concat(minLength('password', 6, password))
- .concat(matches('password', /\W/, password, 'password must contain a special character'))
- .map(_ => password);
-
-const isNameValid = (name) =>
- notEmpty('name', name);
-
-const validateForm3 = (data) =>
- Success().concat(isPasswordValid(data.password))
- .concat(isNameValid(data.name))
- .map(_ => data);
-
-validateForm3({
- name: '',
- password: 'roses$are$red'
-});
-// ==> Failure(['name can\'t be empty'])
-
-validateForm3({
- name: 'Alissa',
- password: 'alis'
-});
-// ==> Failure(['password must have at least 6 characters', 'password must contain a special character'])
-
-
-validateForm3({
- name: 'Alissa',
- password: 'roses$are$red'
-});
-// ==> Success({ name: 'Alissa', password: 'roses$are$red' })
-
A validation m ay be one of the following cases:
-Success(value)
— represents a successful value (e.g.: the result of passing a validator);Failure(value)
— represents an unsuccessful value (e.g.: the result of failing a validation rule);Validation functions just return one of these two cases instead of throwing errors or mutating other variables. Working with Validation values typically falls into one of the following categories:
-Combining validations: Sometimes we want to create more complex validation rules that reuse simpler ones. These functions let us take the result of those rules and put them together into a single Validation value.
-Transforming values: Sometimes we get a Validation
value that isn't quite what we're looking for. We don't really want to change anything about the status of the validation (whether it passed or failed), but we'd like to tweak the value a little bit. This is the equivalent of applying functions in an expression.
Reacting to results: Once we have a Validation value, we must be able to run pieces of code depending on whether the validation succeeded or failed, with access to the failure reason in the later case.
-We'll see each of these categories in more details below.
-Combining validations is the most common thing to do with the Validation structure once you have some more complex validations in place. There are a few options that may be more or less convenient for you.
-The simplest way of combining validations is through the .concat
method. When concatenating validations, failures are themselves concatenated, but concatenating two successes just yields the latter one:
const { Success, Failure } = require('folktale/data/validation');
-
-Failure('a').concat(Failure('b'));
-// ==> Failure('ab')
-
-Failure('a').concat(Success('b'));
-// ==> Failure('a')
-
-Success('a').concat(Success('b'));
-// ==> Success('b')
-
If you have a constructor for a data structure that can be curried, it's often more convenient to use the .apply
method instead:
const curry = require('folktale/core/lambda/curry');
-
-const Language = (name, compiler) => ({ name, compiler });
-
-Success(curry(2, Language))
- .apply(Success('Rust'))
- .apply(Success('rustc'));
-// ==> Success({ name: 'Rust', compiler: 'rustc' })
-
Finally, if you have an array of validations, it's convenient to use the module-level collect
function:
const { collect } = require('folktale/data/validation');
-
-collect([Failure('a'), Failure('b'), Success('c')]);
-// ==> Failure('ab')
-
collect
uses .concat
internally, so you end up with the last success if all validations succeed:
collect([Success('a'), Success('b')]);
-// ==> Success('b')
-
It's usually more convenient to use .apply
when possible to get transformed values in one go, but sometimes you want to discard some of the values, or you may not know how many values you're getting in the validation. Sometimes you don't even want any of the success values in your resulting structure. For all of these cases, .apply
makes less sense, and you have to transform the result after combining the validations. That's what .map
is for:
const { Success, Failure } = require('folktale/data/validation');
-
-Success(1).map(x => x + 1);
-// ==> Success(2)
-
-Failure('a').map(x => x + 1);
-// ==> Failure('a')
-
-Success(1).concat(Success(2)).map(_ => 'hello');
-// ==> Success('hello')
-
It's also possible to transform the failure values through the .mapFailure
function:
Failure('a').mapFailure(x => x.toUpperCase());
-// ==> Failure('A')
-
-Success('a').mapFailure(x => x.toUpperCase());
-// ==> Success('a')
-
Once you've combined all of the validations, you usually want to see if the overall validation succeeded or failed, and then take the appropriate path in your code for each case. The .matchWith
function helps with this. .matchWith
is a function provided for every union structure in Foltkale, and it lets you select a piece of code to run based on how the value you're interacting with is tagged. In the case of Validations, it lets you select a piece of code for failures, and a piece of code for successes:
const { Success, Failure } = require('folktale/data/validation');
-
-Success(1).matchWith({
- Success: ({ value }) => `Success: ${value}`,
- Failure: ({ value }) => `Failure: ${value}`
-});
-// ==> 'Success: 1'
-
-Failure(1).matchWith({
- Success: ({ value }) => `Success: ${value}`,
- Failure: ({ value }) => `Failure: ${value}`
-});
-// ==> 'Failure: 1'
-
Result and Validation are pretty close structures. They both try to represent
-whether a particular thing has failed or succeeded, and even their vocabulary is
-very similar (Error
vs. Failure
, Ok
vs. Success
). The major difference
-is in some of their methods.
A Result is a data structure that implements the Monad interface (.chain
).
-This makes Result a pretty good structure to model a sequence of computations
-that may fail, where a computation may only run if the previous computation
-succeeded. In this sense, a Result's .chain
method is very similar to
-JavaScript's ;
at the end of statements: the statement at the right of the
-semicolon only runs if the statement at the left did not throw an error.
A Validation is a data structure that implements the Applicative interface
-(.apply
), and does so in a way that if a failure is applied to another
-failure, then it results in a new validation that contains the failures of both
-validations. In other words, Validation is a data structure made for errors that
-can be aggregated, and it makes sense in the contexts of things like form
-validations, where you want to display to the user all of the fields that failed
-the validation rather than just stopping at the first failure.
Validations can't be as easily used for sequencing operations because the
-.apply
method takes two validations, so the operations that create them must
-have been executed already. While it is possible to use Validations in a
-sequential manner, it's better to leave the job to Result, a data structure made
-for that.
Combines all validation values from an array of them.
-Tests if an arbitrary value is a Validation instance.
-Constructs a Validation holding a Success value.
-A convenience method for the folktale/data/conversions/maybe-to-validation
module.
A convenience method for the folktale/data/conversions/nullable-to-validation
module.
A convenience method for the folktale/data/conversions/result-to-validation
module.
Parses a JavaScript object previously serialised as aValidation.toJSON()
into a proper Validation structure.
Constructs a Validation whose value represents a failure.
-Constructs a Validation whose value represents a success.
-{
- Success: Validation.Success,
- Failure: Validation.Failure,
- hasInstance: Validation.hasInstance,
- of: Validation.of,
- fromJSON: Validation.fromJSON,
- [typeSymbol]: Validation[typeSymbol],
- collect: require('./collect'),
-
- /*~
- * type: |
- * forall a, b: (a or None, b) => Validation b a
- */
- fromNullable(aNullable, fallbackValue) {
- return require('folktale/data/conversions/nullable-to-validation')(aNullable, fallbackValue);
- },
-
- /*~
- * type: |
- * forall a, b: (Result a b) => Validation a b
- */
- fromResult(aResult) {
- return require('folktale/data/conversions/result-to-validation')(aResult);
- },
-
- /*~
- * type: |
- * forall a, b: (Maybe b, a) => Validation a b
- */
- fromMaybe(aMaybe, fallbackValue) {
- return require('folktale/data/conversions/maybe-to-validation')(aMaybe, fallbackValue);
- }
-}
Constructs a Validation whose value represents a success.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Validation whose value represents a success.
-See the documentation for the Validation structure to understand how to use this.
-Tests if an arbitrary value is a Success case of a Validation instance.
-A textual representation of the Success variant.
-A textual representation of the Success variant.
-The constructor for this variant.
-The container for instance methods for Success variants of the Validation structure.
-The tag for this variant, unique among the Validation variants.
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-Performs a deep-comparison of two Validation values for equality.
-Tests if an arbitrary value is a Validation instance.
-True if the value is a Success
instance.
Constructs a Validation holding a Success value.
-Transforms a Validation into a Maybe. Failure values are lost in the process.
-Transforms a Validation into a Reseult.
-The value contained in an Success instance of the Validation structure.
-A textual representation for Validation instances.
-A textual representation for Validation instances.
-This method has been renamed to unsafeGet()
.
Extracts the value of a Validation structure, if it's a Success, otherwise returns the provided default value.
-Returns the value inside of the Validation structure, regardless of its state.
-Extracts the value from a Validation
structure.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
Part of the Semigroup instance for Fantasy Land 2.x+. See the concat
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Allows a function to provide functionality to variants in an ADT.
-Applies a function to each case of a Validation.
-Chooses and executes a function for each variant in the Validation structure.
-Parses a JavaScript object previously serialised as aValidation.toJSON()
into a proper Validation structure.
Converts a Validation value to a JavaScript object that may be stored as a JSON value.
-The constructor for this variant.
-The variants in the Validation structure.
-Transforms each side of a Validation with a function, without changing the status of the validation. That is, failures will still be failures, successes will still be successes.
-Transforms the successful value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-Transforms the failure value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-Constructs a Validation whose value represents a failure.
-Constructs a Validation whose value represents a success.
-Success(value) {
- return { value };
- }
If successes, applies the function in one Validation to another. Otherwise concatenate the failures.
-forall a, b, c: (Validation (b) => c).(Validation a b) => Validation a c
If successes, applies the function in one Validation to another. Otherwise concatenate the failures.
-const { Success, Failure } = require('folktale/data/validation');
-
-const tuple = (a) => (b) => [a, b];
-
-Success(tuple).apply(Success(1)).apply(Success(2));
-// ==> Success([1, 2])
-
-Success(tuple).apply(Success(1)).apply(Failure('a'));
-// ==> Failure('a')
-
-Success(tuple).apply(Failure('a')).apply(Success(1));
-// ==> Failure('a')
-
-Success(tuple).apply(Failure('a')).apply(Failure('b'));
-// ==> Failure('ab')
-
{
- /*~*/
- Failure: function apply(aValidation) {
- assertValidation('Failure#apply', aValidation);
- return Failure.hasInstance(aValidation) ? Failure(this.value.concat(aValidation.value))
- : /* otherwise */ this;
- },
-
- /*~*/
- Success: function apply(aValidation) {
- assertValidation('Success#apply', aValidation);
- return Failure.hasInstance(aValidation) ? aValidation
- : /* otherwise */ aValidation.map(this.value);
- }
- }
Transforms each side of a Validation with a function, without changing the status of the validation. That is, failures will still be failures, successes will still be successes.
-forall a, b, c, d:
- (Validation a b).((a) => c, (b) => d) => Validation c d
Transforms each side of a Validation with a function, without changing the status of the validation. That is, failures will still be failures, successes will still be successes.
-const Validation = require('folktale/data/validation');
-
-const upcase = (a) => a.toUpperCase();
-const duplicate = (a) => `${a}${a}`;
-
-Validation.Success('a').bimap(duplicate, upcase);
-// ==> Validation.Success('A')
-
-Validation.Failure('a').bimap(duplicate, upcase);
-// ==> Validation.Failure('aa')
-
{
- /*~*/
- Failure: function bimap(failureTransformation, successTransformation) {
- assertFunction('Validation.Failure#fold', failureTransformation);
- assertFunction('Validation.Failure#fold', successTransformation);
- return Failure(failureTransformation(this.value));
- },
-
- /*~*/
- Success: function bimap(failureTransformation, successTransformation) {
- assertFunction('Validation.Success#fold', failureTransformation);
- assertFunction('Validation.Success#fold', successTransformation);
- return Success(successTransformation(this.value));
- }
- }
Combines two validations together such that failures are aggregated.
-forall a, b:
- (Validation a b).(Validation a b) => Validation a b
-where a is Semigroup
Combines two validations together such that failures are aggregated.
-In cases where both validations contain a successful value, this method just selects the last validation.
-const { Success, Failure } = require('folktale/data/validation');
-
-Failure('a').concat(Failure('b'));
-// ==> Failure('ab')
-
-Failure('a').concat(Success('b'));
-// ==> Failure('a')
-
-Success('a').concat(Failure('b'));
-// ==> Failure('b')
-
-Success('a').concat(Success('b'));
-// ==> Success('b')
-
{
- /*~*/
- Failure: function concat(aValidation) {
- assertValidation('Validation.Failure#concat', aValidation);
- if (Failure.hasInstance(aValidation)) {
- return Failure(this.value.concat(aValidation.value));
- } else {
- return this;
- }
- },
-
- /*~*/
- Success: function concat(aValidation) {
- assertValidation('Validation.Success#concat', aValidation);
- return aValidation;
- }
- }
Constructs a Validation whose value represents a failure.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
Constructs a Validation whose value represents a failure.
-See the documentation for the Validation structure to understand how to use this.
-Tests if an arbitrary value is a Failure case of a Validation instance.
-A textual representation of the Failure variant.
-A textual representation of the Failure variant.
-The constructor for this variant.
-The container for instance methods for Failure variants of the Validation structure.
-The tag for this variant, unique among the Validation variants.
-The type for this variant, unique among Folktale data structures (and ideally unique among all ADTs in your application, as its used for type checking).
-Performs a deep-comparison of two Validation values for equality.
-Tests if an arbitrary value is a Validation instance.
-True if the value is a Failure
instance.
Constructs a Validation holding a Success value.
-Transforms a Validation into a Maybe. Failure values are lost in the process.
-Transforms a Validation into a Reseult.
-The value contained in an Failure instance of the Validation structure.
-A textual representation for Validation instances.
-A textual representation for Validation instances.
-This method has been renamed to unsafeGet()
.
Extracts the value of a Validation structure, if it's a Success, otherwise returns the provided default value.
-Returns the value inside of the Validation structure, regardless of its state.
-Extracts the value from a Validation
structure.
Part of the Applicative instance for Fantasy Land 1.x. See the apply
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the apply
method for details.
Part of the Bifunctor instance for Fantasy Land 2.x+. See the bimap
method for details.
Part of the Semigroup instance for Fantasy Land 2.x+. See the concat
method for details.
Part of the Setoid instance for Fantasy Land 2.x+. See the equals
method for details.
Part of the Functor instance for Fantasy Land 2.x+. See the map
method for details.
Part of the Applicative instance for Fantasy Land 2.x+. See the of
method for details.
Allows a function to provide functionality to variants in an ADT.
-Applies a function to each case of a Validation.
-Chooses and executes a function for each variant in the Validation structure.
-Parses a JavaScript object previously serialised as aValidation.toJSON()
into a proper Validation structure.
Converts a Validation value to a JavaScript object that may be stored as a JSON value.
-The constructor for this variant.
-The variants in the Validation structure.
-Transforms each side of a Validation with a function, without changing the status of the validation. That is, failures will still be failures, successes will still be successes.
-Transforms the successful value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-Transforms the failure value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-Constructs a Validation whose value represents a failure.
-Constructs a Validation whose value represents a success.
-Failure(value) {
- return { value };
- }
Applies a function to each case of a Validation.
-forall a, b, c:
- (Validation a b).((a) => c, (b) => c) => c
Applies a function to each case of a Validation.
-const { Success, Failure } = require('folktale/data/validation');
-
-const upcase = (x) => x.toUpperCase();
-const double = (x) => x + x;
-
-Success('a').fold(upcase, double);
-// ==> 'aa'
-
-Failure('a').fold(upcase, double);
-// ==> 'A'
-
{
- /*~*/
- Failure: function fold(failureTransformation, successTransformation) {
- assertFunction('Validation.Failure#fold', failureTransformation);
- assertFunction('Validation.Failure#fold', successTransformation);
- return failureTransformation(this.value);
- },
-
- /*~*/
- Success: function fold(failureTransformation, successTransformation) {
- assertFunction('Validation.Success#fold', failureTransformation);
- assertFunction('Validation.Success#fold', successTransformation);
- return successTransformation(this.value);
- }
- }
This method has been renamed to unsafeGet()
.
We want to discourage the use of partial functions, and having short names -makes it easy for people to want to use them without thinking about the -problems.
-For more details see https://github.com/origamitower/folktale/issues/42
-This method has been renamed to unsafeGet()
.
'get'() {
- warnDeprecation('`.get()` is deprecated, and has been renamed to `.unsafeGet()`.');
- return this.unsafeGet();
- }
Extracts the value of a Validation structure, if it's a Success, otherwise returns the provided default value.
-forall a, b: (Validation a b).(b) => b
Extracts the value of a Validation structure, if it's a Success, otherwise returns the provided default value.
-const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').getOrElse('b');
-// ==> 'a'
-
-Failure('a').getOrElse('b');
-// ==> 'b'
-
{
- /*~*/
- Failure: function getOrElse(_default) {
- return _default;
- },
-
- /*~*/
- Success: function getOrElse(_default) {
- return this.value;
- }
- }
A textual representation of the Success variant.
-This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.
A textual representation of the Success variant.
-() => variantName
Transforms the successful value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-forall a, b, c: (Validation a b).((b) => c) => Validation a c
Transforms the successful value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-const { Success, Failure } = require('folktale/data/validation');
-
-const upcase = (a) => a.toUpperCase();
-
-Success('a').map(upcase);
-// ==> Success('A')
-
-Failure('a').map(upcase);
-// ==> Failure('a')
-
{
- /*~*/
- Failure: function map(transformation) {
- assertFunction('Validation.Failure#map', transformation);
- return this;
- },
-
- /*~*/
- Success: function map(transformation) {
- assertFunction('Validation.Success#map', transformation);
- return Success(transformation(this.value));
- }
- }
Transforms the failure value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-forall a, b, c:
- (Validation a b).((a) => c) Validation c b
Transforms the failure value inside a Validation structure with an unary function without changing the status of the validation. That is, Success values continue to be Success values, Failure values continue to be Failure values.
-const { Success, Failure } = require('folktale/data/validation');
-
-const upcase = (a) => a.toUpperCase();
-
-Success('a').mapFailure(upcase);
-// ==> Success('a')
-
-Failure('a').mapFailure(upcase);
-// ==> Failure('A')
-
{
- /*~*/
- Failure: function mapFailure(transformation) {
- assertFunction('Validation.Failure#mapFailure', transformation);
- return Failure(transformation(this.value));
- },
-
- /*~*/
- Success: function mapFailure(transformation) {
- assertFunction('Validation.Failure#mapFailure', transformation);
- return this;
- }
- }
Returns the value inside of the Validation structure, regardless of its state.
-Returns the value inside of the Validation structure, regardless of its state.
-const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').merge();
-// ==> 'a'
-
-Failure('a').merge();
-// ==> 'a'
-
merge() {
- return this.value;
- }
Constructs a Validation holding a Success value.
-Constructs a Validation holding a Success value.
-const Validation = require('folktale/data/validation');
-
-Validation.of(1);
-// ==> Validation.Success(1)
-
of(value) {
- return Success(value);
- }
Allows recovering from Failure values with a handler function.
-forall a, b, c:
- (Validation a b).((a) => Validation c b) => Validation c b
Allows recovering from Failure values with a handler function.
-const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').orElse(e => Success('b'));
-// ==> Success('a')
-
-Failure('a').orElse(e => Success('b'));
-// ==> Success('b')
-
-Failure('a').orElse(e => Failure('b'));
-// ==> Failure('b')
-
{
- /*~*/
- Failure: function orElse(handler) {
- assertFunction('Validation.Failure#orElse', handler);
- return handler(this.value);
- },
-
- /*~*/
- Success: function orElse(handler) {
- assertFunction('Validation.Success#orElse', handler);
- return this;
- }
- }
Inverts the status of a Validation, such that Failures become Successes, and vice-versa.
-forall a, b: (Validation a b).() => Validation b a
Inverts the status of a Validation, such that Failures become Successes, and vice-versa.
-const { Success, Failure } = require('folktale/data/validation');
-
-Success('a').swap();
-// ==> Failure('a')
-
-Failure('a').swap();
-// ==> Success('a')
-
{
- /*~*/
- Failure: function swap() {
- return Success(this.value);
- },
-
- /*~*/
- Success: function swap() {
- return Failure(this.value);
- }
- }
Transforms a Validation into a Maybe. Failure values are lost in the process.
-Transforms a Validation into a Maybe. Failure values are lost in the process.
-const { Success, Failure } = require('folktale/data/validation');
-const Maybe = require('folktale/data/maybe');
-
-Success('a').toMaybe();
-// ==> Maybe.Just('a')
-
-Failure('a').toMaybe();
-// ==> Maybe.Nothing()
-
toMaybe() {
- return require('folktale/data/conversions/validation-to-maybe')(this);
- }
Transforms a Validation into a Reseult.
-Transforms a Validation into a Reseult.
-Validation's Failure
s are mapped into Result's Error
s, and Validation's Success
es are mapped into Result's Ok
s.
const { Success, Failure } = requiure('folktale/data/validation');
-const Result = require('folktale/data/result');
-
-Success('a').toResult();
-// ==> Result.Ok('a')
-
-Failure('a').toResult();
-// ==> Result.Error('a')
-
toResult() {
- return require('folktale/data/conversions/validation-to-result')(this);
- }
Extracts the value from a Validation
structure.
forall a, b: (Validation a b).() => b :: throws TypeError
Extracts the value from a Validation
structure.
--WARNING
-
This method is partial, which means that it will only work forSuccess
-structures, not forFailure
structures. It's recommended to use.getOrElse()
-or.matchWith()
instead.
const { Failure, Success } = require('folktale/data/validation');
-
-Success('a').unsafeGet();
-// ==> 'a'
-
-
-try {
- Failure('a').unsafeGet();
- // TypeError: Can't extract the value of an Error
-} catch (e) {
- e instanceof TypeError; // ==> true
-}
-
{
- /*~*/
- Failure: function unsafeGet() {
- throw new TypeError(`Can't extract the value of a Failure.
-
- Failure does not contain a normal value - it contains an error.
- You might consider switching from Validation#get to Validation#getOrElse, or some other method
- that is not partial.
- `);
- },
-
- /*~*/
- Success: function unsafeGet() {
- return this.value;
- }
- }
The value contained in an Success instance of the Validation structure.
-The value contained in an Success instance of the Validation structure.
-This is usually used to destructure the instance in a .matchWith
call.
const Validation = require('folktale/data/validation');
-
-Validation.Success(1).matchWith({
- Success: ({ value }) => value, // equivalent to (x) => x.value
- Failure: ({ value }) => 'nothing'
-});
-// ==> 1
-
get value() {
- throw new TypeError('`value` can’t be accessed in an abstract instance of Validation.Success');
- }
Folktale is a standard library for functional programming in JavaScript.
-Folktale is a standard library for functional programming in JavaScript.
-Folktale is preferrably installed by npm
, but this guide will walk you through installing Folktale in platforms other than Node.js.
Answers to some common questions people have when using Folktale.
-Found an error in Folktale? Think the documentation is missing or confusing? Not sure how to report these? Read on.
-Think Folktale should do something it doesn't yet? Read on.
-Read this if you want to fix a bug in Folktale or implement a new feature.
-Found a typo in the documentation, or think that something is missing from it, and want to help fixing that? Read on.
-Folktale uses a mix of property-based and unit tests. Read this to understand how to add new tests, or update existing ones.
-Describes the coding style used by the Folktale project.
-Describes the design principles behind Folktale.
-Folktale uses GNU Make for development tooling. From -there you can compile the source code, run tests, perform cleanup routines, or -check your source code for style errors.
-Describes how Git commits are annotated.
-What all the folders and files in the Folktale repository are for.
-Describes how the issue/feature tracker is organised.
-A step-by-step guide to set up a development environment for working on Folktale.
-In order to concisely describe what to expect of a piece of functionality, Folktale uses its own type notation. This guide describes the notation used.
-{
- core: require('./core'),
- data: require('./data')
-}
Short answer: yes, but there are no type definitions for them currently, and some of the features in Folktale require more advanced type system concepts that they don't support.
-Short answer: yes, but there are no type definitions for them currently, and some of the features in Folktale require more advanced type system concepts that they don't support.
-It is possible to use Folktale with Flow and TypeScript, however some of the features Folktale relies on can't be expressed in those type systems, and so they must be described with the any
type. This is not as useful for static checking, and it might make using some of the features more annoying or more difficult.
Better support for some of the features that Folktale uses depends on the concept of Higher-Kinded Polymorphism (very roughly: types that generalise other types. Think of generics, but instead of generalising the x
in List<x>
, it generalises the List
and the x
in List<x>
). Right now, the status of supporting HKP in Flow is "maybe at some point in the future", meanwhile TypeScript's status is "we like the idea, but it's a lot of effort and low priority. We're accepting PRs, though", with some of the community trying to work something out. So, maybe in the future, but definitely not right now.
That said, basic support for TypeScript is planned for the initial 2.0 release. Right now you can use it if you explicitly declare the module to have the any
type (or rely on implicit any
, but that's even less ideal). Not great, but works. Support for Flow might come after that, but no guarantees.
Describes the coding style used by the Folktale project.
-Describes the coding style used by the Folktale project.
---TODO
-
This section is a stub and needs to be improved.
The ESLint rules will catch most of the style issues, so you might just want to
-run make lint
on your code. That said, there are a few idioms that aren't
-captured by ESLint rules:
Prefer const
for declaring names wherever possible. When a name or the
-value it refers to is mutated in the scope, use let
instead. In this
-sense, we use const
to indicate an immutable binding and value, and
-let
for everything else.
Prefer arrow functions whenever possible. Regular JavaScript functions have
-many unnecessary properties for functional programming in general, such as
-this
, super
and .prototype
. So, if you're not writing a method, where
-you'll want this
, it should be an arrow function.
Use Lisp/Haskell-alignment for conditionals when using ternaries. For -example:
-return argCount < arity ? curried(allArgs)
-: argCount === arity ? fn(...allArgs)
-: /* otherwise */ unrollInvoke(fn, arity, allArgs);
-
-The comment for the last else
case is not optional, but can be shortened
-to /* else */
for shorter conditions.
Read this if you want to fix a bug in Folktale or implement a new feature.
-Read this if you want to fix a bug in Folktale or implement a new feature.
-Before making your changes, you'll have to set up a development environment. You might also be interested in understanding how the repository is organised and how Git commits are annotated.
-Note that Folktale uses a common workflow where new development happens in topic branches, -and then a Pull Request is made in order to merge those branches back into the -master branch. GitHub has a -nice description of this workflow.
-All JavaScript files in the project should start with the following preamble:
-//----------------------------------------------------------------------
-//
-// This source file is part of the Folktale project.
-//
-// Licensed under MIT. See LICENCE for full licence information.
-// See CONTRIBUTORS for the list of contributors to the project.
-//
-//----------------------------------------------------------------------
-
-Note, again, that Folktale is licensed under MIT, and by contributing patches, -you agree to release your contribution under the MIT licence.
-If you're making or have made a contribution to the project, please add yourself
-to the CONTRIBUTORS
file if you haven't done so yet.
Whether you're fixing a bug or adding a new feature, the process is roughly the same. You'll want to create a new branch for your feature. This allows you to -work on more than one feature at a time, if necessary. Remember that working on -a patch for a software project involves a lot of communication, and you might -need to review your code a few times.
-Roughly the process follows these steps:
-make test-all
to ensure that all tests pass. This will run your tests on both Node and a PhantomJS (WebKit) instance. Compiling everything may take a while…make lint
to ensure that your code conforms with the coding style used by Folktale.master
branch.Folktale uses Meta:Magical for attaching documentation to objects, which can be -queried at runtime. There's a -comprehensive text on documenting objects with Meta:Magical, -so this section will only focus on the basics to get you started.
-In the source code, functions, methods, modules, and objects should be annotated with -Meta:Magical comments. -These comments should describe who authored that functionality, what is its stability, and its type. -When relevant, functions should also describe their complexity in Big O notation.
-Here's an example of documentation for the core/lambda/compose
function:
/*~
- * stability: stable
- * authors:
- * - "@robotlolita"
- * type: |
- * forall a, b, c: ((b) => c, (a) => b) => (a) => c
- */
-const compose = (f, g) => (value) => f(g(value))
-
-A documentation comment is a block comment whose first line contain only the ~
-character. Subsequent lines use *
as an indentation marker, meaning that all
-content before that, and at most one space after that is ignored.
Once an entity has been marked to be documented, you can follow the documentation guide to write documentation for it.
-Describes the design principles behind Folktale.
-Describes the design principles behind Folktale.
-The design of the Folktale library is guided by the following principles:
-Favour a heavier use of arrow functions over currying -— Curried functions can be composed in several ways. And this is a good -thing, because it means it's easier to create new functionality by combining -existing ones. However, in an untyped language, you have no way of -enforcing a correct composition of functions, so currying in JS tends to -lead to silent or late errors. Coupled with JavaScript's heavy use of -variadic functions, and the VM's unhelpful error reporting, these have the -potential for being very frustrating, especially for people new to FP.
-ES2015 lessens a bit the burden of being more explicit with your -composition, since arrow functions have less visual overhead, and can -enforce some compositional constraints syntactically. They also side-step -the problems with JavaScript's variadic functions to an extent.
-Provide separate free-method versions of functions -— JavaScript's syntax is limited, but some of the proposed additions to it -lead to programs that are easier to read. One of them is the -This-Binding syntax, which -allows using functions in infix position:
-```js -const _groupBy = require('folktale/core/iterator/group-by');
-function groupBy(f) { - return _groupBy(this, f); -}
-[1, 2, 3, 4, 5]::groupBy(isOdd)
-// => _groupBy([1, 2, 3, 4, 5], isOdd)
-
-// Some combinators may be provided specifically to use as infix
-list.sort(compare::on(first))
-// => list.sort(on(compare, first))
-```
-
Where possible, as long as laws aren't broken, reuse the language's native methods -— This makes it easier to combine Folktale with existing projects and -libraries, as they'd have similar expectations.
-Monolithic package over several micro-libraries -— There are plenty of advantages of micro-libraries. They are more modular, -since they have to work with less expectations; they are easier to replace; -we can make a better use of semantic versioning; and they are easier to -understand.
-But there are plenty of drawbacks to them as well:
-They're harder to use when you need to combine more than one -module. For example, if you want an Either module to be able to convert -its values to a Maybe structure, you need to provide the Either module -with the Maybe structure to use. This, while modular, adds some friction -for people to use these features, which goes against our aim to make -functional programming easier to newcomers.
-They're harder to maintain. Keeping modules, tooling, and issues in -sync between different modules takes much more effort, and it's easy to -miss things. When all of the modules are related anyway, you'll have to -keep them in sync all the time.
-They're less consistent. Because each module is independent, exposes -its own API, and evolves at its own pace, it's easy to get inconsistent -behaviour across them. This was the case with how methods worked in the -old versions of Maybe, Either, and Validation.
-Better support for interactive (REPL) development -— One of the long-term goals with Folktale is to have really good support -for interactive development in the REPL. The first step for that is to be -able to view documentation directly from the REPL, which is being done by -annotating objects with the -Meta:Magical library.
-As newer features get added to Meta:Magical, like searching functions by -approximate type signature, Folktale and its users will benefit from them -without having to do anything.
-Logically grouped, minimal modules
-— Each module should be entirely self-contained, and only provide the
-absolute minimum necessary for that particular functionality. Most of the
-time this will mean one function = one module
, but not always. See the
-folktale/core/adt
module, and the
-Siren's Platform Design document
-for examples.
There are two reasons behind this:
-Extracting a minimal application from a large framework/environment -(tree-shaking) requires full type inference in JavaScript, because -modules are first class, and you need to know which properties of which -objects are accessed where. For historical data on this, see the -report on Foundations of Object-Oriented Languages -and Aegesen at al's work on -type inference for Self.
-Because all modules are annotated, inline, for the Meta:Magical library, -even modules comprised of simple, one line functions end up being more -than 40 lines of code when you consider documentation.
-Modules are grouped in an hierarchy of categories
-— This is mostly to make using the libraries easier given the previous
-point. Modules that are higher in the hierarchy should re-export all
-features below it. This allows people to require a bag of things, like
-require('folktale/data/result')
, or a very specific functionality, when
-they are concerned about the resulting size of the application (if they are
-deploying to a Browser, for example), like
-require('folktale/data/result/from-nullable')
.
Folktale uses GNU Make for development tooling. From -there you can compile the source code, run tests, perform cleanup routines, or -check your source code for style errors.
-Folktale uses GNU Make for development tooling. From -there you can compile the source code, run tests, perform cleanup routines, or -check your source code for style errors.
-Note: before you can do anything in Folktale you must initialise the documentation tools:
-$ git submodule init
-$ git submmodule update
-$ make tools
-
-Running make
or make help
will show you all of the available development
-tasks.
Running make compile
will compile the source code using Babel.
-After this you can import the objects in the REPL and interact with them.
Running make compile-annotated
will compile the source code with the special documentation
-marks. You can either use make documentation
to generate HTML docs from that, or
-require('./documentation')
in a Node REPL to read the docs from there.
Running make test
will compile the project and run all of the tests. Running
-make test-all
will also test the examples in the documentation. You can run
-tests in a PhantomJS (a headless WebKit browser) using make test-browser
.
Running make lint
will check all of the source code for style
-inconsistencies. The coding style used by Folktale is described later in this
-document.
Finally, running make clean
will remove any compiled files from your working
-directory, but will keep all other files intact. You can always run make
-compile
again to re-build the project.
Short answer: yes. Folktale 1 implements fantasy-land@1.x, and Folktale 2 implements fantasy-land@1.x up to fantasy-land@3.x, wherever possible.
-Short answer: yes. Folktale 1 implements fantasy-land@1.x, and Folktale 2 implements fantasy-land@1.x up to fantasy-land@3.x, wherever possible.
-Refer to the table below for implemented algebras:
-Folktale 1 implements only the non-prefixed methods of Fantasy Land v0.x~1.x.
-- | Maybe | -Either | -Validation | -Task | -
---|---|---|---|---|
Setoid | -❌ | -❌ | -❌ | -🚫 | -
Semigroup | -❌ | -✅ | -❌ | -✅¹ | -
Monoid | -🚫² | -🚫² | -🚫² | -✅¹ | -
Functor | -✅ | -✅ | -✅ | -✅ | -
Contravariant | -🚫 | -🚫 | -🚫 | -🚫 | -
Apply | -✅ | -✅ | -✅ | -✅ | -
Applicative | -✅ | -✅ | -✅ | -✅3 | -
Alt | -❌ | -❌ | -❌ | -❌ | -
Plus | -❌ | -❌ | -❌ | -❌ | -
Alternative | -❌ | -❌ | -❌ | -❌ | -
Foldable | -❌ | -❌ | -❌ | -❌ | -
Traversable | -❌ | -❌ | -❌ | -❌ | -
Chain | -✅ | -✅ | -🚫⁴ | -✅ | -
ChainRec | -❌ | -❌ | -🚫⁴ | -❌ | -
Monad | -✅ | -✅ | -🚫⁴ | -✅ | -
Extend | -🚫⁵ | -🚫⁵ | -🚫⁵ | -🚫⁵ | -
Comonad | -🚫⁵ | -🚫⁵ | -🚫⁵ | -🚫⁵ | -
Bifunctor | -🚫⁶ | -✅ | -✅ | -✅ | -
Profunctor | -🚫 | -🚫 | -🚫 | -🚫 | -
Folktale 2 implements both unprefixed and prefixed methods, and thus supports Fantasy Land v0.x~3.x.
---NOTE
-
The structures implement the old version of.ap
(fn.ap(value)
), and the new version of."fantasy-land/ap"
(value['fantasy-land/ap'](fn)
). Fantasy Land actually made this breaking change without bumping the major version first. If some library expects the unprefixed method to implement the new argument order, things won't work nicely.
- | Maybe | -Result | -Validation | -Task | -Future | -
---|---|---|---|---|---|
Setoid | -✅ | -✅ | -✅ | -🚫 | -🚫 | -
Semigroup | -🔜 | -🔜 | -✅ | -❌ | -❌ | -
Monoid | -🚫² | -🚫² | -🚫² | -❌ | -❌ | -
Functor | -✅ | -✅ | -✅ | -✅ | -✅ | -
Contravariant | -🚫 | -🚫 | -🚫 | -🚫 | -🚫 | -
Apply | -✅ | -✅ | -✅ | -✅ | -✅ | -
Applicative | -✅ | -✅ | -✅ | -✅ | -✅ | -
Alt | -🔜 | -🔜 | -🔜 | -🔜 | -🔜 | -
Plus | -🔜 | -🔜 | -🔜 | -🔜 | -🔜 | -
Alternative | -🔜 | -🔜 | -🔜 | -🔜 | -🔜 | -
Foldable | -❌ | -❌ | -❌ | -❌ | -❌ | -
Traversable | -❌ | -❌ | -❌ | -❌ | -❌ | -
Chain | -✅ | -✅ | -🚫⁴ | -✅ | -✅ | -
ChainRec | -🔜 | -🔜 | -🚫⁴ | -🔜 | -🔜 | -
Monad | -✅ | -✅ | -🚫⁴ | -✅ | -✅ | -
Extend | -🚫⁵ | -🚫⁵ | -🚫⁵ | -🚫⁵ | -🚫⁵ | -
Comonad | -🚫⁵ | -🚫⁵ | -🚫⁵ | -🚫⁵ | -🚫⁵ | -
Bifunctor | -🚫⁶ | -✅ | -✅ | -✅ | -✅ | -
Profunctor | -🚫 | -🚫 | -🚫 | -🚫 | -🚫 | -
--NOTES
--
-- ✅: The algebra is implemented for this structure;
-- ❌: The algebra is not implemented for this structure;
-- 🚫: The algebra can't be implemented for this structure;
-- 🔜: The algebra will be implemented for this structure in the future.
-
--
-- ¹: The Task instance of Monoid is non-deterministic, and the equivalent of Promise.race.
-- ²: Implementing a generic Monoid would require return-type polymorphism. It's theoretically possible, but not practically possible (requires free monoids and late reifying). See https://eighty-twenty.org/2015/01/25/monads-in-dynamically-typed-languages for a detailed explanation.
-- ³: Resolves Tasks in parallel, so may be observably different than the Monad instance if the ordering of effects matters.
-- ⁴: See Why is there no
-.chain
/Monad for Validation? in this document.- ⁵: It's not possible to implement these without being partial, so we choose to not implement it.
-- ⁶: One side of the Maybe is nullary, and Bifunctor requires two unary functions.
-
Answers to some common questions people have when using Folktale.
-Answers to some common questions people have when using Folktale.
-Short answer: Folktale unrolls application of curried functions, so if you pass more arguments than what a function takes, that function might try passing some of those arguments to its return value.
-Short answer: the way Validation's .ap
method works makes it impossible to implement the Monad interface (.chain
). You might either want to use Either/Result, or rethink how you're approaching the problem.
Short answer: yes, but there are no type definitions for them currently, and some of the features in Folktale require more advanced type system concepts that they don't support.
-Short answer: yes. Folktale 1 implements fantasy-land@1.x, and Folktale 2 implements fantasy-land@1.x up to fantasy-land@3.x, wherever possible.
-Short answer: Folktale 1 packages (folktale/data.*
) are the latest stable release. Folktale 2 (origamitower/folktale
) is currently in alpha.
A stable version of Folktale 2 should be released somewhere from April to May of 2017, although that will depend on me having free time. You can check what's blocking the release on the v2.0.0 milestone on GitHub.
-Describes how Git commits are annotated.
-Describes how Git commits are annotated.
-Folktale uses .gitlabels to -tag commits so humans can better understand the scope of changes, and tools -can help visualising changes.
-Commits often follow this format:
-(<tags>) <summary>
-
-<body>
-
-<footer>
-
Where:
-tags
: The .gitlabels
tags defining the scope of changes. See the .gitlabels
file for documentation;summary
: A very short description of the change.body
: A detailed description of the change.footer
: contains information about issue references and breaking changes.All text should be formatted using Markdown, and use the active, present -tense. It's "changes", rather than "change" or "changed".
-For example, a commit that adds tests to the folktale/core/lambda
module would
-look like:
(test lambda) Adds tests for compose
-
-`compose` was the only function in the module that didn't have tests.
-This provides a few example based tests, and property-based tests for
-common mathematical laws expected of function composition, such as
-associativity.
-
-Fixes #12
-
Issues may be referenced through the body of the commit, where relevant, by
-providing (see #N)
, where N
is the number of the issue. For example:
This is a first step in providing better documentation tools (see #91),
-but does not implement type searching and type summaries.
-
Issues should be closed when the commit fully fixes the problem in that -issue. This is done by listing these issues in the footer, using -GitHub's issue keywords. For example:
-Fixes #12, fixes #43, and fixes #46
-
All breaking changes should be detailed in the footer of the commit message. It
-should contain BREAKING CHANGE:
followed by a short summary of what was
-broken, and a detailed justification of why it was broken, along with a
-migration path from the old feature to the new one. For example:
BREAKING CHANGE: makes `data` return an object with constructors,
-instead of the old interface.
-
-This makes creating new algebraic data structures a simpler task,
-and aligns better with the aim of making Folktale a welcoming
-library to people new to functional programming.
-
-Where one used to write:
-
- const List = data({
- Nil: [],
- Cons: ['value', 'rest']
- });
-
- const Nil = () => new List.Nil.constructor({});
- const Cons = (value, rest) => new List.Cons.constructor({ value, rest })
-
-One would write:
-
- const List = data({
- Nil: [],
- Cons: ['value', 'rest']
- });
-
- const { Nil, Cons } = List;
-
Short answer: Folktale 1 packages (folktale/data.*
) are the latest stable release. Folktale 2 (origamitower/folktale
) is currently in alpha.
Short answer: Folktale 1 packages (folktale/data.*
) are the latest stable release. Folktale 2 (origamitower/folktale
) is currently in alpha.
The Folktale organisation on GitHub hosts the libraries referred to as Folktale 1. These are split into many independent packages:
- -The packages above are the most recent stable releases of Folktale at this moment. The Folktale organisation also hosts many other experimental packages. Those are either unfinished, untested, or known to have implementation or design problems, and shouldn't be used in production.
-In an effort to improve the quality and coherence of Folktale packages, a new, monolithic package is being developed. That's referred to as Folktale 2, and lives in the origamitower/folktale repository. It explains the design principles behind the new library and why it was moved to a monolithic repository/package.
-At the moment, Folktale 2 is considered alpha, and should not be used in production. Aside from the lack of documentation, some of the APIs are still changing, and some features have not been implemented yet. API changes from Folktale 1 to 2 have also not been documented yet.
-Found a typo in the documentation, or think that something is missing from it, and want to help fixing that? Read on.
-Found a typo in the documentation, or think that something is missing from it, and want to help fixing that? Read on.
-Folktale uses a dialect of Markdown for documentation and some tools to process that. This dialect of Markdown lets one associate documentation with JavaScript objects, and then we can use that to show API documentation on the REPL or render it to HTML.
-These Markdown files live in the docs/source/
directory, separated by a language identifier (currently only English, en/
, is supported). You can edit these files in any text editor you like, but to test your changes you'll need to set up a development environment for Folktale.
To look at the HTML docs, run make documentation
at the root of the repository, and open docs/api/en/folktale.html
in your browser. To test the examples in the docs, run make test-documentation
.
To document a new entity, you create a .md
file inside the docs/source/<language>
folder, ideally replicating a similar hierarchy to the one found in the source code folder. This file should use the special @annotate: <JS expression>
line to describe which entity is being annotated. Inside that expression, the folktale
variable points to the root of the Folktale library.
The @annotate
line is followed by an YAML document that defines the metadata about that entity. The only required metadata right now is category
, which allows us to group methods and functions in the API reference. Finally, a ---
line separates the metadata from the actual documentation.
The documentation for an entity must contain at least a short summary of what the functionality is for, and an example of how to use it.
-For example, if documenting the compose
function (core/lambda/compose
), the file would look like:
```md
-Folktale uses a mix of property-based and unit tests. Read this to understand how to add new tests, or update existing ones.
-Folktale uses a mix of property-based and unit tests. Read this to understand how to add new tests, or update existing ones.
-Folktale uses Mocha and -JSVerify for tests. Property-based tests -are preferable, but sometimes example-based tests make more sense, Amanda -Laucher and Paul Snively have a very good -talk about when to use Property-based tests and when to use example-based tests, -which you can use as a basis to make this decision.
-Tests go in the proper <category>.<subcategory>.js
file in the test/specs-src/
folder
-for the category of the functionality you're writing tests for. For example,
-tests for the compose
function of core/lambda
would go in the
-test/specs-src/core.lambda.js
file. If the file already exists, you only need to add a new
-test definition to it. Otherwise, you'll need to create a new file.
Here's an example of how one would write tests for the compose
function, in a
-core.lambda.js
file:
// Import the jsverify library. This will be used to define the tests
-const { property } = require('jsverify');
-
-// Import the category we're testing (core.lambda). By convention, the
-// variable `_` is used to hold this object, since it has less visual
-// clutter.
-const _ = require('folktale').core.lambda;
-
-// Define the test case. Mocha adds the `describe` function to define
-// a test suite. You should create a suite for the category, and a
-// suite for the function being tested inside of that:
-describe('Core.Lambda', () => {
- describe('compose', () => {
- // Finally, we define our test, using JSVerify's `property`
- property('Associativity', 'nat', (a) => {
- const f = (x) => x - 1;
- const g = (x) => x * 2;
- const h = (x) => x / 3;
-
- return _.compose(f, _.compose(g, h))(a)
- === _.compose(_.compose(f, g), h)(a)
- })
- })
-})
-
-With property based tests, JSVerify generates random data of the type described -in the property and feeds it into the function. When an error occurs, it tries -to find the smallest value that still reproduces the error and reports that to -you, so you can try to debug it.
-Sometimes it makes more sense to write an example-based test than a
-property-based one. For these, instead of the property
function from JSVerify,
-test cases are defined with the it
function from Mocha, and assertions use
-Node's native assert
module:
const assert = require('assert');
-const _ = require('folktale').core.lambda;
-
-describe('Core.Lambda', _ => {
- describe('compose', _ => {
- it('Invokes functions from right to left', _ => {
- const f = (x) => x - 1;
- const g = (x) => x * 2;
-
- assert.equal(_.compose(f, g)(2), 3);
- })
- })
-})
-
-To run tests, you can use make test
from the root of the project.
Folktale is preferrably installed by npm
, but this guide will walk you through installing Folktale in platforms other than Node.js.
Folktale is preferrably installed by npm
, but this guide will walk you through installing Folktale in platforms other than Node.js.
The recommended way of getting Folktale is through npm. If you don't have Node installed yet, you should download a binary from the official website or use an installer like nvm.
---NOTE
-
Folktale requires Node 6.x+ for its development tools, but you can use a prebuilt version of Folktale in Node 4.x+.
To install Folktale using npm, run the following in your command line:
-$ npm install folktale
-
Node.js has native support for the CommonJS module system, which Folktale uses, thus in order to use Folktale you just require
it:
const folktale = require('folktale');
-
-folktale.core.lambda.identity(1); // ==> 1
-
-Like Node.js, Electron and nw.js have native support for the CommonJS module system, so you load Folktale using require
:
const folktale = require('folktale');
-
-folktale.core.lambda.identity(1); // ==> 1
-
-Browsers don't have native support for CommonJS modules. We still recommend using a module system (like Browserify or WebPack) and bundling your modules to distribute your application. This allows you to only load the parts of Folktale that you use, reducing the amount of data you have to send to your users.
-First install Browserify from npm (you should have a package.json describing your application's dependencies):
-$ npm install browserify --save-dev
-
Then install Folktale through npm as well:
-$ npm install folktale --save
-
Ideally, require only the Folktale modules you'll be using. This helps keeping the overall size smaller. For example, if you're using only the Maybe
and compose
functions, don't load the library's entry-point, just those modules:
const Maybe = require('folktale/data/maybe');
-const compose = require('folktale/core/lambda/compose');
-
-const inc = (x) => x + 1;
-const double = (x) => x * 2;
-
-Maybe.Just(1).map(compose(inc, double));
-// ==> Maybe.Just(4)
-
-To compile your application, run browserify
on your entry-point module (you run this from the root of your project, where your package.json
is located at):
$ ./node_modules/.bin/browserify index.js > my-app.js
-
Finally, load my-app.js
in your webpage. This file will contain all of the modules you've require
d in your index.js
file:
<!DOCTYPE html>
-<html>
- <head>(...)</head>
- <body>
- (...)
- <script src="/path/to/my-app.js"></script>
- </body>
-</html>
-
-For more information about Browserify, check Browserify's website.
-First install WebPack from npm (you should have a package.json describing your application's dependencies):
-$ npm install webpack --save-dev
-
Then install Folktale through npm as well:
-$ npm install folktale --save
-
Ideally, require only the Folktale modules you'll be using. This helps keeping the overall size smaller. For example, if you're using only the Maybe
and compose
functions, don't load the library's entry-point, just those modules:
const Maybe = require('folktale/data/maybe');
-const compose = require('folktale/core/lambda/compose');
-
-const inc = (x) => x + 1;
-const double = (x) => x * 2;
-
-Maybe.Just(1).map(compose(inc, double));
-// ==> Maybe.Just(4)
-
-Create a webpack.config.js
in your project's root directory, containing instructions for how WebPack should build your application:
module.exports = {
- entry: './index.js',
- output: {
- filename: 'my-app.js'
- }
-};
-
-Finally, load my-app.js
in your webpage. This file will contain all of the modules you've require
d in your index.js
file:
<!DOCTYPE html>
-<html>
- <head>(...)</head>
- <body>
- (...)
- <script src="/path/to/my-app.js"></script>
- </body>
-</html>
-
-For more information about WebPack, check WebPack's website.
-While the recommended way of using Folktale is with a module system, it's possible to use it without one as well. The drawback of not using a module system is that your website will have to ship the entire Folktale library to your users, even if you don't use all of its features.
-To use a prebuilt version, first, download one of the prebuilt releases on GitHub. Unpack the distribution file and add the dist/folktale.min.js
or dist/folktale.js
file to your website. Reference this file in your HTML like any other JavaScript file:
<!DOCTYPE html>
-<html>
- <head>(...)</head>
- <body>
- (...)
- <script src="/path/to/folktale.min.js"></script>
- </body>
-</html>
-
-In your JavaScript code, the Folktale library will be available through the global variable folktale
, unless you're using a CommonJS or AMD module system in your webpage:
folktale.core.lambda.identity(1);
-// ==> 1
-
---NOTE: -If you're using a module system in your webpage (for example, AMD in Dojo or Require.js), then Folktale will be available through that module system under
-folktale
.
What all the folders and files in the Folktale repository are for.
-What all the folders and files in the Folktale repository are for.
-The Folktale repository is organised as follows:
-/folktale
— Root of the project.
Configuration files:
-.babelrc
- — Configurations for the Babel compiler..eslintrc.json
- — Linting rules for ESLint.package.json
-— Meta-data for the npm package (version, description, dependencies, etc)..travis.yml
-— Configuration for running automated tests in the Travis CI server..eslintignore
-— A set of glob patterns for files that shouldn't be linted..npmignore
-— A set of glob patterns for files that shouldn't be included in npm's packages..gitignore
-— A set of glob patterns for files that shouldn't be committed to the repository.Documentation files:
-
- - `CODE_OF_CONDUCT.md`
- — Social rules that all people in the community are expected to follow.
- - `CONTRIBUTING.md`
- — This file. A guide for people who want to contribute to Folktale.
- - `README.md`
- — Tells people what Folktale is for, how to install it, how to begin using it, where to get support, and other important information.
- - `LICENCE`
- — The licence under which Folktale is released (MIT).
- - `CONTRIBUTORS`
- — A list of all people who have contributed to Folktale.
- - `CHANGELOG.md`
- — A chronological list of changes made to the project, grouped by versions.
- - `docs/source`
- — Special markdown/YAML documentation files, separated by language.
-
-
-
-Developer tooling:
-
- - `Makefile`
- — Common development tasks.
- - `tools`
- — Custom tooling written for Folktale.
- - `metamagical/`
- — A set of tools to support documentation and tests.
-
-
-Source files:
-
- - `src/`
- — The implementation of Folktale libraries, in JavaScript.
- - `test/`
- — Unit tests for Folktale, using Mocha.
-
-
-Auto-generated files:
-
- - `index.js`, `core/`, `data/`, `helpers`
- — Generated from the source code in `src/` through `make compile`. These are the library's code that gets loaded by requiring the package.
-
- - `annotated/`
- — Generated from the source code in `src/` through `make compile-annotated`. Contains special annotations for the documentation tooling.
-
- - `dist/`
- — Generated with `make bundle`. Distribution files for browser environments, compiled with Browserify.
-
- - `docs/build/`
- — Generated from documentation files in `docs/source/` through `make compile-documentation`. Used to generate documentation and test examples in the documentation.
-
- - `docs/api/`
- — Static HTML documentation, generated through `make documentation`.
-
- - `node_modules/`
- — Dependencies installed by npm.
-
- - `test/helpers/` and `test/specs/`
- — Generated from their `*-src/` folder through `make compile-test`. These are the test files that get actually ran by the testing tool.
-
- - `test/browser/browser-tests.js`
- — Generated from `test.js` through `make compile-test`. A bundle of the entire library and test files that can be loaded in a browser.
-
- - `releases/`
- — Generated through `publish`. Contains distribution packages.
-
The source tree is organised as a hierarchy of objects. At each level you have
-an index.js
file that re-exports functions and objects of that level. This
-allows people to import bags of features, or a particular feature separately.
core
— Provides the baseline for all features in Folktale, by filling the
-gaps of the functionality needed for the standard data structures. The
-purpose of core
is to just provide enough functionality to make other
-categories possible. The focus is on better data structures and composing
-data structures.
lambda
— Common combinators for functions.adt
— Support for algebraic data types.object
— Support working with objects-as-records/dictionaries.string
— Common operations on strings.comparison
— Comparisons between built-in JS values.equality
— Equality between built-in JS values.inspecting
— Textual representations of built-in JS values.contracts
— First and higher-order run-time contracts.iterables
— Lazy sequences using JS's iterable protocol.data
— Provides implementations of data structures that are common in
-functional programming.
conversions
— Functions to convert between data types.maybe
— A structure for modelling the presence or absence of a value.result
— A structure for modelling a tagged disjunction of two values.validation
— A structure similar to Result, but designed for validations and supporting error aggregation.task
— A structure for modelling a potentially asynchronous action.future
— A structure for modelling an eventual value. Used by task
.Folktale uses Mocha for testing, which expects tests to go in the test/specs-src/
-folder. Right now all test files are placed directly in that folder.
Test files are named <category>.<subcategory>.js
(e.g.: core.lambda.es6
),
-and provide tests for all functionality defined in that category. When compiled,
-this generates <category>.<subcategory>.js
files in the test/specs/
folder.
Most of the source code is compiled through Babel before
-running. This generates what we call "build artefacts" in the project's working
-directory. While these files are ignored by Git (with .gitignore
rules), they
-might still show up in your text editor and be somewhat confusing.
When source code is compiled (from src/
), the same structure you see under
-src/
is replicated under the root of the project's working directory. So, for
-example, after compiling you'd have an index.js
at the root, then an
-core/lambda/identity.js
starting at the root, and so on, and so forth.
When test cases are compiled (from test/specs-src/
and test/helpers-src/
), they
-generate files with the same name in the test/specs/
(and test/helpers/
) folder.
Documentation files, in docs/source/
are compiled to regular JavaScript files
-that add special annotations to runtime objects. When compiled, the structure in
-docs/source/
is replicated in docs/build/
.
You can always remove these files by running make clean
from the root of the
-project.
Describes how the issue/feature tracker is organised.
-Describes how the issue/feature tracker is organised.
-Ideas and bugs live in the Github Issue tracker, and can be -visualised as a Kanban board in Waffle.io. -If you're not sure where to start, there's a selection of good first -issues -which you may want to try.
-All tasks are categorised in terms of the kind of work, its scope, its -effort, its status, and its priority.
-Here's what the labels mean:
-c:*
— The category labels classify the scope of the issue.e:*
— The effort labels define how much effort resolving a particular
-issue is likely to take.p:*
— The priority labels define how urgent resolving a particular
-issue is.Kind labels:
-k:Enhancement
— The task refers to something that improves the Folktale
-library, such as adding new features or making existing features easier
-to use.k:Error
— The task refers to a problem with the Folktale library. We
-consider problems not only bugs, but things like missing documentation and
-confusing behaviour.k:Optimisation
— The task doesn't change anything about the behaviour of
-Folktale, but it improves the performance of some of its components.k:Discussion
— Discussions about features that don't fit any of the
-kinds above.Status labels:
-s:Duplicate
— The issue is already covered by a separate issue, and
-should be discussed there.s:Invalid
— The issue is not a problem in Folktale.s:Won't Fix
— The issue does not fit Folktale's philosophy.All triaged issues will at least contain a category
label and a kind
label.
Found an error in Folktale? Think the documentation is missing or confusing? Not sure how to report these? Read on.
-Found an error in Folktale? Think the documentation is missing or confusing? Not sure how to report these? Read on.
-Sometimes things don't work as well as they should, or we can't make them work -the way we want, and we get frustrated by that. Both of those are situations -were we encourage you to open an issue in the GitHub issue tracker so -we can help you.
-Opening a ticket is primarily a way of starting a discussion on a particular -problem. In some cases (for example, if you're not sure what the behaviour of -something should be), you might consider sticking around the -Gitter channel and talking to people about it before opening an issue, -but don't feel like you need to. Both the Gitter channel and the issue tracker -are places for discussion.
-Once you've decided to open an issue, please follow these guidelines to ensure -that the conversation goes as smoothly as possible, so we can help you faster:
-Search the issue tracker to see if someone has already reported the problem -before. This helps keep the conversation in just one place, where possible, -and benefits more people.
-Try to create a minimal test cases that reproduces the problem. These make -it easier for you and for us to understand what's really happening, and -what's causing the problem.
-Try to provide as much information about the problem as possible, and be -prepared to follow up if we ask you for clarifications on certain parts of -it in order to understand the problem better.
-If you're not sure how to format your bug report, here's a template you can follow:
-(A short description of the problem)
-
-### Steps to reproduce
-
-(Provide, where possible, a clear set of instructions to replicate the
-problem. If you can, include a minimal test case that reproduces the problem and
-that we can run)
-
-#### Expected behaviour
-
-(What you expected to happen)
-
-#### Observed behaviour
-
-(What happened instead)
-
-### Environment
-
-(Describe the environment where the problem happens. This usually includes:)
-
- - OS
- - JavaScript VM
- - Folktale version
-
-### Additional information
-
-(Anything else you feel relevant to the issue)
-
-Here's an example that uses the template above:
-`constant` throws `"0 is not a function"` error when used with `Array.map`.
-
-### Steps to reproduce
-
- const constant = require('folktale/core/lambda/constant');
- [1, 2, 3].map(constant(0));
- // => Uncaught TypeError: 0 is not a function
-
-#### Expected behaviour
-
-I expected the code above to result in the array `[0, 0, 0]`.
-
-#### Observed behaviour
-
-The code throws a type error saying `0 is not a function`.
-
-### Environment
-
- - Folktale 2.0.0
- - Node 4.2.4
-
-Of course, you're not required to follow this exact template. It's there for you -to use if you want to, but it doesn't fit all possible tickets. If you're -reporting an issue with the documentation, for example, your ticket should focus -on what you feel the problem with the documentation is. Maybe the phrasing is -hard to follow, maybe there are grammatical mistakes, maybe it assumes knowledge -of some concept that's not explained anywhere else, maybe it's outdated.
-Below is an example of how one could go about reporting an issue with the -documentation:
-The documentation for `Data.Maybe` assumes familiarity with concepts such as
-`Monad` and `Functor` that are not explained anywhere. Maybe it's immediately
-obvious to those people how and when they would choose to use this data
-structure, but I still don't know why I would use it after reading the
-documentation.
-
-I should note that I am a beginner in functional programming, and I think the
-documentation right now could be more clear on its motivations. More concrete
-examples would help.
-
-Think Folktale should do something it doesn't yet? Read on.
-Think Folktale should do something it doesn't yet? Read on.
-Folktale doesn't do everything, so more often than not you'll find something -that you wish Folktale supported but that isn't implemented yet. Maybe the -thought has never crossed our minds. This is a great opportunity to -tell us about that feature you want.
-Before you open an issue in the GitHub tracker, however, it's important to -consider whether the feature you're proposing really belongs in Folktale. Here -are a few guidelines for things that would be a good fit for Folktale:
-The feature is an algebraic data structure, with well-defined -mathematical laws governing its behaviour. Folktale is a functional -library greatly inspired by Abstract Algebra and Category Theory, and it -avoids law-less functionality where possible in order to make abstractions -safer and more composable.
-The feature is an utility function that covers a recurrent -pattern. Sometimes the abstractions provided in Folktale are general, but -still require some boilerplate when doing common things with it. Utility -functions that avoid this boilerplate by providing the feature out of the -box are a great fit.
-If you're in doubt on whether the feature you have in mind fits Folktale or not, -open an issue anyway, that way we can start a discussion about it. You can also -ask in the Gitter channel.
-When you open an issue, it's important to describe clearly what the missing -feature is, and why it is important to have this feature. Examples are a great -way to present the feature and show how it would look like when implemented.
-If you're not sure about how to format it, you can follow this template:
-(A short description of what the functionality is)
-
- (An example showing how one would use the functionality)
-
-### Why?
-
-(Describe why the functionality is important to have in Folktale)
-
-### Additional resources
-
-(If there are any materials relevant to the suggestion — a paper, a talk on the
-subject, etc. you can provide them here)
-
-Here's an example of a feature request using this template:
-I would like to have a `.concat` method for `Data.Validation`.
-
- const v1 = Validation.Failure(1);
- const v2 = Validation.Failure("error");
- v1.concat(v2)
- // => Validation.Failure([1, "error"])
-
-
-### Why?
-
-Validations can already aggregate failures through the applicative instance, but
-this requires lifting all failures into a semigroup (like Array), and
-constructing a curried function with N arguments to handle the Success case. So,
-while one can achieve the same as the example above by using `.ap`, it requires
-much more effort.
-
-We often have to aggregate unrelated failures. Some of these failures might not
-have been lifted into a semigroup yet. So we'd like a simpler function that only
-provides the failure aggregation part.
-
-A step-by-step guide to set up a development environment for working on Folktale.
-A step-by-step guide to set up a development environment for working on Folktale.
-Configuring a development environment for Folktale is currently a little bit -more complicated than it should in some systems, but this guide should help -you achieve that anyway.
-First of all, you'll have to make sure you have Git -installed. Folktale uses Git repositories, and while while you can use things -like hg-git to interact with the repository from -Mercurial, we can't support that.
-The Git website has a list of GUI clients for Git, if you're not comfortable -using it from the command line.
-Next you'll need to install Node.js. The website -provides binaries for all supported platforms. All newer versions of Node come -with npm as well, which you'll use to pull the dependencies of the project.
-Note that Folktale requires at least Node 4.x, so if you have an older version -you'll need to upgrade.
-Folktale uses Make as its build system, and also relies on some common *NIX
-tools, like find
. Because of this, it might be harder to configure a
-development environment for Windows.
Below are instructions to install Make on common systems:
---TODO
-
Provide instructions for OS/X and *BSD systems.
$ apt-get install build-essential
-
$ pacman -S base-devel
-
$ yum install "Development Tools"
-
GetGnuWin32-*.exe
;download.bat
from the folder you extracted the components to;install.bat
from that folder.The first thing you'll want to do is -forking Folktale. This means -you get a clone of the entire project that you have rights to change as you -wish. Once you have forked Folktale, you can clone your fork:
-$ git clone https://github.com/YOUR_GITHUB_USERNAME/folktale.git
-
-This will create a folktale
folder containing all of the code in the
-project. The first thing you need to do is move in that folder and install all
-of the dependencies and development tools that Folktale uses:
$ cd folktale
-$ npm install
-# Some of the tools are in a git subrepo
-$ git submodule init
-$ git submodule update
-$ make tools
-
-In order to concisely describe what to expect of a piece of functionality, Folktale uses its own type notation. This guide describes the notation used.
-In order to concisely describe what to expect of a piece of functionality, Folktale uses its own type notation. This guide describes the notation used.
-JavaScript is a dynamically typed language, meaning that we generally have no way of adding type annotation to functionality, and there's also no way of checking them. Tools like TypeScript and Flow help by adding their own type system, so that information can be provided and checked, but ultimately they're not powerful enough to describe some of the features Folktale uses.
-We still think that types, even when not checked, are a powerful tool for concisely describing what one can expect of a piece of functionality. It answer questions such as "what does this piece of functionality expect me to provide?", and "what can I get out of this piece of functionality?", but also things like "can I combine this and that piece of functionality?", "is there anything I have to watch out for when using this functionality?", and others.
-Since those are all valuable questions to answer, every functionality in Folktale describes its "type", and this document describes that particular notation.
-The system Folktale uses is a structural type system. That means that two types are considered compatible if they have the same structure (roughly, if two objects have the same properties, they're "compatible"). This closely models Object-Oriented and dynamic language idioms, but is also commonly used in Functional languages. Folktale uses some OO concepts and some FP concepts, so this kind of system fits.
-The document uses the term x matches y
to mean that the object or type y
is compatible with the object or type x
. So, if we say that "Boolean matches a JS primitive boolean", we're pretty much saying that values like true
or false
are compatible with the type Boolean
.
In a type system, values may only occur where a compatible type is specified. That is, if we have a function like:
-/*~ type: (Number, Number) => Number */
-function add(a, b) {
- return a + b;
-}
-
-Then the type annotation describes that both of its parameters must be compatible with a JavaScript number, and the value it returns will also be compatible with a number. So:
-// OK:
-add(1, 2); // => 3 (all numbers)
-
-// Not OK:
-add(1, "2"); // "2" is not compatible with Number
-
-A primitive type is the most basic type in the system. Each of the following types is unique, and reflects JavaScript primitive values:
-Undefined
— matches the value undefined
Null
— matches the value null
Number
— matches any primitive numeric value (e.g.: 1
)String
— matches any primitive textual value (e.g.: "foo"
)Boolean
— matches any primitive boolean value (e.g.: true
)Any
typeThe Any
type is a special type that all JavaScript values are compatible with.
type
- Any
-
-// Matches the type: EVERYTHING
-1;
-'foo';
-null;
-{ a: 1 };
-...
-
-A tuple type is a sequence of types with a fixed size. Think of it as an array that is always guaranteed to have the same number of elements, and in the same order. In order to describe a tuple type we use the ,
operator:
type
- Number, String
-
-// Matches the type
-[1, "hello"];
-
-// Does not match the type
-[1];
-["hello", 1];
-
-A record type is a collection of key/type pairs that is matched substructurally. That is, objects are considered compatible with the type if they contain at least those keys, and whose values are compatible with the type defined for it.
-For example:
-type
- { x: Number, y: Number }
-
-// Matches the type
-{ x: 1, y: 2 };
-{ x: 1, y: 2, z: 3 };
-
-// Does not match the type
-{ x: '1', y: 2 }; // `x` is a String
-{ x: 1, z: 3 }; // missing `y`
-
-A function type describes the input and output of a function, alongside with its effects (if any).
-In the simplest form, the type of a function describes the types of its parameters, and the type of its return value:
-// A function with no arguments
-type
- () => Number
-
-// Matches the type
-() => 1;
-
-// Does not match the type
-(a) => 1; // has one parameter
-() => '1'; // returns a string
-
-
-// A function with some arguments
-type
- (Number, Number) => Number
-
-// Matches the type
-(a, b) => a + b;
-
-// Does not match the type
-(a, b, c) => a + b + c; // has three parameters
-(a) => a; // has one parameter
-(a, ...b) => a; // has a variadic parameter
-(a, b) => a.toString(); // returns a string
-
-Sometimes functions in JavaScript take an arbitrary number of arguments. These functions are called "variadic", and they are explicitly marked in the type as such with the ...
operator:
type
- (Number, ...String) => String
-
-// Matches the type
-(chars, ...texts) => texts.map(x => x.slice(0, chars)).join('');
-(chars, texts) => '';
-
-// Does not match the type
-(chars) => chars; // has one parameter
-(chars, ...texts) => 1; // returns a number
-
-Sometimes JavaScript functions do something outside of just taking in and returning values, and it might be nice to capture those. These things are commonly called "effects", and to capture them we have what's called an "effects list", which describes the effects a function has. Examples of effects are things like throwing an error, or mutating an object.
-For example:
-type
- (Number) => Number :: throws RangeError
-
-// Matches the type
-(n) => {
- if (n < 1) {
- throw new RangeError(`Expected a number >= 1, got ${n}`);
- }
- return 5 / n;
-};
-
-
-// Does not match the type
-(n) => {
- if (n < 1) {
- throw new TypeError(`Invalid value: ${n}`);
- }
- return 3;
-}; // throws a TypeError, not a RangeError
-
-
-(n) => 3; // does not throw
-
-Effects are discussed in more details later in this document.
-Finally, a function type may accept a special parameter, accessible as this
inside of that function. This particular object-oriented feature is captured with the .
operator:
type
- ({ name: String }).() => { name: String }
-
-// Matches the type
-function () {
- return { name: this.name.toUpperCase() };
-}
-
-function () {
- this.name = this.name.toUpperCase();
- return this;
-}
-
-// Does not match the type
-function (person) {
- return { name: person.name.toUpperCase() };
-} // uses a positional parameter, not a this parameter
-
-function () {
- this.name = this.name.toUpperCase();
-} // does not return an object with a 'name' field
-
-An union type describes something that matches any of the described types. It uses the or
operator:
type
- String or Null
-
-// Matches the type
-'foo';
-null;
-
-// Does not match the type
-1;
-false;
-undefined;
-
-An intersection type describes something that has many different characteristics at the same time. A value still must possess all of these characteristics in order to be compatible with the type. Intersections use the and
operator.
Some types are nonsensical with intersections, for example String and Null
describes a value that is, at the same time, null
and a String. Such value does not exist in JavaScript.
On the other hand, intersection types are useful for describing function types where they may behave differently depending on the parameters given to them. For example:
-type
- (Number) => Number
-and (String) => String
-
-// Matches the type
-(value) => {
- if (typeof value === 'number') {
- return value + 1;
- } else {
- return value + '!';
- }
-};
-
-(value) => value; // supports at least the two cases
-
-
-// Does not match the type
-(value) => value + 1; // only supports (Number) => Number
-(value) => value + '!'; // only supports (String) => String
-
-A type alias may be defined in two ways. First, any type may be given a name by the type
keyword:
type Point2d = { x: Number, y: Number }
-
-The name on the left side of the =
and the type on the right side are exact the same, but naming more complex types helps with making some type expressions easier to understand, by reducing the amount of information one has to understand at once.
The other way of providing a type alias is with the :
operator in a type expression:
// This:
-(a: Number, a) => a
-
-// Is the same as:
-(Number, Number) => Number
-
-With record types, an alias is already created for each field, but you may provide an additional alias:
-// This:
-{ x: (the_x: Number), y: x, z: the_x }
-
-// Is the same as:
-{ x: Number, y: Number, z: Number }
-
-Sometimes a functionality uses an arbitrary type, and we want to make sure that this arbitrary type is the same everywhere. For example, if we write:
-type
- (Any) => Any
-
-Then (n) => n
is compatible with it, but so is (n) => 1
. There's no requirement that the type of the input be the exact same as the type of the output. In order to fix that, we can use type variables:
type
- forall a: (a) => a
-
-// Matches the type
-(n) => n
-
-// Does not match the type
-(n) => n + n; // `a` is not any known type, we can't use it
-(n) => 1; // 1 is not compatible with abstract type `a`
-
-Note that now we introduce a type variable a
with forall
. More variables can be introduced by separating them with commas. A type variable is an abstract type, so it only matches itself, and there's no way of constructing a value of such type, or operating on such type. The only possible operations are receiving such value as a parameter, and passing such value as an argument.
Some types (like Array
) contain other types, and ideally we should be able to specify which types they contain. This uses the type variables described early:
type Tuple a b = (a, b)
-
-[1, "foo"]; // Matches "Tuple Number String"
-["foo", "foo"]; // Matches "Tuple String String"
-
-In order to use these types, we just juxtapose the types that will replace the variables to the type name. An array, for example, is a container of elements of type a
, so:
type
- Array Number
-
-// Matches the type
-[1, 2, 3];
-[1];
-[];
-
-// Does not match the type
-[1, '2', 3]; // Number or String
-['2']; // String
-
-A type constraint restricts an existing type in some way. Commonly this is used to specify some characteristics for abstract types (see the section on Type Parameters), so people know what they can do with those values.
-Constraints are defined in the where
clause. Currently, the only constraint supported by this notation is the A is B
, which restricts A
to objects that also implement the type described by B
:
type Semigroup a = {
- concat: (a).(a) => a
-}
-
-type
- forall S, a: (S a).(S a) => S a
- where S is Semigroup
-
-// Matches the type
-function concat(a, b) {
- return a.concat(b);
-}
-
-// -- Primitives
-type Null // matches null
-type Undefined // matches undefined
-type String // matches 'string'
-type Boolean // matches true
-type Number // matches 1
-
-type None = Null or Undefined
-type Void = Undefined
-
-
-// -- Built-ins
-type RegExp
-type Symbol
-type Date
-type Int8Array
-type Uint8Array
-type Uint8ClampedArray
-type Int16Array
-type Uint16Array
-type Int32Array
-type Uint32Array
-type Float32Array
-type Float64Array
-type ArrayBuffer
-type DataView
-type Proxy
-
-type Error
-type EvalError <: Error
-type InternalError <: Error
-type RangeError <: Error
-type ReferenceError <: Error
-type SyntaxError <: Error
-type TypeError <: Error
-type URIError <: Error
-
-type Function = (...Any) => Any
-
-
-// -- Parameterised types
-type Object values
-type Array elements
-type Map keys values
-type Set values
-type WeakMap keys values
-type WeakSet keys values
-type Promise value error
-type Generator value
-
-
-// -- Effects
-effect throws types
-effect mutates types
-effect io
-
-A stable version of Folktale 2 should be released somewhere from April to May of 2017, although that will depend on me having free time. You can check what's blocking the release on the v2.0.0 milestone on GitHub.
-A stable version of Folktale 2 should be released somewhere from April to May of 2017, although that will depend on me having free time. You can check what's blocking the release on the v2.0.0 milestone on GitHub.
-Short answer: Folktale unrolls application of curried functions, so if you pass more arguments than what a function takes, that function might try passing some of those arguments to its return value.
-Short answer: Folktale unrolls application of curried functions, so if you pass more arguments than what a function takes, that function might try passing some of those arguments to its return value.
-JavaScript makes heavy use of variadic functions (functions that may receive a varying number of arguments, rather than a fixed one). Meanwhile, functional programming makes heavy use of a technique called currying. These two are largely incompatible, as currying relies on functions taking exactly one argument, and returning either the result of the computation (all dependencies have been gathered), or a new curried function to gather more arguments.
-Both Folktale 1 and Folktale 2 use currying in its functions. Due to the problems with variadic functions, currying is avoided by default in Folktale 2, but you may still run into them. The core of the issue lies within the effort Folktale makes to allow composing curried functions arbitrarily, through unrolling function application. This is done so curried functions may interact seamlessly with JavaScript's regular functions that may know nothing about currying:
-// math3 takes exactly 3 arguments, 1 at a time
-const math3 = curry(3, (a, b, c) => a - b + c);
-
-math3(4)(2)(1); // ==> 3
-
-// flip knows nothing about currying (it passes more than one argument)
-const flip = (f) => (a, b) => f(b, a);
-
-flip(math3(4))(1, 2); // ==> math3(4)(2, 1)
-
-Now, if Folktale forced curried functions to take exactly one argument at a time, then math3(4)(2, 1)
would ignore the second argument, and thus instead of the expected result, 3
, you'd get back a function that expects one more argument. This is awkward in JavaScript, so instead Folktale's curry creates functions that unroll their application. That is:
math3(4, 2, 1)
-=== math3(4, 2)(1) or math3(4)(2, 1)
-=== math3(4)(2)(1)
-
-It takes those many arguments and applies them one by one, until they're all passed to the functions your curried one may return. The new docs have a more detailed description of how this works. This means that our flip(maths(4))(1, 2)
works, even though flip
was not written for curried functions. That's great.
But what happens when we provide more arguments than what a curried function can take? Well, it depends. If you're using Folktale 1's core.lambda/curry
, then those additional arguments are passed down to the resulting function regardless:
flip(math3(4))(1, 2)
-// ==> math3(4)(2, 1)
-// ==> math3(4)(2)(1)
-// ==> 3
-
-flip(math3(4, 2))(1, 3)
-// ==> math3(4, 2)(1, 3)
-// ==> math3(4)(2)(1)(3)
-// ==> 3(3)
-// ==> TypeError: (3).apply(...) is not a function
-
-That's… not great. This happens quite often in JavaScript because most functions are variadic, so JavaScript APIs just pass a bunch of arguments that your function can ignore by simply not declaring them in its signature. Array#map
is a good example:
[1, 2, 3].map((element, index, array) => element + 1);
-// ==> [2, 3, 4]
-
-// This is also fine:
-[1, 2, 3].map((element) => element + 1);
-// ==> [2, 3, 4]
-
-.map
is still passing 3 arguments to the second function, so the number of arguments passed is still 3 even if the function declares no intention of accessing the other 2. Folktale 2 mitigates this a bit. The application is only unrolled if the resulting function has been marked as a Folktale curried function. This way:
flip(math3(4))(1, 2)
-// ==> math3(4)(2, 1)
-// ==> math3(4)(2)(1)
-// ==> 3
-
-flip(math3(4, 2))(1, 3)
-// ==> math3(4, 2)(1, 3)
-// ==> math3(4)(2)(1) ^-- ignored as 3 is not a Folktale curried fn
-// ==> 3
-
-Short answer: the way Validation's .ap
method works makes it impossible to implement the Monad interface (.chain
). You might either want to use Either/Result, or rethink how you're approaching the problem.
Short answer: the way Validation's .ap
method works makes it impossible to implement the Monad interface (.chain
). You might either want to use Either/Result, or rethink how you're approaching the problem.
Validation and Either/Result are very similar types, but while Either/Result have .chain
and .ap
, Validation only has .ap
. This confuses people expecting to use Validation to sequence things that may fail.
The new documentation explains these similarities and differences very concisely, and likens the Either/Result + .chain
to JavaScript's exceptions and the ;
operator. Either/Result, Future, and Task all have Monad implementations that could be understood in terms of ;
. That is, a regular code like var x = doX(); doY()
would be equivalent to doX().chain((x) => doY())
if using the Monad implementation of those data structures.
Validation is a bit different. It's not designed to sequence computations like that, but to aggregate failures. A common use case is validating a form submission or checking if a data structure matches a schema. In those cases you don't really check the first field, and then move to checking the next one only if the first one succeeds. The checks are largely independent, so you just check all of them separately, then combine them, so you can get all of the ones that have failed.
-The way Validation combines these independent checks is through its Applicative implementation, or the .ap
method. It works like this:
Success(x) ap Success(y) => Success(x(y))
-Failure(x) ap Success(y) => Failure(x)
-Success(x) ap Failure(y) => Failure(y)
-Failure(x) ap Failure(y) => Failure(x + y)
-
Out of these four cases, the last one is the interesting one. If we have two Failures, and we apply them together, we get a new Failure that is the combination of both of their values (we combine them through the .concat
method!). That is, if we have the following code:
Failure('hello').ap(Failure(', ')).ap(Failure('world!'));
-
-We get:
-Failure('hello'.concat(', ').concat('world!'));
-
-This is in line with the goal of combining all of the errors (as in the form validation example), but how exactly does this prevent Validation from having a .chain
method? It's more that Validation can't implement Monad than it being unable to have a .chain
method, but it so happens that in Fantasy Land, adding a .chain
method when you have a .of
method is considered an implementation of Monad. And if you implement Monad, turns out your methods have to satisfy some requirements (so people can write generic code that doesn't behave weirdly here and there). The requirement that matters here is this one:
V1.map(x => y => [x, y]).ap(V2) = V1.chain(x => V2.map(y => [x, y]));
-
-So, if you implement Monad and Applicative, then the code on the left has to be equivalent (i.e.: do the same thing) as the code on the right. If we were to implement .chain
for Validation, it would look like this:
Success(x) chain f => f(x) -- `f` has to return a Validation
-Failure(x) chain f => Failure(x)
-
Quite simple, right? But remember our definition of .ap
? Let's compare some results and see why these aren't equivalent:
Success('1').chain(x => Success('2').map(y => x + y)) // ==> Success('12')
-Success(x => y => x + y).ap(Success('1')).ap(Success('2')) // ==> Success('12')
-
-Success('1').chain(x => Failure('2').map(y => x + y)) // ==> Failure('2')
-Success(x => y => x + y).ap(Success('1')).ap(Failure('2')) // ==> Failure('2')
-
-Failure('1').chain(x => Success('2').map(y => x + y)) // ==> Failure('1')
-Success(x => y => x + y).ap(Failure('1')).ap(Success('2')) // ==> Failure('1')
-
-Failure('1').chain(x => Failure('2').map(y => x + y)) // ==> Failure('1')
-Success(x => y => x + y).ap(Failure('1')).ap(Failure('2')) // ==> Failure('12')
-
-Oops. The last case doesn't behave quite the same. Since the .chain
method can't execute the function to get the other Failure, the only thing it can do is return the Failure it's got. Meanwhile, .ap
can compare both values and decide how to combine them. But this combining makes their behaviours incompatible, and thus one's got to decide whether they want the sequential part, or the combining part.
Since you're likely to need both in you application, Folktale divides that in Either/Result (the sequential part), and Validation (the combining part). Starting with Folktale 2, you can easily convert between the two with Result#toValidation()
and Validation#toResult()
. It's possible to write equivalent conversion functions in Folktale 1, but none is provided out of the box.