ericelliott / ericelliott/rtype

Haskell-style Typeclasses

Open
#120 14 comments 0 reactions 0 assignees View on GitHub
enhancement proposal
Dominant language
JavaScript
Stars
1.1k
Forks
38
PR merge metrics
No merged PRs in 30d

Description

When I first started rtype, I was tempted to just steal Haskell's Hindley-Milner type annotations and just start using them, but I saw several problems with that:

1. Haskell types are all curry all the time. I wanted to be able to express: `add2(a: Number, b: Number) => Number`.
2. How do you deal with `this`?
3. I wanted to be able to name parameters for clearer documentation. Haskell's types are very expressive and clean-looking, but it's sometimes hard to figure out what the type variables represent in real code.

So, we abandoned Hindley-Milner altogether, and forgot all about typeclasses. Ever since, we've been struggling with the question: How do we represent type polymorphism, and especially, higher order functions like map. Take Haskell's Functor type:

```haskell
fmap :: Functor f => (a -> b) -> f a -> f b
```

In this example, `a` and `b` are both type variables, and `f a` and `f b` mean "functor of `a`" and "functor of `b`", respectively.

The `Functor f =>` is called a type constraint. It basically says, "in this definition, f represents the `Functor` typeclass". Like generic type constructors in other languages, a typeclass takes a type parameter, so `f a` represents a type that is a member of the `Functor` typeclass, and `f b` represents a possibly different type that also a member of the `Functor` typeclass.

We currently have no way to express this in rtype, and because I do a lot of functional programming, I have been falling back on Haskell's Hindley-Milner types in a lot of my writing.

I'm frustrated with that because as I mentioned already, switching from rtype to Hindley-Milner or vice verse, we lose a lot of valuable information. Haskell's Hindley-Milner notation doesn't express enough about the names and purposes of variables, lacks ...rest, etc..., and also doesn't naturally allow for uncurried multi-arg functions.

If we try to express a lot of higher-order functions in rtype, it falls flat. To complicate matters, I'm building lots of types that implement a bunch of stuff with methods, meaning I need a way to say that `map` is a method on a `Functor` type, and takes `this` as an argument.

We need the best of both worlds.

## Proposal

What if we could do this in rtype?

```js
fmap = (a => b) => Functor(a) => Functor(b)
```

And for Mappables using `this`:

```js
interface Mappable {
map: Functor(a) ~> (a => b) => Functor(b)
}
```

Where `Functor(a)` is the type of `this` in the map method. The `~>` for `this` syntax is lifted from the Fantasyland specification.

Instead of specifying a type constraint with a variable name, we'll explicitly say `Functor(a)` or `Functor(b)` instead of `f a` or `f b`.

```js
updateWhere = Predicate => (a => b) => Functor(a) => Functor(b)
```

`updateWhere()` is a curried function that takes a predicate, an updater function from `a` to `b`, (where `a` and `b` represent any type, and may refer to the same type), and a `Functor` of `a`, and returns a new `Functor` of `b`, with the matching elements replaced with updated versions.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.