diff --git a/CHANGELOG.md b/CHANGELOG.md
index b32fd9a51..256c2b89e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,11 @@
**Note**: Gaps between patch versions are faulty/broken releases.
**Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.
+# 0.2
+
+- **Breaking Change**
+ - complete refactoring: new technique to get higher kinded types and typeclasses
+
# 0.1.1
- **New Feature**
diff --git a/README.md b/README.md
index 585d12a5e..75f8145d1 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,6 @@ A mix of
- PureScript
- Scala
-The idea (faking higher kinded types in TypeScript) is based on the paper [Lightweight higher-kinded polymorphism](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf), [elm-brands](https://github.com/joneshf/elm-brands) and [flow-static-land](https://github.com/gcanti/flow-static-land).
-
See the section "Technical overview" below for an explanation of the technique.
# Algebraic types
@@ -53,331 +51,69 @@ See the section "Technical overview" below for an explanation of the technique.
# Technical overview
-## A basic `Option` type
-
-File: Option.ts
-
-```ts
-// definition
-type None = {
- __tag: 'None'
-}
-
-type Some = {
- __tag: 'Some',
- value: A
-}
-
-type Option = None | Some
-
-// helpers
-const none: None = { __tag: 'None' }
-
-function some(a: A): Option {
- return { __tag: 'Some', value: a }
-}
-
-// a specialised map for Option
-function map(f: (a: A) => B, fa: Option): Option {
- switch (fa.__tag) {
- case 'None' :
- return fa
- case 'Some' :
- return some(f(fa.value))
- }
-}
-```
-
-Usage
-
-```ts
-const double = (n: number): number => n * 2
-const length = (s: string): number => s.length
-
-console.log(map(double, some(1))) // { __tag: 'Some', value: 2 }
-console.log(map(double, none)) // { __tag: 'None' }
-console.log(map(length, some(2))) // <= error
-```
-
-## Adding static land support
-
-TypeScript doesn't support higher kinded types
-
-```ts
-interface StaticFunctor {
- map(f: (a: A) => B, fa: ?): ?
-}
-```
-
-but we can fake them with an interface
-
-```ts
-interface HKT {
- __hkt: F
- __hkta: A
-}
-```
-
-where `F` is a unique identifier representing the type constructor and `A` its type parameter.
-
-Now we can define a generic `StaticFunctor` interface
-
-```ts
-interface StaticFunctor {
- map(f: (a: A) => B, fa: HKT): HKT
-}
-```
-
-and a new `Option` type
-
-```ts
-// unique identifier
-type URI = 'Option'
-
-type None = {
- __tag: 'None'
- __hkt: URI
- __hkta: any
-}
-
-type Some = {
- __tag: 'Some',
- __hkt: URI
- __hkta: A
- value: A
-}
-
-type Option = None | Some
-
-const none: None = {
- __tag: 'None',
- __hkt: 'Option',
- __hkta: undefined as any
-}
-
-function some(a: A): Option {
- return {
- __tag: 'Some',
- __hkt: 'Option',
- __hkta: a,
- value: a
- }
-}
-
-function map(f: (a: A) => B, fa: Option): Option {
- switch (fa.__tag) {
- case 'None' :
- return fa
- case 'Some' :
- return some(f(fa.value))
- }
-}
-```
-
-Let's check the implementation
-
-```ts
-// if this type-checks the signature is likely correct
-;({ map } as StaticFunctor)
-```
-
-Usage
-
-```ts
-console.log(map(double, some(1))) // { __tag: 'Some', __hkt: 'Option', __hkta: 2, value: 2 }
-console.log(map(double, none)) // { __tag: 'None', __hkt: 'Option', __hkta: undefined }
-console.log(map(length, some(2))) // <= error
-```
-
-Exports can be directly used as a static land dictionary
-
-```ts
-import * as option from './Option' // option contains map
-```
-
-There's a problem though. Let's define a generic `lift` function based on the `StaticFunctor` interface
-
-```ts
-class FunctorOps {
- lift(functor: StaticFunctor, f: (a: A) => B): (fa: HKT) => HKT {
- return fa => functor.map(f, fa)
- }
-}
-
-const ops = new FunctorOps()
-```
-
-If we try to use `lift` and `map` together TypeScript raises an error
-
-```ts
-const maybeLength = ops.lift({ map }, length)
-
-map(double, maybeLength(some('hello')))
-/*
-Argument of type 'HKT<"Option", number>' is not assignable to parameter of type 'Option'.
- Type 'HKT<"Option", number>' is not assignable to type 'Some'.
- Property '__tag' is missing in type 'HKT<"Option", number>'
-*/
-```
-
-Every `Option` is a `HKT<"Option", A>` but the converse is not true. In order to fix this (we **know** that `Option = HKT<"Option", A>`) functions like `map` should accept the more general version `HKT<"Option", A>` and return the more specific version `Option`
-
-```ts
-type HKTOption = HKT
-
-function map(f: (a: A) => B, fa: HKTOption): Option {
- const option = fa as Option
- switch (option.__tag) {
- case 'None' :
- return option
- case 'Some' :
- return some(f(option.value))
- }
-}
+## Higher kinded types and type classes
-map(double, maybeLength(some('hello'))) // ok
-```
+Higher kinded types are represented by a unique string literal (called `URI`).
-We can do even better. Note that `maybeLength` has the following signature
+There's a central type dictionary where a mapping `URI` -> concrete type is stored
```ts
-(fa: HKT<"Option", string>) => HKT<"Option", number>
+// file ./HTK.ts
+export interface HKT {}
```
-We'd like to have `(fa: Option) => Option` instead.
-
-We'll use a feature called [Module Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) for that
-
-Let's move the `Functor` definition to its own file
-
-File: Functor.ts
-
-```ts
-export interface StaticFunctor {
- map(f: (a: A) => B, fa: HKT): HKT
-}
-
-export class FunctorOps {
- // base signature
- lift(functor: StaticFunctor, f: (a: A) => B): (fa: HKT) => HKT
- lift(functor: StaticFunctor, f: (a: A) => B): (fa: HKT) => HKT {
- return fa => functor.map(f, fa)
- }
-}
-
-export const ops = new FunctorOps()
-```
+Instances can be defined (everywhere) using a feature called [Module Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) so there's no danger of name conflict (the typechecker checks for duplicates).
-File: Option.ts
+Here's a mapping between the string literal `'Option'` and the concrete type `Option`
```ts
-declare module './Functor' {
- interface FunctorOps {
- // specialized signature for Option
- lift(functor: StaticFunctor, f: (a: A) => B): (fa: Option) => Option
+// file ./Option.ts
+declare module './HKT' {
+ interface HKT {
+ Option: Option
}
}
-```
-
-That means that `lift` is truly polimophic and may have a specialized signature for any higher kinded type.
-
-## Adding fantasy land support
-
-We can define a generic `FantasyFunctor` interface
-
-```ts
-interface FantasyFunctor extends HKT {
- map(f: (a: A) => B): FantasyFunctor
-}
-```
-And now let's change the implementation of `None` and `Some`
+export const URI = 'Option'
+export type URI = typeof URI
+export type Option = None | Some
-```ts
-type URI = 'Option'
-
-class None implements FantasyFunctor {
- __tag: 'None'
- __hkt: URI
- __hkta: any
+export class None {
+ readonly _URI: URI
map(f: (a: A) => B): Option {
return none
}
+ ...
}
-class Some implements FantasyFunctor {
- __tag: 'Some'
- __hkt: URI
- __hkta: A
- constructor(public value: A) { }
+export Some {
+ readonly _URI: URI
+ // fantasy-land implementation
map(f: (a: A) => B): Option {
- return some(f(this.value))
+ return new Some(f(this.value))
}
+ ...
}
-type Option = None | Some
-
-const none = new None()
-
-function some(a: A): Option {
- return new Some(a)
-}
-```
-
-Note that `None` has a type parameter, because the signature of `map` (the method) must be the same for both `None` and `Some` otherwise TypeScript will complain.
-
-The implementation of `map` (the static function) is now trivial.
-
-```ts
-function map(f: (a: A) => B, fa: HKTOption): Option {
- return (fa as Option).map(f)
+// static-land implementation
+export function map(f: (a: A) => B, fa: Option): Option {
+ return fa.map(f)
}
```
-## Faking Haskell's type classes
-
-Let's add to `FunctorOps` a polimorphic `map` function
-
-```ts
-export class FunctorOps {
- lift(functor: StaticFunctor, f: (a: A) => B): (fa: HKT) => HKT
- lift(functor: StaticFunctor, f: (a: A) => B): (fa: HKT) => HKT {
- return fa => functor.map(f, fa)
- }
+Concrete types can be retrieved by their `URI` using a feature called [Index types](https://www.typescriptlang.org/docs/handbook/advanced-types.html).
- map(f: (a: A) => B, fa: FantasyFunctor): FantasyFunctor
- map(f: (a: A) => B, fa: FantasyFunctor): FantasyFunctor {
- return fa.map(f)
- }
-}
-```
+Type classes are implemented following (when possible) both the [static-land](https://github.com/rpominov/static-land) spec and the [fantasy-land](https://github.com/fantasyland/fantasy-land) spec.
-And the corresponding module augmentation in the `Option.ts` file
+ Here's the definition of the type class `Functor` (following the static-land spec)
```ts
-declare module './Functor' {
- interface FunctorOps {
- lift(functor: StaticFunctor, f: (a: A) => B): (fa: Option) => Option
- map(f: (a: A) => B, fa: HKTOption): Option
- }
+export interface StaticFunctor {
+ URI: F
+ map(f: (a: A) => B, fa: HKT[F]): HKT[F]
}
```
-> Roughly speaking the `map` definition in `FunctorOps` corresponds to a type class definition, while the module augmentation part corresponds to declaring an instance
-
-Now `map` is a truly polimorphic function with prefect type inference
-
-```ts
-import * as option from 'fp-ts/lib/Option'
-import * as io from 'fp-ts/lib/IO'
-const length = (s: string): number => s.length
-
-// x :: Option
-const x = map(length, option.of('hello'))
-// y :: IO
-const y = map(length, io.of('hello'))
-```
-
# License
The MIT License (MIT)
diff --git a/package.json b/package.json
index b0252fc69..32ef577aa 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fp-ts",
- "version": "0.1.1",
+ "version": "0.2.0",
"description": "Functional programming in TypeScript",
"files": [
"lib",
@@ -11,9 +11,11 @@
"typings": "lib/index.d.ts",
"scripts": {
"lint": "tslint src/**/*.ts test/**/*.ts",
+ "typings-checker": "typings-checker --allow-expect-error --project typings-checker/tsconfig.json typings-checker/index.ts",
"mocha": "mocha -r ts-node/register test/*.ts",
- "test": "npm run lint && npm run mocha",
- "build": "rm -rf lib/* && tsc && tsc -m es6 --outDir lib-jsnext"
+ "test": "npm run lint && npm run typings-checker && npm run mocha",
+ "clean": "rm -rf lib/* && rm -rf lib-jsnext/*",
+ "build": "npm run clean && tsc && tsc -m es6 --outDir lib-jsnext"
},
"repository": {
"type": "git",
@@ -27,13 +29,14 @@
"homepage": "https://github.com/gcanti/fp-ts",
"dependencies": {},
"devDependencies": {
- "@types/mocha": "^2.2.38",
- "@types/node": "^7.0.4",
- "mocha": "^3.2.0",
- "ts-node": "^2.0.0",
- "tslint": "^4.4.2",
- "tslint-config-standard": "^4.0.0",
- "typescript": "^2.2.0"
+ "@types/mocha": "2.2.38",
+ "@types/node": "7.0.4",
+ "mocha": "3.2.0",
+ "ts-node": "2.0.0",
+ "tslint": "4.4.2",
+ "tslint-config-standard": "4.0.0",
+ "typescript": "2.2.2",
+ "typings-checker": "1.1.2"
},
"tags": [
"typescript",
diff --git a/src/Alt.ts b/src/Alt.ts
index f5fe972a0..bd525801a 100644
--- a/src/Alt.ts
+++ b/src/Alt.ts
@@ -1,21 +1,10 @@
-import { HKT } from './HKT'
-import { StaticFunctor } from './Functor'
-import { Function1 } from './function'
+import { HKT, HKTS } from './HKT'
+import { StaticFunctor, FantasyFunctor } from './Functor'
-export interface StaticAlt extends StaticFunctor {
- alt(fx: HKT, fy: HKT): HKT
+export interface StaticAlt extends StaticFunctor {
+ alt(fx: HKT[F], fy: HKT[F]): HKT[F]
}
-export interface FantasyAlt {
- map(f: Function1): FantasyAlt
- alt(fy: FantasyAlt): FantasyAlt
+export interface FantasyAlt extends FantasyFunctor {
+ alt(fy: FantasyAlt): HKT[F]
}
-
-export class AltOps {
- alt(fx: FantasyAlt, fy: FantasyAlt): FantasyAlt
- alt(fx: FantasyAlt, fy: FantasyAlt): FantasyAlt {
- return fx.alt(fy)
- }
-}
-
-export const ops = new AltOps()
diff --git a/src/Alternative.ts b/src/Alternative.ts
index 26e682834..1ea13c32d 100644
--- a/src/Alternative.ts
+++ b/src/Alternative.ts
@@ -1,13 +1,7 @@
-import { StaticApplicative } from './Applicative'
-import { StaticPlus } from './Plus'
-import { Function1 } from './function'
+import { HKTS } from './HKT'
+import { StaticApplicative, FantasyApplicative } from './Applicative'
+import { StaticPlus, FantasyPlus } from './Plus'
-export interface StaticAlternative extends StaticApplicative, StaticPlus {}
+export interface StaticAlternative extends StaticApplicative, StaticPlus {}
-export interface FantasyAlternative {
- map(f: Function1): FantasyAlternative
- of(b: B): FantasyAlternative
- ap(fab: FantasyAlternative>): FantasyAlternative
- alt(fy: FantasyAlternative): FantasyAlternative
- zero(): FantasyAlternative
-}
+export interface FantasyAlternative extends FantasyApplicative, FantasyPlus {}
diff --git a/src/Applicative.ts b/src/Applicative.ts
index 185e7bdad..fe43f0ad3 100644
--- a/src/Applicative.ts
+++ b/src/Applicative.ts
@@ -1,11 +1,7 @@
-import { StaticPointedFunctor } from './PointedFunctor'
-import { StaticApply } from './Apply'
-import { Function1 } from './function'
+import { HKTS } from './HKT'
+import { StaticPointed, FantasyPointed } from './Pointed'
+import { StaticApply, FantasyApply } from './Apply'
-export interface StaticApplicative extends StaticPointedFunctor, StaticApply {}
+export interface StaticApplicative extends StaticPointed, StaticApply {}
-export interface FantasyApplicative {
- map(f: Function1): FantasyApplicative
- of(b: B): FantasyApplicative
- ap(fab: FantasyApplicative>): FantasyApplicative
-}
+export interface FantasyApplicative extends FantasyPointed, FantasyApply {}
diff --git a/src/Apply.ts b/src/Apply.ts
index 1debcdd82..d836d1a35 100644
--- a/src/Apply.ts
+++ b/src/Apply.ts
@@ -1,36 +1,29 @@
-import { HKT } from './HKT'
-import { StaticFunctor } from './Functor'
-import { Function1, Function2, Function3, Function4, Curried2, Curried3, Curried4 } from './function'
+import { HKT, HKTS, HKT2, HKT2S } from './HKT'
+import { StaticFunctor, FantasyFunctor } from './Functor'
+import { Curried2, Curried3, Curried4 } from './function'
-export interface StaticApply extends StaticFunctor {
- ap(fab: HKT>, fa: HKT): HKT
+export interface StaticApply extends StaticFunctor {
+ ap(fab: HKT<(a: A) => B>[F], fa: HKT[F]): HKT[F]
}
-export interface FantasyApply {
- map(f: Function1): FantasyApply
- ap(fab: FantasyApply>): FantasyApply
+export interface FantasyApply extends FantasyFunctor {
+ ap(fab: FantasyApply B>): HKT[F]
}
-export class ApplyOps {
- ap(fab: FantasyApply>, fa: FantasyApply): FantasyApply
- ap(fab: FantasyApply>, fa: FantasyApply): FantasyApply {
- return fa.ap(fab)
- }
-
- liftA2(apply: StaticApply, f: Curried2): Function2, HKT, HKT>
- liftA2(apply: StaticApply, f: Curried2): Function2, HKT, HKT> {
- return (fa, fb) => apply.ap(apply.map(f, fa), fb)
- }
-
- liftA3(apply: StaticApply, f: Curried3): Function3, HKT, HKT, HKT>
- liftA3(apply: StaticApply, f: Curried3): Function3, HKT, HKT, HKT> {
- return (fa, fb, fc) => apply.ap(apply.ap(apply.map(f, fa), fb), fc)
- }
+export function liftA2(apply: StaticApply, f: Curried2): (fa: HKT2[F], fb: HKT2[F]) => HKT2[F]
+export function liftA2(apply: StaticApply, f: Curried2): (fa: HKT[F], fb: HKT[F]) => HKT[F]
+export function liftA2(apply: StaticApply, f: Curried2): (fa: HKT[F], fb: HKT[F]) => HKT[F] {
+ return (fa, fb) => apply.ap(apply.map(f, fa), fb)
+}
- liftA4(apply: StaticApply, f: Curried4): Function4, HKT, HKT, HKT, HKT>
- liftA4(apply: StaticApply, f: Curried4): Function4, HKT, HKT, HKT, HKT> {
- return (fa, fb, fc, fd) => apply.ap(apply.ap(apply.ap(apply.map(f, fa), fb), fc), fd)
- }
+export function liftA3(apply: StaticApply, f: Curried3): (fa: HKT2[F], fb: HKT2[F], fc: HKT2[F]) => HKT2[F]
+export function liftA3(apply: StaticApply, f: Curried3): (fa: HKT[F], fb: HKT[F], fc: HKT