DEV Community

Functional design: Algebraic Data Types

Giulio Canti on March 18, 2019

A good first step while building a new application is to define its domain model. TypeScript offers many tools to help you in this task. Algebraic ...
Collapse
 
mdi profile image
mdl • Edited

Great article !
What do you think about this approach to pattern matching ?
I think it looks a bit more declarative then checking the type manually.

type Tagged = {_tag: string}

type Tags<TUnionType extends Tagged> = TUnionType['_tag']

type TagTypeMap<TTags extends string, TUnionType extends Tagged> = {
  [TTag in TTags]: TUnionType extends {_tag: TTag} ? TUnionType : never
}

type Pattern<TResult, TTagTypeMap> = {
  [K in keyof TTagTypeMap]: (item: TTagTypeMap[K]) => TResult
}

type Patt<TResult, TUnionType extends Tagged> =
  Pattern<TResult, TagTypeMap<Tags<TUnionType>, TUnionType>> 

const match = <TUnionType extends Tagged, TObject extends TUnionType, TResult>
  (pattern: Patt<TResult, TUnionType>) =>
    (object: TObject) =>
      pattern[object._tag][object] as TResult

// ------------------Sample------------------
type Square = {len: number, _tag: 'Square'}
type Circle = {rad: number, _tag: 'Circle'}
type Rectangle = {xlen: number, ylen: number, _tag: 'Rectangle'}

type Shape = Square | Circle | Rectangle

const shapes = [
  {len: 1} as Square,
  {rad: 2} as Circle,
  {xlen: 3, ylen: 4} as Rectangle
]

const shapeDescription: Patt<string, Shape> = {
  Square: ({len}) => `I am square with length ${len}`,
  Circle: ({rad}) => `I am circle with radius ${rad}`,
  Rectangle: ({xlen, ylen}) => `I am rectangle with sides ${xlen} and ${ylen}`
}

const descriptions = shapes.map(match(shapeDescription))
Enter fullscreen mode Exit fullscreen mode
Collapse
 
gcanti profile image
Giulio Canti

Check this out github.com/pfgray/ts-adt

Collapse
 
christiantakle profile image
Christian Takle

There is a small typo and don't know if one can submit a change to a post.

const fold = <A, R>(fa: Option<A>, onNone: () => R, onSome: (a: A) => R): R =>
  fa.type === 'None' ? onNone() : onSome(fa.value)
onNone: () => R

The following code block declare:

const s = fold(head([]), 'Empty array', a => String(a))

Its should have been:

const s = fold(head([]), () => 'Empty array', a => String(a))
Collapse
 
christopheriolo profile image
Christophe Riolo

Are you aware of ReasonML, which have real support for ADTs and pattern matching? πŸ™‚

Collapse
 
theodesp profile image
Theofanis Despoudis

ReasonML is way more readable...

Collapse
 
rametta profile image
Jason

Great article, well written!

Collapse
 
lovesponge profile image
Guy

Straight and to the point, great writing.
Question, is a 'Maybe' the equivalent to a 'Option' or an 'Either' in your example?

Collapse
 
gcanti profile image
Giulio Canti

Maybe is equivalent toΒ Option