Skip to content

Instantly share code, notes, and snippets.

@nrkn
Last active May 25, 2020 08:52
Show Gist options
  • Select an option

  • Save nrkn/7d1e202591216c8045ea0a31572c1914 to your computer and use it in GitHub Desktop.

Select an option

Save nrkn/7d1e202591216c8045ea0a31572c1914 to your computer and use it in GitHub Desktop.
TypeScript pattern matching with predicates
import { Shape } from './shapes.types'
import { area, perimeter } from './shapes.lib'
const shapes: Shape[] = [
{ radius: 4 }, { side: 5 }, { width: 6, height: 7 }
]
const totalArea = shapes.reduce(
( sum, shape ) => sum + area( shape ),
0
)
const totalPerimeter = shapes.reduce(
( sum, shape ) => sum + perimeter( shape ),
0
)
console.log( { totalArea, totalPerimeter } )
import { TypePredicates, TypeMapper } from './types'
export const matcher = <KeyMapper,T>(
predicates: TypePredicates<KeyMapper>, mapper: TypeMapper<KeyMapper,T>
) =>
<K extends keyof KeyMapper>( value: KeyMapper[ K ] ) => {
const predicateKeys = Object.keys( predicates ) as K[]
const key = predicateKeys.find( key => predicates[ key ]( value ) )
if ( key === undefined )
throw Error(
`Expected value to match one of: ${ predicateKeys.join( ', ' ) }`
)
return mapper[ key ]( value )
}
import { matcher } from './matcher'
import { ShapeMapper } from './shapes.types'
import { shapePredicates } from './shapes.predicates'
const areaMapper: ShapeMapper<number> = {
Square: square => square.side * square.side,
Circle: circle => circle.radius * Math.PI,
Rectangle: rectangle => rectangle.width * rectangle.height
}
const perimeterMapper: ShapeMapper<number> = {
Square: square => square.side * 4,
Circle: circle => 2 * Math.PI * circle.radius,
Rectangle: rect => rect.width * 2 + rect.height * 2
}
export const area = matcher( shapePredicates, areaMapper )
export const perimeter = matcher( shapePredicates, perimeterMapper )
import { Circle, Square, Rectangle, ShapePredicates } from './shapes.types'
export const isNumber = ( value: any ): value is number =>
typeof value === 'number' && !isNaN( value )
export const isCircle = ( value: any ): value is Circle =>
value && isNumber( value.radius )
export const isSquare = ( value: any ): value is Square =>
value && isNumber( value.side )
export const isRectangle = ( value: any ): value is Rectangle =>
value && isNumber( value.width ) && isNumber( value.height )
export const shapePredicates: ShapePredicates = {
Circle: isCircle,
Square: isSquare,
Rectangle: isRectangle
}
import { TypeMapper, TypePredicates } from './types'
export interface Circle {
radius: number
}
export interface Square {
side: number
}
export interface Rectangle {
width: number
height: number
}
export type Shape = Circle | Square | Rectangle
export interface ShapeKeyMapper {
Circle: Circle
Square: Square
Rectangle: Rectangle
}
export type ShapeMapper<T> = TypeMapper<ShapeKeyMapper, T>
export type ShapePredicates = TypePredicates<ShapeKeyMapper>
export type TypeMapper<KeyMapper, T> = {
[ K in keyof KeyMapper ]: ( value: KeyMapper[ K ] ) => T
}
export type TypePredicates<KeyMapper> = {
[ K in keyof KeyMapper ]: ( value: any ) => value is KeyMapper[ K ]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment