Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/gipphe/caseof
A case expression-like implementation for javascript
https://github.com/gipphe/caseof
caseof functional-programming javascript pattern-matching sanctuary
Last synced: 4 days ago
JSON representation
A case expression-like implementation for javascript
- Host: GitHub
- URL: https://github.com/gipphe/caseof
- Owner: Gipphe
- License: mit
- Created: 2018-08-01T14:05:17.000Z (over 6 years ago)
- Default Branch: main
- Last Pushed: 2023-04-21T10:58:32.000Z (almost 2 years ago)
- Last Synced: 2024-10-10T09:16:08.969Z (4 months ago)
- Topics: caseof, functional-programming, javascript, pattern-matching, sanctuary
- Language: TypeScript
- Homepage:
- Size: 426 KB
- Stars: 1
- Watchers: 3
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# caseof
> Elm and Haskell-inspired case-of
[![Package status](https://img.shields.io/npm/v/caseof.svg?style=flat-square)](https://www.npmjs.com/package/caseof)
[![Build status](https://img.shields.io/circleci/project/github/Gipphe/caseof.svg?style=flat-square)](https://circleci.com/gh/Gipphe/caseof)
[![Code coverage](https://img.shields.io/coveralls/Gipphe/caseof.svg?style=flat-square)](https://coveralls.io/github/Gipphe/caseof)
[![License](https://img.shields.io/github/license/Gipphe/caseof.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?style=flat-square)](https://conventionalcommits.org)A tiny package imitating a case-of expression from languages like Elm and Haskell.
- [Getting Started](#getting-started)
- [Installing](#installing)
- [Usage](#usage)
- [Motivation](#motivation)
- [Contribution](#contribution)
- [Compatibility](#compatibility)
- [Changelog](#changelog)
- [Versioning](#versioning)
- [License](#license)
- [Further reading](#further-reading)## Getting started
### Installing
Use the following command to add it to your project:
```shell
npm install --save caseof
```### Usage
### `otherwise :: a -> Boolean`
Simply a function that always returns true. Will always be considered a
"match", and its handler will always be executed if it is encountered.
It is mainly useful as a `default` case.```javascript
> otherwise ()
true> otherwise ('foo')
true> caseOf ((when) => {
. when (x => x < 20) (x => x + 10)
. when (otherwise) (() => 0)
. }) (23)
0
```### `caseOfAll :: ((a -> Boolean) -> (a -> b) -> Undefined) -> a -> Array b`
Returns the result of all matching cases' handlers. The order will be
the same order as `when` was called.```javascript
> caseOf.all ((when) => {
. when (x => typeof x === 'number') (x => x + 1)
. when (x => typeof x === 'number') (x => x - 0)
. }) (1)
[2, 0]> caseOf.all ((when) => {
. when (x => x > 0) (x => x - 1)
. when (x => x < 0) (x => x + 1)
. }) (-4)
[-3]
```Like `caseOf`, this function throws if none of the cases match.
```javascript
> caseOf.all ((when) => {
. when (x => x > 3) (x => x)
. }) (0)
! Error None of the cases matches the value
```### `caseOf :: ((a -> Boolean) -> (a -> b) -> Undefined) -> a -> b`
Returns the result of the first matching case. This function is lazy,
and only the first matching handler is run.```javascript
> caseOf ((when) => {
. when (x => x === 'foo') (x => x + 'bar')
. when (x => typeof x === 'string') (x => x.toUpperCase ())
. }) ('foo')
'foobar'> let fn = caseOf ((when) => {
. when (x => x % 15 === 0) (() => 'FizzBuzz')
. when (x => x % 3 === 0) (() => 'Fizz')
. when (x => x % 5 === 0) (() => 'Buzz')
. when (() => true) (x => x) // these two
. when (caseOf.otherwise) (x => x) // are equivalent
. })> fn (1)
1> fn (3)
'Fizz'> fn (5)
'Buzz'> fn (15)
'FizzBuzz'
```This function throws an error if none of the cases match.
```javascript
> caseOf ((when) => {
. when (x => x === 'foo') (x => x + 'bar')
. }) ('quack')
! Error: None of the cases matches the value
```## Motivation
Having used [sanctuary](https://sanctuary.js.org/) quite a lot, we found it
annoying having to repeat this kind of pattern:```javascript
function whatFoo(x) {
if (x === 3) {
return "foo";
}
if (x > 3) {
return "bar";
}
if (x < 3) {
return "foobar";
}
return "quack";
}
```Inevitably, even with monads encapsulating most of this kind of control flow
for you, you might inevitably find yourself repeating this kind of
"if-stacking" pattern.To me, this is rather verbose and ugly, and there are a lot of things being
repeated in terms of statements. Keep in mind though: there is absolutely
nothing inherently _wrong_ with this kind of "if-stacking".Because of this, and because we simply adore [Elm](http://elm-lang.org/) and
[Haskell](https://www.haskell.org/)'s `case x of` expressions (aka simply
"Case expressions"), we saw it necessary to implement something along those
lines in Javascript.So, instead of the "ugly" if-stacking above, we can write
```javascript
function whatFoo(x) {
return caseOf((when) => {
when((x) => x === 3)(() => "foo");
when((x) => x > 3)(() => "bar");
when((x) => x < 3)(() => "foobar");
when(() => true)(() => "quack");
})(x);
}
```But, as you can see, `caseOf` (and `caseOf.all`) is a curried function,
meaning is not a function that takes two arguments, like `function fn(x, y)`.
No no, it is a function that takes one argument, and then returns another
function, which subsequently takes another argument:```javascript
function fn(x) {
return function(y) {
...
}
}
```This allows us to just asign to `whatFoo`, instead of wrapping `caseOf`:
```javascript
var whatFoo =
caseOf((when) => {
when((x) => x === 3)(() => "foo");
when((x) => x > 3)(() => "bar");
when((x) => x < 3)(() => "foobar");
when(() => true)(() => "quack");
}) > whatFoo(3);
("foo");
```And the predicate functions can just be references, like when you partially
apply a function from sanctuary or ramda:```javascript
var S =
require("sanctuary") >
caseOf((when) => {
when(S.equals(3))(S.K("foo"));
when(S.gt(3))(S.K("bar"));
when(S.lt(3))(S.K("foobar"));
when(S.K(true))(S.K("quack"));
})(3);
("foo");
```Give it a little more breathing room,
[like you might do in a language where spaces are function application](https://github.com/sanctuary-js/sanctuary/issues/438),
and you've got yourself some easily readable code:```javascript
var S =
require("sanctuary") >
caseOf((when) => {
when(S.equals(3))(S.K("foo"));when(S.gt(3))(S.K("bar"));
when(S.lt(3))(S.K("foobar"));
when(S.K(true))(S.K("quack"));
})(3);
("foo");
```That is, "easily readable" if you're used to a more Haskell-like style. How
you want to style your code is up to you.## Contribution
This package is open to pull requests. To set up the development environment,
fork it, clone it, and run```shell
yarn
```in the project folder. This will install all necessary dependencies.
Run the command
```shell
yarn test
```to run unit tests.
This project uses a slightly altered variant of
[sanctuary-style](http://github.com/sanctuary-js/sanctuary-style). You can
lint the project using```shell
yarn lint
```## Compatibility
This package is compatible all the way down to Node 6 and IE9. It might be
compatible with older versions of Node/IE, but such guarantees cannot be made.## Type checking
This library features no actual type checking, other than that you call
`caseOf` and `caseOf.all` as curried functions, as well as that `when` is
passed actual functions as arguments.As previously mentioned in the "motivation" section, this library is heavily
inspired by the work of [sanctuary-js](https://github.com/sanctuary-js), but
there is one thing that is hard to integrate from their work: `sanctuary-def`
type definitions. `caseOf` technically has a type signature of
`caseOf :: ((a -> Boolean) -> (a -> b) -> Undefined) -> a -> b`, but this type
signature is in practice completely impossible to enforce, even with the help
of `sanctuary-def`. This is because the `when` function that is passed to the
first argument of `caseOf` cannot be expected to check type signatures of each
predicate and handler, given it only calls the handler of which the predicate
is satisfied. Meaning this```javascript
> let fn = caseOf ((when) => {
. when (x => x === 'foo') (x => x + 'bar')
. when (x => x > 3) (x => x - 10)
. })> fn (14)
4> fn ('foo')
'foobar'
```is perfectly valid, and will never complain, even if we utilized
`sanctuary-def` in some way (which we didn't). This is a similar problem you
will see with `sanctuary`'s own `ifElse`:```javascript
> let fn = S.ifElse
. (x => x !== 'foo')
. (() => 'bar')
. (() => 34)> fn ('foo')
34> fn (1)
'bar'
```This is valid according to its signature,
`ifElse :: (a -> Boolean) -> (a -> b) -> (a -> b) -> a -> b`, because in each
case, `b` isn't checked for the function that doesn't get called. For input
`'foo'`, `b` is `String`, but the result of the `else` function isn't checked
unless it is run (unless it is a function defined with `sanctuary-def`). And
for the second case, where input is `'bar'`, `b` is `Number`, but the `then`
function isn't checked.All in all, this is a limitation of runtime type checking.
## Versioning
We use [SemVer](http://semver.org/) for versioning. For the versions available,
see [the tags on this repository](https://github.com/Gipphe/caseof/tags).## License
This project is licensed under the MIT License - see the
[LICENSE.md file](https://github.com/Gipphe/caseof/blob/master/LICENSE.md) for
details.## Further reading
- [Elm Case-expression example](http://elm-lang.org/examples/case)
- [Haskell Case-expression examples](http://zvon.org/other/haskell/Outputsyntax/caseQexpressions_reference.html)