Skip to content

Instantly share code, notes, and snippets.

@fvilante
Last active September 17, 2019 20:17
Show Gist options
  • Save fvilante/6ce13329bf307d307a040a63b70fe560 to your computer and use it in GitHub Desktop.
Save fvilante/6ce13329bf307d307a040a63b70fe560 to your computer and use it in GitHub Desktop.
Pattern Match
namespace Maybe_ {
type Undefined = typeof Undefined
const Undefined = Symbol.for("None") // undefined
interface Just<T> {
readonly type: "Just",
readonly val: T | Undefined
}
interface None<T> {
readonly type: "None",
readonly val: T | Undefined
}
export interface Maybe<T> {
readonly type: "Maybe"
readonly val: Just<T> | None<T>
}
export const Just = <T>(val: T ): Maybe<T> =>
({ type: "Maybe", val: { type: "Just", val } })
const None_ = <T>(val: T ): Maybe<T> =>
({ type: "Maybe", val: { type: "None", val } })
export const None = None_<Undefined>(Undefined)
// Teste inferencia implicita deste maybe foi
// omitida por motivos de brevidade. mas pode
// ser vista aqui:
// [link](https://gist.github.com/fvilante/2f53b699794c1ac35e087872e0acac98#file-functional-core-ts-L27)
}
namespace PatternMatch {
import Just = Maybe_.Just
import None = Maybe_.None
import Maybe = Maybe_.Maybe
type InferMaybeType<T> = T extends Maybe<infer U> ? U : never
type Maybe_<T> = Maybe<InferMaybeType<T>> // helper for brevity
type GetMaybeConstructors<T extends Maybe_<T>> = T['val']['type']
type CallBack<T, U> = (value: T) => U
type Match<T extends Maybe_<T>> = {
[Constructor in GetMaybeConstructors<T>]: CallBack< InferMaybeType<T>, any >
}
const match = <T extends Maybe_<T>>(maybe: T, match_: Match<T>) => {
const typeOfMaybeConstructor = maybe.val.type
const valueOfMaybe = maybe.val.val
return match_[typeOfMaybeConstructor](valueOfMaybe)
}
// *********************************************
// USO:
function plus_one(x: Maybe<number>): Maybe<number> {
return match(x, {
None: () => None,
Just: (i) => Just(i+1)
})
}
const five = Just(5)
const six = plus_one(five)
const none_ = plus_one(None)
}
@fvilante
Copy link
Author

fvilante commented Feb 27, 2019

Advantages of using pattern match

Pattern match is a feature of strong typed functional languages which main advantages are:

  • Static safety
  • Bug prevention

What happens if you forget to handle a particular case of your algorithm logic?

function plus_one(x: number | undefined: number | undefined {
    if (x > 0) 
        return x+1
    else
        return x

    // Wait! What about the 'undefined' condition?
    // This code will compile, but it is buggy!
} 

Pattern match assures that you will handle all paths of your programming logic. And if you do not, it will altert you at compile-time (or in wrinting-time if you use Linter). For example:

function plus_one(x: Maybe<number>): Maybe<number> {
    // pattern match
    return match(x, {
        // None: () => None,     
        Just: (i) => (i>0) ? Just(i+1) : Just(i)
    })

   // Compile Error! 'None' case MUST be implemented!
}

Aboce code will pass without raise any compile-time error:

function plus_one(x: Maybe<number>): Maybe<number> {
    // pattern match
    return match(x, {
        None: () => None,     // --> Ok, no Error
        Just: (i) =>  (i>0) ? Just(i+1) : Just(i)
    })

    // Ok, all cases had been handled.
}

Inspiration

Our inspiration comes from well-typed functional languages like Haskell, Rust, F#. Particularly from this two articles Rust Option and F# Option. Both articles include pattern matching mechanism concepts.

  • This is how pattern's match uses case looks-like in Rust Language:
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        Some(x) => println!("Result: {}", x),
        None => println!("Cannot divide by 0"),
    }
}
  • This is how our Typescript implementation looks like:
function plus_one(x: Maybe<number>): Maybe<number> {
    return match(x, {
        None: () => None,
        Just: (i) => Just(i+1)
    })
}

Constraints and future actions

This implementation is just a prove-of-concept. At moment It just works with Maybe type, but we intend to generalize the function to work with any Monad like Either, IO, or even an user-defined Monad. All contribution are welcome!

Conceptual framework

We are depending of two concepts that Typescript's team introduced recently:

Those two concepts are used in the line of code that says: type InferMaybeType<T> = T extends Maybe<infer U> ? U : never

And in Generics in general. See:

OBS In line codes 43 and 44 we use a specific kind of access (ie: the T['val']['type'] part ) that I din't find any reference in TS documentation, but in an Stackoverflow anwser: sorry don't remember de link

Acknoledge

Thanks to Vitor Luiz Cavalcant to sugest Rust as inspirantion and to challange me.

@fvilante
Copy link
Author

You may access the type inference test of Maybe here link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment