Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
robotlolita authored Jul 15, 2017
2 parents e076283 + 2078b05 commit 84a1b06
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/_data/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
docs:
- v2.0.0/release-notes
- v2.0.0/download
- v2.0.0/overview
- url: /api/v2.0.0/en/folktale.html
title: API reference
- v2.0.0/changelog
Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/v2.0.0/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Changelog
prev_doc: v2.0.0/download
prev_doc: v2.0.0/overview
next_doc: v2.0.0/known-issues
---

Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/v2.0.0/download.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Installing
prev_doc: v2.0.0/release-notes
next_doc: v2.0.0/changelog
next_doc: v2.0.0/overview
---

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](https://nodejs.org/en/) or use an installer like [nvm](https://github.com/creationix/nvm).
Expand Down
7 changes: 6 additions & 1 deletion docs/_docs/v2.0.0/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ We've also got some pages on [how to get help]({% link _docs/support/index.md %}
<p>Getting Folktale up and running in different JS platforms.</p>
</div>

<div class="box">
<div class="box-title"><a href="{% link _docs/v2.0.0/overview.md %}">Overview</a></div>
<p>An introduction to the most important concepts in Folktale.</p>
</div>

<div class="box">
<div class="box-title"><a href="/api/v2.0.0/en/folktale.html">API reference</a></div>
<p>Describes all functions and objects, with usage examples.</p>
<p>Describes all functions and objects, with motivating use cases and usage examples.</p>
</div>

<div class="box">
Expand Down
234 changes: 234 additions & 0 deletions docs/_docs/v2.0.0/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
---
title: Overview
prev_doc: v2.0.0/download
next_doc: v2.0.0/changelog
---

Folktale is a library to support a functional style of programming in JavaScript.
In its current version Folktale provides utilities for [combining functions](/api/v2.0.0/en/folktale.core.lambda.html),
[transforming objects](/api/v2.0.0/en/folktale.core.object.html), [modelling data](/api/v2.0.0/en/folktale.adt.html), [handling errors](/api/v2.0.0/en/folktale.html#cat-handling-failures), and [concurrency](/api/v2.0.0/en/folktale.concurrency.task.html).

The [API reference](/api/v2.0.0/en/folktale.html) provides detailed documentation
on these, but may be difficult to navigate. This page serves as an introduction to
the most important concepts.


## Contents
{:.no_toc}

* TOC
{:toc}


## Folktale and Functional Programming

The goal of Folktale is to be a fully-featured standard library that supports a functional
style of programming in JavaScript and TypeScript. In this style, functionality is broken
down in small and focused functions, and more complex functionality is created by combining
these functions. Functional Programming makes these pieces easier to combine by avoiding
accidental complexities, such as [overloading polymorphism][] and [side-effects][].

Other characteristics from the functional style of programming are stricter definitions
of data structures to simplify transforming them and aid correctness; and the use of
*pure* structures. That is, instead of changing a structure in memory, an entire new
structure is created for every change.

To do this efficiently, one needs special implementations of data structures, and new
ways to construct them. Folktale's goals is to help you achieve this.


[overloading polymorphism]: https://www.quora.com/Object-Oriented-Programming-What-is-a-concise-definition-of-polymorphism/answer/Quildreen-Motta
[side-effects]: https://en.wikipedia.org/wiki/Side_effect_(computer_science).


## Programming with functions

Core to functional programming is the idea of programming with functions (in the more mathematical
sense of the word). The concept is fairly broad, and most of the things encompassed by it can be
achieved with some work in ECMAScript.

For example, sometimes it's important to evaluate an expression eagerly when using higher-order
functions (such as `Array.map`), because otherwise the expression could be evaluated more than
once. This happens in particular where side-effects are concerned:

{% highlight js %}
const counter = {
current: 0,
next() {
return ++this.current;
}
};

// `counter.next()` is evaluated for every item in the array
['a', 'b', 'c'].map(x => counter.next());
// ==> [1, 2, 3]
{% endhighlight %}

In order to evaluate it only once, we'd need to evaluate the expression and store its value
in some variable:

{% highlight js %}
const value = counter.next();
['a', 'b', 'c'].map(x => value);
// ==> [4, 4, 4]
{% endhighlight %}

Another option is using a functional combinator like [constant](/api/v2.0.0/en/folktale.core.lambda.constant.constant.html):

{% highlight js %}
const constant = require('folktale/core/lambda/constant');

['a', 'b', 'c'].map(constant(counter.next()));
// ==> [5, 5, 5]
{% endhighlight %}

Folktale's [`core/lambda`](/api/v2.0.0/en/folktale.core.lambda.html) module tries to provide
these small utilities that help combining and transforming functions in a program. See the
module's documentation for more details.


## Handling errors

There are a few ways in which errors are generally handled in JavaScript, but they tend to
boil down mostly to control-flow structures (`if/else`) or exceptions (`try/catch`). These
tend to either be hard to predict or hard to maintain. Foltkale provides three data
structures to help with error handling:

- [Maybe](/api/v2.0.0/en/folktale.maybe.html) - A structure that helps handling values
that may or may not be present. For example, a function like `Array.find` may or may
not be able to return a value. People tend to use `null` as a return value when the
function fails to return one, but that's ambiguous if the original array had a `null`
value. Maybe allows one to deal with these cases without ambiguity, and forces errors
to be handled.

{% highlight js %}
const Maybe = require('folktale/maybe');

function find(xs, predicate) {
for (const x of xs) {
if (predicate(x)) return Maybe.Just(x);
}

return Maybe.Nothing();
}

find([null, 1], x => true); // ==> Maybe.Just(null)
find([null, 1], x => false); // ==> Maybe.Nothing()
{% endhighlight %}

- [Result](/api/v2.0.0/en/folktale.result.html) - A structure that models the result
of functions that may fail. For example, parsing a JSON string into native objects
doesn't always succeed, but it may fail for different reasons: the JSON string could
be malformed, the reifying function could throw an error, etc. Ideally we'd capture
this additional information so the person receiving the result of the parsing could
deal with the failure in an appropriate manner. Result lets you do this safely.

{% highlight js %}
const Result = require('folktale/result');

class DivisionByZero extends Error {
get name() { return "DivisionByZero" }
}

class IllegalArgument extends Error {
get name() { return "IllegalArgument" }
}

function divide(x, y) {
if (typeox x !== "number" || typeof y !== "number") {
return Result.Error(new IllegalArgument(`arguments to divide must be numbers`));
} else if (y === 0) {
return Result.Error(new DivisionByZero(`${x} / ${y} is not computable.`));
} else {
return Result.Ok(x / y);
}
}

divide(4, 2); // ==> Result.Ok(2)
divide(2, 0); // ==> Result.Error([DivisionByZero: 2 / 0 is not computable])
{% endhighlight %}

- [Validation](/api/v2.0.0/en/folktale.validation.html) - A structure that helps with
validations (such as form validations). A validation function may succeed or fail,
like the functions mentioned above, but unlike the cases where Result is indicated,
when doing validations one generally wants to capture *all* of the failures and
display them back to the user. Validation works similarly to Result, but provides
methods that help aggregating all failures, rather than stopping at the first one.


## Modelling data

Another core part of functional programming is modelling data. Properly modelling
what you can and can't do with a data structure helps writing correct programs,
but JavaScript has very few tools for this. Folktale lessens this by adding
experimental support for tagged unions, which are used to model possibilities in a particular
type of data.

Think of tagged unions as a beefed-up enum structure. For example, a binary
tree can be seen as a type with the possibilities: "a branch node" or "a
leaf node":

{% highlight js %}
const { union } = require('folktale/adt/union');

const Tree = union('Tree', {
Leaf(value){
return { value };
},
Branch(left, right) {
return { left, right };
}
});
{% endhighlight %}

Folktale allows you to use a limited form of pattern matching to operate
on these structures:

{% highlight js %}
Tree.sum = function() {
return this.matchWith({
Leaf: ({ value }) => value,
Branch: ({ left, right }) => left.sum() + right.sum()
});
};

Branch(
Leaf(1),
Branch(Leaf(2), Leaf(3))
).sum();
// ==> 6

Tree.invert = function() {
return this.matchWith({
Leaf: ({ value }) => Leaf(value),
Branch: ({ left, right }) => Branch(right.invert(), left.invert())
});
};

Branch(
Leaf(1),
Leaf(2)
).invert();
// ==> Branch(Leaf(2), Leaf(1))
{% endhighlight %}


More documentation can be found in the [`adt/union`](/api/v2.0.0/en/folktale.adt.union.html)
module reference.


## Concurrency

JavaScript has added better support to concurrency recently with Promises and `async/await`,
but Promises work at the value level, so they can't help with actions and their composition.
For example, if one sends an HTTP request, they may want to cancel it if it takes too long,
or they may send two HTTP requests, take the first that returns, and cancel the other one.
None of this is supported by Promises, but they're supported by Folktale's experimental Task.

A Task is a structure that models asynchronous actions, including their resource management
and cancellation. This means that, if a Task is cancelled before it finishes executing, any
resources it allocated will be properly disposed of. Even when combined with other tasks,
or transformed by other functions.

The documentation for the [`concurrency/task`](/api/v2.0.0/en/folktale.concurrency.task.html)
module describes this in details.
1 change: 1 addition & 0 deletions packages/base/CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
- Tenor Biel (https://github.com/L8D)
- Juan Soto (https://github.com/sotojuan)
- Florian (https://github.com/floriansimon1)
- Jordan Ryan Reuter <oss@jreut.com> (https://www.jreut.com)
32 changes: 32 additions & 0 deletions packages/base/source/concurrency/task/do.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//----------------------------------------------------------------------
//
// 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.
//
//----------------------------------------------------------------------

const Task = require('./_task');

/*~
* stability: experimental
* type: |
* forall v, e: (GeneratorInstance [Task e v Any]) => Any => Task e [v] Any
*/
const nextGeneratorValue = (generator) => (value) => {
const { value: task, done } = generator.next(value);
return !done ? task.chain(nextGeneratorValue(generator))
/* else */ : task;
}

/*~
* stability: experimental
* type: |
* forall v, e: (Generator [Task e v Any]) => Task e [v] Any
*/
const taskDo = (generator) =>
new Task((resolver) => resolver.resolve(generator()))
.chain((generator) => nextGeneratorValue(generator)());

module.exports = taskDo;
1 change: 1 addition & 0 deletions packages/base/source/concurrency/task/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module.exports = {
task: require('./task'),
waitAny: require('./wait-any'),
waitAll: require('./wait-all'),
do: require('./do'),
_Task: Task,
_TaskExecution: require('./_task-execution'),

Expand Down
4 changes: 0 additions & 4 deletions packages/base/source/concurrency/task/wait-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ const { of } = require('./_task');
* forall v, e: ([Task e v Any]) => Task e [v] Any
*/
const waitAll = (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([])
Expand Down
48 changes: 48 additions & 0 deletions test/source/specs/base/concurrency/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ describe('Data.Task', () => {

const result2 = await Task.waitAll([Task.of(1), Task.rejected(2), Task.of(3)]).run().promise().catch(e => e);
$ASSERT(result2 == 2);

const result3 = await Task.waitAll([]).run().promise();
$ASSERT(result3 == []);
});

it('waitAny()', async () => {
Expand All @@ -326,6 +329,51 @@ describe('Data.Task', () => {
$ASSERT(result2 == true);
});

it('do()', async () => {
const delay = (ms) => Task.task((r) => {
const timer = setTimeout(() => r.resolve(ms), ms);
r.cleanup(() => clearTimeout(timer));
});
const result = await Task.do(function *() {
const a = yield delay(10);
const b = yield delay(11);
const c = yield Task.of(5);
return Task.of(a + b + c + 4);
}).run().promise();

$ASSERT(result == 30)

const exceptionThrown = [false, false];
await Task.do(function *() {
const a = yield Task.rejected(5);
return Task.of(a);
}).run().promise().catch((exception) => {
exceptionThrown[0] = true;
$ASSERT(exception == 5);
})
$ASSERT(exceptionThrown[0] == true);

const resultCancel = await Task.do(function *() {
const a = yield Task.task(r => r.cancel());
return Task.of(a);
}).run().promise().catch(() => {
exceptionThrown[1] = true;
});
$ASSERT(exceptionThrown[1] == true);

const multipleCallsResults = [];
const multipleCallsTask = await Task.do(function *() {
const a = yield delay(10);
return Task.of(a);
});
multipleCallsResults[0] = await multipleCallsTask.run().promise();
multipleCallsResults[1] = await multipleCallsTask.run().promise();

$ASSERT(multipleCallsResults[0] == 10);
$ASSERT(multipleCallsResults[1] == 10);
});


describe('#run()', () => {
it('Executes the computation for the task', () => {
let ran = false;
Expand Down

0 comments on commit 84a1b06

Please sign in to comment.