Refinements with Flow

A refinement type is a type endowed with a predicate which must hold for all instances of the refined type

For example the type Integer can be defined as the pair

`Integer = ( number, (n) => n % 1 === 0 )`

where number is the refined type and (n) => n % 1 === 0 is the predicate.

This is a proof of concept, the goal here is to implement refinements without boxing values.

The idea

A refinement has the following blueprint (where A is the refined type)

`// myrefinement.js`
`// privateclass IsR {}`
`// publicexport type R = A & IsR;`
`// publicexport function refinement(a: A): ?R {  return predicate(a) ? ((a: any): R) : null}`
• calling the function refinement should be the only legal way to get an instance of R
• the class IsR must not be exported. If the constructor is not exported then users of the library that defined R can’t accidentally build a value v of type R (unless they voluntarily do an unsafe cast with ((v: any): R) which should be avoided)
• collisions between two refinements of the same refined type A are not possible because of their corresponding nominal IsR types

A first example, integers

`// integer.js`
`class IsInteger {}`
`export type Integer = number & IsInteger;`
`export function integer(a: number): ?Integer {  return a % 1 === 0 ? ((a: any): Integer) : null}`

Let’s see how you can use the integer.js module

`import type { Integer } from './integer.js'import { integer } from './integer.js'`
`function foo(n: number) { ... }function bar(n: Integer) { return n > 0 } // <= n is not boxed`
`integer(1.1) // => null`
`const i = integer(1)if (typeof i === 'number') {  foo(i) // <= we can call foo because an integer is a number...  bar(i)}`
`// ... but a number is not an integerbar(1) // <= error: number. This type is incompatible with isInteger`

So an Integer is a number but a number is not an Integer. Nice.

Polymorphic refinements

The refined type A can be polymorfic (e.g. Array<*>).

Let’s define a refinement which represents a non empty array

`// nonEmptyArray.js`
`class IsNonEmptyArray {}`
`export type NonEmptyArray<A> = Array<A> & IsNonEmptyArray;`
`// polymorfic functionexport function nonEmptyArray<A>(a: Array<A>): ?NonEmptyArray<A> {`
`  return a.length > 0 ? ((a: any): NonEmptyArray<A>) : null`
`}`

Usage

`import type { NonEmptyArray } from './nonEmptyArray.js'import { nonEmptyArray } from './nonEmptyArray.js'`
`function foo<A>(a: NonEmptyArray<A>) { ... }`
`const a = nonEmptyArray([1, 2, 3])if (a) {  foo(a)}`
`// foo([1, 2, 3]) // error`

Drawbacks

Every refinement type is also an Object

`function foo(x: Object) { ... }`
`const i = integer(1)if (typeof i === 'number') {  foo(i) // <= Flow doesn't complain}`