Frequently Asked Questions
This document provides answers to some common questions people have when using Folktale.
Contents
Design decisions
Why does Folktale have Result
instead of Either
?
The name Either
, which is used by Haskell, is far more general then the name Result
. However, because Either
is biased — the Monad/Functor/etc instances only consider the Right
case —, it ends up being used mostly as a way of modelling failures. Within this particular use case, the names Left
and Right
aren’t particularly useful to help people figure out what they’re supposed to be modelling.
Languages like Rust provide a Result type that has a vocabulary that’s closer to the intention of the data structure. Meanwhile, languages like Scala don’t provide a monad instance for its Either
type, making it actually general-purpose.
The way Folktale does instances doesn’t allow multiple instances to be defined, so we have to specialise the types anyway. And if we’re doing that, it’s better to use terms that are more useful for people who’re trying to figure out what a particular piece of code does.
Common mistakes
Why do I get an error saying “.apply
is not a function”?
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:
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:
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:
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:
.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:
Why is there no .chain
for Validation?
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.
- Validation: A data structure for aggregating errors;
- Result/Either: A data structure for representing results of computations and sequencing them;
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:
We get:
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:
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:
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.
Folktale and the JavaScript ecosystem
Can I use Folktale with Flow or TypeScript?
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.
NOTES
- @jongold has started a project with Flow definitions for Folktale 1. Currently it has definitions for Data.Task.
Do Folktale structures implement Fantasy Land?
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
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
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.