Giter Site home page Giter Site logo

match's Introduction

poor man's pattern matching 🚥

npm version CircleCI

Install

yarn add @housinganywhere/match

npm i @housinganywhere/match

Use

Edit @housinganywhere/match

import * as React from 'react';
import match from '@housinganywhere/match';

type Status = 'loading' | 'error' | 'success';

enum Status {
  loading = 'loading',
  error = 'error',
  success = 'success',
}

const StatusMsg: React.SFC<{ status: Status }> = ({ status }) =>
  match<Status, React.ReactNode>({
    loading: () => <Spinner />,
    error: () => <Alert type="danger">There was an error</Alert>,
    success: () => <Alert type="success">Yay! It worked</Alert>,
  })(status);

For matching several cases together use wildMatch. All the missing cases will be handled by case _.

import { wildMatch } from '@housinganywhere/match';

type Vowels = 'a' | 'e' | 'i' | 'o' | 'u';

const isA = wildMatch<Vowels, string>({
  a: () => 'Yay!',
  _: (v) => `Nope, "${v}" is not "a"`,
});

isA('a'); // 'Yay!'
isA('e'); // 'Nope, "e" is not "a"'
isA('i'); // 'Nope, "i" is not "a"'
isA('o'); // 'Nope, "o" is not "a"'
isA('u'); // 'Nope, "u" is not "a"'

License

MIT © 2019 HousingAnywhere

match's People

Contributors

gillchristian avatar dependabot[bot] avatar

Stargazers

Daniel  avatar Gianmarco avatar Logan Fuller avatar O Teerasak Vichadee avatar Paulo Reis avatar  avatar Mark D'Arensbourg avatar bree avatar  avatar zhanba avatar Nicholas Van Doorn avatar Oliver Joseph Ash avatar Jakub Jirutka avatar  avatar Halil Coban avatar

Watchers

Ramkumar  avatar Maksim Terekhin avatar James Cloos avatar Prakash Wadhwani avatar Felix Enescu avatar Dyaa avatar Vlad Dyachenko avatar Shabbir Hossain avatar  avatar  avatar Dmitrii Lobanov avatar Neil Shah avatar Jakub Slocki avatar Djordy Seelmann avatar Arjun Bhandage avatar Blessing Pariola avatar Mahdi Valizadeh avatar Alessandra avatar Joshua Folivi avatar Alex Tutea avatar Eldar avatar Tharun Rajendran avatar  avatar Sinan Sivri avatar Michalis Papilaris avatar Nana Adjei Manu avatar Anna avatar Casprine Assempah avatar Adebisi Oluwabukunmi avatar  avatar Oğuzhan Gür avatar

Forkers

spaubleit

match's Issues

Match tagged unions

Matching on string unions is great and already brings a lot of value for writing declarative code.

To take it a step further and make it useful for more cases we should be able to match on tagged unions (or discriminated unions) allowing to pass not only the matched tag/key (like current match does) but the whole object.

Here is a potential implementation:

interface TaggedUnion<Union extends string> {
  readonly tag: Union;
}

type TaggedUnionMatcher<T extends TaggedUnion<string>, R> = {
  // how to refine `t` to the real type of the TaggedUnion
  [K in T['tag']]: <U extends TaggedUnion<K>>(t: U) => R
};

export const taggedMatch = <
  Union extends string,
  T extends TaggedUnion<Union>,
  R = void
>(
  m: TaggedUnionMatcher<T, R>,
) => (t: T) => {
  const refined = t;
  const f = m[refined.tag];

  return f(refined);
};

There's a problem though, I did not find a way for it to infer the whole object passed to the cases handlers.

type Tag = 'foo' | 'bar' | 'baz';

type Foo = { tag: 'foo'; foo: string };
type Bar = { tag: 'bar'; bar: string };
type Baz = { tag: 'baz'; baz: string };

type Union = Foo | Bar | Baz;

const m = taggedMatch<Tag, Union, string>({
  foo: ({ foo }) => foo,
  bar: ({ bar }) => bar,
  baz: ({ baz }) => baz,
});

TS complains that the foo, bar, baz properties, respectively, are not defined. Which we know, by checking the implementation, they are.

Here's the whole thing in the playground.

Once that's solved we can add it to the library.

Pattern match on tuples

Currently match and wildMatch support only matching one single union type.

If a match needs to be done on several types, nesting becomes very painful and requires a lot of boilerplate.

type A = 'A' | 'B' | 'C'
type F = 'F' | 'G' | 'H'

const matchTyple = ([a, f]: [A, F]) => match<A, string>({
  A: () =>
    wildMatch<F, string>({
      F: () => 1,
      _: () => 2,
    })(f),
  B: () =>
    wildMatch<F, string>({
      G: () => 3,
      _: () => 4,
    })(f),
  C: () =>
    wildMatch<F, string>({
      H: () => 5,
      _: () => 6,
    })(f),
})(a)

Here is how we would write that in Haskell

data A = A | B | C
data F = F | G | H

-- pattern match using `case x of`
matchTyple :: A -> F -> Int
matchTyple a f =
  case (a, f) of
    (A, F) -> 1
    (A, _) -> 2
    (B, G) -> 3
    (B, _) -> 4
    (C, H) -> 5
    (C, _) -> 6

-- pattern match on the function arguments
-- it doesn't need to be a tuple, but it's the easiest way to port to TS (I guess)
matchTyple' :: (A, F) -> Int
matchTyple' (A, F) = 1
matchTyple' (A, _) = 2
matchTyple' (B, G) = 3
matchTyple' (B, _) = 4
matchTyple' (C, H) = 5
matchTyple' (C, _) = 6

And to show a somehow more similar syntax to TS/JS, here's Reason version:

type a =
  | A
  | B
  | C;

type f =
  | F
  | G
  | H;

let matchType = (a, f) => 
  switch((a, f)) {
  | (A, F) => 1
  | (A, _) => 2
  | (B, G) => 3
  | (B, _) => 4
  | (C, H) => 5
  | (C, _) => 6
  };

Finding a way to match on tuples would indeed reduce a lot the boilerplate.

I'm not sure yet how to handle a few things regarding the implementation and API I have in mind, and also don't know which one is the best API.

Would be something like this

// `matchMany` or `matchTuple` ? or other ?
import  { matchMany, catchAll as _ } from '@housinganywhere/match'

type A = 'A' | 'B' | 'C'
type F = 'F' | 'G' | 'H'

type ToMatch = [A, F] // would most of the time be defined in line

matchMany<ToMatch, number>(
  [['A', 'F'], () => 1],
  [['A', _],   () => 2],
  [['B', 'G'], () => 3],
  [['B', _],   () => 4],
  [['C', 'H'], () => 5],
  [['C', _],   () => 6],
)('A', 'F') // 1

const isAF = matchMany<ToMatch, boolean>(
  [['A', 'F'], () => true],
  [[_, _],     () => false],
)

isAF('A', 'F') // true
isAF('B', 'F') // false

matchMany accepts a rest param, each one is a tuple of the ToMatch tuple and the function that is going to be used in case of the match.

Is this a good API? Any ideas for something better?

What needs to be solved:

  • Constrain the first type parameter to be a tuple
  • From that type parameter be able to infer the rest of the types

Considerations:

  • Now match is exhaustive, for this tuple match it's not possible (not that I know off with current state of TS). Should be handle the error or find a way to at least get one match?
  • Examples show a two items tuple, it should work for any tuple. But we could maybe hardcode it for 2, 3 and 4 (or 5?) if it's easier than for all.
  • How to type the catchAll, we could use any but doesn't feel like good idea 😕

Match number enums

Match number enums not working. I get error: Error:(8, 17) TS2344: Type 'ObjectStatus' does not satisfy the constraint 'string'.

Example:

export enum ObjectStatus {
    OPENED = 10,
    CLOSED = 20,
}

const x = match<ObjectStatus, string>({
});

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.