Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add types #281

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,143 @@ The `profunctor` method takes two arguments:

3. `promap` must return a value of the same Profunctor

## Types

In Fantasy Land, types are specified by an object containing a catamorphic
method `cata`. `cata` has an arity matching the number of constructor functions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be better to use different names for each type, instead of using one for all, as for example if sanctuary or ramda wanted to implement it, i don't see what should be a type for cata.
I would go with either for Either, maybe for Maybe and runIdentity for Identity`.

This way one could define maybe instance for Array for example, which might be useful in some cases.

Also regarding this general introduction of the # Types I think this part is a bit confusing, which as confusing as type of cata could be :D

If we instead go with special names for specific types, then we would have more precise description for each type.

Copy link
Member Author

@gabejohnson gabejohnson Jan 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go with either for Either, maybe for Maybe and runIdentity for Identity

This way one could define maybe instance for Array for example, which might be useful in some cases.

See #280 for an alternate proposal. It is identical to what you propose and would allow the variety of polymorphism you're describing.

belonging to each type. Each argument to `cata` is either:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the benefit of people who don't know the difference between Types and Type Classes, I think we should elaborate the difference here to avoid confusion. Maybe a paragraph explaining the benefits in having a specification for both?

Copy link
Member Author

@gabejohnson gabejohnson Jan 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered placing the type specifications in a separate file but figure that could be a separate PR.

I agree that an introductory paragraph contrasting the specification of algebras with that of types would be useful. Suggestions are welcome 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be helpful to have a section describing sum and product types.

Though I don't consider this specification a particularly good resource for JS devs new to FP, many find themselves here early on.


1. a value corresponding to a nullary constructor

1. Value arguments to `cata` must match the return type of `cata` itself.

2. a function with an arity matching the number of arguments to the
corresponding constructor function

1. Function arguments to `cata` must return values of the same type as
`cata` itself.

For example
```hs
Identity a = { cata :: (a -> b) -> b }
'------' ' '------' '
' ' ' ' - return type
' ' '
' ' ' - destructor function
' '
' ' - type variable
'
' - type name

identity :: a -> Identity a
'------' ' '--------'
' ' '
' ' ' - return type
' '
' ' - constructor argument
'
' - constructor name
```

Libraries conforming to this specification may provide custom data constructors
corresponding with the constructor functions. No provided constructors are
required to share the names of the constructor functions defined herein. For
example, instead of `Identity`, the constructor could be named `Id`. If custom
constructors _are_ provided, they must return values which contain a `cata`
method. This method must exhibit the same behaviour as described above.

For example
```js
var identity = require('fantasy-land/identity');

function Id(x) {
this.x = x;
}
Id.prototype['fantasy-land/cata'] = function cata(f) {
return f(this.x);
}

// Alternatively
Id.prototype['fantasy-land/cata'] = function cata(f) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When prefixing, should we also include the type name (e.g. fantasyland/either-cata ) to allow libraries to customize their dispatch and provide type errors for incompatible types? E.g. bimap over Maybe could fail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you talking about:

S.cata(Id.of(1), /* How do I check the arity of this function? */...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JAForbes this would also be taken care of by the strategy proposed in #280

return identity(this.x).cata(f);
}

(new Id(42)).cata(x => x) === identity(42).cata(x => x);
```

### Maybe

The `Maybe` type encodes the concept of optionality (Nothing and Just a).

```hs
Maybe a = { cata :: (b, a -> b) -> b }
nothing :: Maybe a
just :: a -> Maybe a
```

A value which conforms to the Maybe specification must provide a `cata` method.

The `cata` method takes two arguments:

m.cata(f, g)

1. `f` must be a nullary function. It is called in the `Nothing` case

1. If `f` is not a nullary function or the return value of `f` does not
match the return value of `cata`, the behaviour of `cata` is unspecified.

2. `g` must be a unary function. It is called in the `Just` case

1. If `g` is not a unary function or the return value of `g` does not match
the return value of `cata`, the behaviour of `cata` is unspecified.

### Either

The `Either` type encodes the concept of binary possibility (Left a and Right b).

```hs
Either a b = { cata :: (a -> c, b -> c) -> c }
left :: a -> Either a b
right :: b -> Either a b
```

A value which conforms to the Either specification must provide a `cata` method.

The `cata` method takes two arguments:

e.cata(f, g)

1. `f` must be a unary function. It is called in the `Left` case

1. If `f` is not a unary function or the return value of `f` does not match
the return value of `cata`, the behaviour of `cata` is unspecified.

2. `g` must be a unary function. It is called in the `Right` case

1. If `g` is not a unary function or the return value of `g` does not match
the return value of `cata`, the behaviour of `cata` is unspecified.

### Tuple

`Tuple` is the canonical product type and represents a structure containing two
values (Tuple a b).

```hs
Tuple a b = { cata :: ((a, b) -> c) -> c }
tuple :: (a, b) -> Tuple a b
```

A value which conforms to the Tuple specification must provide a `cata` method.

The `cata` method takes a single argument:

t.cata(f)

1. `f` must be a binary function

1. If `f` is not a binary function or the return value of `f` does not match
the return value of `cata`, the behaviour of `cata` is unspecified.

## Derivations

When creating data types which satisfy multiple algebras, authors may choose
Expand Down