Skip to content

Commit

Permalink
define $.Function and $.UnaryTypeVariable
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchambers committed Jul 4, 2016
1 parent c3fa873 commit 0c69ad6
Show file tree
Hide file tree
Showing 3 changed files with 1,379 additions and 834 deletions.
202 changes: 135 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const Integer = ...;
// NonZeroInteger :: Type
const NonZeroInteger = ...;

// env :: [Type]
// env :: Array Type
const env = $.env.concat([Integer, NonZeroInteger]);
```

Expand Down Expand Up @@ -165,6 +165,14 @@ $.Any :: Type

Type comprising every JavaScript value.

#### `AnyFunction`

```haskell
$.AnyFunction :: Type
```

Type comprising every Function value.

#### `Arguments`

```haskell
Expand Down Expand Up @@ -218,10 +226,15 @@ Type comprising every [`ValidNumber`](#validnumber) value except `Infinity` and
#### `Function`

```haskell
$.Function :: Type
$.Function :: [Type] -> Type
```

Type comprising every Function value.
Constructor for Function types.

Examples:

- `$.Function([$.Date, $.String])` represents the `Date -> String` type; and
- `$.Function([a, b, a])` represents the `(a, b) -> a` type.

#### `Integer`

Expand Down Expand Up @@ -474,63 +487,6 @@ bodies of incoming POST requests against these types.

sanctuary-def provides several functions for defining types.

#### `TypeVariable`

Polymorphism is powerful. Not being able to define a function for all types
would be very limiting indeed: one couldn't even define the identity function!

```haskell
TypeVariable :: String -> Type
```

Before defining a polymorphic function one must define one or more type
variables:

```javascript
const a = $.TypeVariable('a');
const b = $.TypeVariable('b');

// id :: a -> a
const id = def('id', {}, [a, a], x => x);

id(42);
// => 42

id(null);
// => null
```

The same type variable may be used in multiple positions, creating a
constraint:

```javascript
// cmp :: a -> a -> Number
const cmp =
def('cmp', {}, [a, a, $.Number], (x, y) => x < y ? -1 : x > y ? 1 : 0);

cmp(42, 42);
// => 0

cmp('a', 'z');
// => -1

cmp('z', 'a');
// => 1

cmp(0, '1');
// ! TypeError: Type-variable constraint violation
//
// cmp :: a -> a -> Number
// ^ ^
// 1 2
//
// 1) 0 :: Number
//
// 2) "1" :: String
//
// Since there is no type of which all the above values are members, the type-variable constraint has been violated.
```

#### `NullaryType`

`NullaryType` is used to construct types with no type variables. `$.Number` is
Expand Down Expand Up @@ -602,7 +558,7 @@ rem(42, 0);
defined via `UnaryType`.

```javascript
// sum :: [Number] -> Number
// sum :: Array Number -> Number
const sum =
def('sum', {}, [$.Array($.Number), $.Number], xs => xs.reduce((x, y) => x + y, 0));

Expand All @@ -629,9 +585,10 @@ To define a unary type `t a` one must provide:
if (and only if) the value is a member of `t x` for some type `x`;

- a function which takes any value of type `t a` and returns an array
of the values of type `a` contained in the `t` (exposed as `t._1`); and
of the values of type `a` contained in the `t` (exposed as
`t.types.$1.extractor`); and

- the type of `a` (exposed as `t.$1`).
- the type of `a` (exposed as `t.types.$1.type`).

```haskell
UnaryType :: String -> (Any -> Boolean) -> (t a -> [a]) -> Type -> Type
Expand Down Expand Up @@ -701,14 +658,16 @@ To define a binary type `t a b` one must provide:
`x` and `y`;

- a function which takes any value of type `t a b` and returns an array
of the values of type `a` contained in the `t` (exposed as `t._1`);
of the values of type `a` contained in the `t` (exposed as
`t.types.$1.extractor`);

- a function which takes any value of type `t a b` and returns an array
of the values of type `b` contained in the `t` (exposed as `t._2`);
of the values of type `b` contained in the `t` (exposed as
`t.types.$2.extractor`);

- the type of `a` (exposed as `t.$1`); and
- the type of `a` (exposed as `t.types.$1.type`); and

- the type of `b` (exposed as `t.$2`).
- the type of `b` (exposed as `t.types.$2.type`).

```haskell
BinaryType :: String -> (Any -> Boolean) -> (t a b -> [a]) -> (t a b -> [b]) -> Type -> Type -> Type
Expand Down Expand Up @@ -870,6 +829,115 @@ dist(0);
// The value at position 1 is not a member of ‘{ x :: FiniteNumber, y :: FiniteNumber }’.
```

#### `TypeVariable`

Polymorphism is powerful. Not being able to define a function for all types
would be very limiting indeed: one couldn't even define the identity function!

```haskell
TypeVariable :: String -> Type
```

Before defining a polymorphic function one must define one or more type
variables:

```javascript
const a = $.TypeVariable('a');
const b = $.TypeVariable('b');

// id :: a -> a
const id = def('id', {}, [a, a], x => x);

id(42);
// => 42

id(null);
// => null
```

The same type variable may be used in multiple positions, creating a
constraint:

```javascript
// cmp :: a -> a -> Number
const cmp =
def('cmp', {}, [a, a, $.Number], (x, y) => x < y ? -1 : x > y ? 1 : 0);

cmp(42, 42);
// => 0

cmp('a', 'z');
// => -1

cmp('z', 'a');
// => 1

cmp(0, '1');
// ! TypeError: Type-variable constraint violation
//
// cmp :: a -> a -> Number
// ^ ^
// 1 2
//
// 1) 0 :: Number
//
// 2) "1" :: String
//
// Since there is no type of which all the above values are members, the type-variable constraint has been violated.
```

#### `UnaryTypeVariable`

As its name suggests, `UnaryTypeVariable` combines [`UnaryType`](#unarytype)
and [`TypeVariable`](#typevariable).

To define a unary type variable `t a` one must provide:

- a name (conventionally matching `^[a-z]$`); and

- the type of `a` (exposed as `t.types.$1.type`).

```haskell
UnaryTypeVariable :: String -> Type -> Type
```

Consider the type of a generalized `map`:

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

`f` is a unary type variable. With two (regular) type variables, one unary
type variable, and one [type class](#type-classes) it's possible to define
a fully polymorphic `map` function:

```javascript
const a = $.TypeVariable('a');
const b = $.TypeVariable('b');
const f = $.UnaryTypeVariable('f');

// Functor :: TypeClass
const Functor = ...;

// map :: Functor f => (a -> b) -> f a -> f b
const map =
def('map',
{f: [Functor]},
[$.Function([a, b]), f(a), f(b)],
(fn, functor) => functor.map(fn));
```

Whereas a regular type variable is fully resolved (`a` might become
`Array (Array String)`, for example), a unary type variable defers to
its type argument, which may itself be a type variable. The type argument
corresponds to the type argument of a unary type or the *second* type
argument of a binary type. The second type argument of `Map k v`, for
example, is `v`. One could replace `Functor => f` with `Map k` or with
`Map Integer`, but not with `Map`.

This shallow inspection makes it possible to constrain a value's "outer"
and "inner" types independently.

### Type classes

`concatS`, defined earlier, is a function which concatenates two strings.
Expand Down
Loading

0 comments on commit 0c69ad6

Please sign in to comment.