Add typescript/option
This commit is contained in:
commit
41d4b10b29
|
@ -3,4 +3,5 @@
|
|||
| Name | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| [lunardash](./lunardash/) | Rhythm game I dropped super early into development |
|
||||
| [option](./option/) | Typescript implementation of the `Maybe` monad |
|
||||
| [wave38](./wave38/) | Remake of [wave37](https://github.com/Mateiadrielrafael/wave37) I dropped super early into development. |
|
||||
|
|
25
typescript/option/.github/workflows/release.yml
vendored
Normal file
25
typescript/option/.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# check out repository code and setup node
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
# install dependencies and run semantic-release
|
||||
- run: npm i -g pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
- run: pnpm run build
|
||||
- run: pnpx semantic-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
4
typescript/option/.gitignore
vendored
Normal file
4
typescript/option/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
dist
|
||||
sandbox
|
||||
lib
|
5
typescript/option/.prettierrc
Normal file
5
typescript/option/.prettierrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": false,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true
|
||||
}
|
4
typescript/option/.vscode/settings.json
vendored
Normal file
4
typescript/option/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.formatOnSave": true
|
||||
}
|
39
typescript/option/LICENSE
Normal file
39
typescript/option/LICENSE
Normal file
|
@ -0,0 +1,39 @@
|
|||
The Prosperity Public License 2.0.0
|
||||
|
||||
Contributor: Matei Adriel
|
||||
|
||||
Source Code: https://github.com/Mateiadrielrafael/ecs
|
||||
|
||||
This license lets you use and share this software for free,
|
||||
with a trial-length time limit on commercial use. Specifically:
|
||||
|
||||
If you follow the rules below, you may do everything with this
|
||||
software that would otherwise infringe either the contributor's
|
||||
copyright in it, any patent claim the contributor can license
|
||||
that covers this software as of the contributor's latest
|
||||
contribution, or both.
|
||||
|
||||
1. You must limit use of this software in any manner primarily
|
||||
intended for or directed toward commercial advantage or
|
||||
private monetary compensation to a trial period of 32
|
||||
consecutive calendar days. This limit does not apply to use in
|
||||
developing feedback, modifications, or extensions that you
|
||||
contribute back to those giving this license.
|
||||
|
||||
2. Ensure everyone who gets a copy of this software from you, in
|
||||
source code or any other form, gets the text of this license
|
||||
and the contributor and source code lines above.
|
||||
|
||||
3. Do not make any legal claim against anyone for infringing any
|
||||
patent claim they would infringe by using this software alone,
|
||||
accusing this software, with or without changes, alone or as
|
||||
part of a larger application.
|
||||
|
||||
You are excused for unknowingly breaking rule 1 if you stop
|
||||
doing anything requiring this license within 30 days of
|
||||
learning you broke the rule.
|
||||
|
||||
**This software comes as is, without any warranty at all. As far
|
||||
as the law allows, the contributor will not be liable for any
|
||||
damages related to this software or this license, for any kind of
|
||||
legal claim.**
|
71
typescript/option/README.md
Normal file
71
typescript/option/README.md
Normal file
|
@ -0,0 +1,71 @@
|
|||
![npm (scoped)](https://img.shields.io/npm/v/@adrielus/option?style=for-the-badge)
|
||||
![npm bundle size (scoped)](https://img.shields.io/bundlephobia/minzip/@adrielus/option?style=for-the-badge)
|
||||
[![forthebadge](https://forthebadge.com/images/badges/powered-by-water.svg)](https://forthebadge.com)
|
||||
|
||||
# Option
|
||||
|
||||
Probably the most opinionated implementation of the Option type for TypeScript.
|
||||
|
||||
## Features:
|
||||
|
||||
- Lazy and async versions of helpers:
|
||||
One of the goals of this lib is to provide variations of helpers which are lazy (don't compute something if it's not needed) or async (make mixing Promises and Options easier). If there is any function you want one of those variations of, be sure to open an issue:)
|
||||
- Large amount of helpers (curently 30), more than f#'s and elm's core libraries combined.
|
||||
- Typesafe:
|
||||
```ts
|
||||
const foo0: Option<string> = None // works
|
||||
const foo1: Option<string> = Some('foo1') // works
|
||||
const foo2: Option<string> = 'foo2' // errors out
|
||||
const foo3: Option<string> = null // errors out
|
||||
const foo4: Option<string> = Some(4) // errors out
|
||||
```
|
||||
- Reference equality:
|
||||
```ts
|
||||
Some(7) === Some(7) // true
|
||||
Some(7) === Some(5) // false
|
||||
Some(7) === None // false
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
Both limitaions bellow come from the lack of nominal-typing offered by TypeScript and are inherited from the `Brand` type offered by the [utility-types](https://github.com/piotrwitek/utility-types) library
|
||||
|
||||
- Due to the way the library works (using the `Brand`
|
||||
type from [utility-types](https://github.com/piotrwitezutility-types)) `Some(4) === 4` will return true, similarly to how `4 == "4"` returns true (except in this libraries case the `===` operator will behave the same way).
|
||||
- The inner value of `Option` cannot have a `__brand` prop
|
||||
(well, tehnically it can but it would be overwritten by the `Brand` type from [utility-types](https://github.com/piotrwitek/utility-types))
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install @adrielus/option
|
||||
```
|
||||
|
||||
(There is also an amd build at `/dist/bundle.umd.js` which uses the `Option` namespace)
|
||||
|
||||
## Usage
|
||||
|
||||
For detailed usage read [the docs](https://github.com/Mateiadrielrafael/option/tree/master/docs/main.md)
|
||||
|
||||
> Note: The docs are still work in progress. Contributions are welcome:)
|
||||
|
||||
# Contributing
|
||||
|
||||
First, clone this repo:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Mateiadrielrafael/option
|
||||
cd option
|
||||
```
|
||||
|
||||
Then use **_pnpm_** to install the dependencies:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
You can use the `build` command to build the package (this is dont automatically by github actions):
|
||||
|
||||
```sh
|
||||
pnpm run build
|
||||
```
|
230
typescript/option/docs/main.md
Normal file
230
typescript/option/docs/main.md
Normal file
|
@ -0,0 +1,230 @@
|
|||
# Documentation
|
||||
|
||||
## Table of contents:
|
||||
|
||||
### General:
|
||||
|
||||
- [Option](#Option)
|
||||
- [Some](#Some)
|
||||
- [None](#None)
|
||||
|
||||
### Helpers:
|
||||
|
||||
- [bind](#Bind)
|
||||
- [count](#Count)
|
||||
- [exists](#Exists)
|
||||
- [filter](#Filter)
|
||||
- [fold](#Fold)
|
||||
- [foldback](#Foldback)
|
||||
- [forall](#Forall)
|
||||
- [fromNullable](#FromNullable)
|
||||
- [fromArray](#FromArray)
|
||||
|
||||
# General
|
||||
|
||||
## Option
|
||||
|
||||
Data type holding an optional value (can be either None or Some(x))
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
type Option<T> = Internals.SomeClass<T> | Internals.NoneClass
|
||||
```
|
||||
|
||||
## None
|
||||
|
||||
Value holding nothing
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const None: Internals.NoneClass
|
||||
```
|
||||
|
||||
## Some
|
||||
|
||||
Creates an Option instance holding a value
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const Some: <T>(v: T) => Internals.SomeClass<T>
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some } from '@adrielus/option'
|
||||
|
||||
Some(x) // Some(x)
|
||||
```
|
||||
|
||||
# Helpers
|
||||
|
||||
## Bind
|
||||
|
||||
Invokes a function on an optional value that itself yields an option.
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const bind: <T, U>(binder: Mapper<T, Option<U>>, option: Option<T>) => Option<U>
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, bind } from '@adrielus/option'
|
||||
|
||||
const half = (x: number) => (x % 2 ? None : Some(x / 2))
|
||||
|
||||
bind(half, Some(14)) // Some(7)
|
||||
bind(half, Some(13)) // None
|
||||
bind(half, None) // None
|
||||
```
|
||||
|
||||
## Count
|
||||
|
||||
Returns a zero if the option is None, a one otherwise.
|
||||
|
||||
### Signature:
|
||||
|
||||
```ts
|
||||
const count: <T>(option: Option<T>) => number
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, count } from '@adrielus/option'
|
||||
|
||||
count(Some(x)) // 1
|
||||
count(None) // 0
|
||||
```
|
||||
|
||||
## Exists
|
||||
|
||||
Returns false if the option is None, otherwise it returns the result of applying the predicate to the option value.
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const exists: <T>(predicate: Mapper<T, boolean>, option: Option<T>) => boolean
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, exists } from '@adrielus/option'
|
||||
|
||||
exists(() => true, None) // false
|
||||
exists(() => true, Some(x)) // true
|
||||
exists(() => false, Some(x)) // false
|
||||
```
|
||||
|
||||
## Filter
|
||||
|
||||
Invokes a function on an optional value that itself yields an option.
|
||||
|
||||
### Signature:
|
||||
|
||||
```ts
|
||||
const filter: <T>(predicate: Mapper<T, boolean>, option: Option<T>) => NoneClass
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, filter } from '@adrielus/option'
|
||||
|
||||
filter(() => true, None) // None
|
||||
filter(() => true, Some(x)) // Some(x)
|
||||
filter(() => false, Some(x)) // None
|
||||
```
|
||||
|
||||
## Fold
|
||||
|
||||
A function to update the state data when given a value from an option.
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const fold: <T, U>(folder: Folder<T, U>, initial: U, option: Option<T>) => U
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, fold } from '@adrielus/option'
|
||||
|
||||
const add = (a: number, b: number) => a + b
|
||||
|
||||
fold(add, x, None) // x
|
||||
fold(add, x, Some(y)) // x + y
|
||||
```
|
||||
|
||||
## Foldback
|
||||
|
||||
A function to update the state data when given a value from an option.
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const foldback: <T, U>(
|
||||
folder: BackFolder<T, U>,
|
||||
option: Option<T>,
|
||||
initial: U
|
||||
) => U
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, foldback } from '@adrielus/option'
|
||||
|
||||
const add = (a: number, b: number) => a + b
|
||||
|
||||
foldback(add, None, x) // x
|
||||
foldback(add, Some(y), x) // x + y
|
||||
```
|
||||
|
||||
# FromNullable
|
||||
|
||||
A function to create options from nullable values.
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const fromNullable: <T>(value: Nullable<T>) => Option<T>
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, fromNullable } from '@adrielus/option'
|
||||
|
||||
fromNullable(7) // Some(7)
|
||||
fromNullable(null) // None
|
||||
```
|
||||
|
||||
## FromArray
|
||||
|
||||
A function to create options from arrays. If the given array is empty produces None, else Some of the first element.
|
||||
|
||||
### Signature
|
||||
|
||||
```ts
|
||||
const fromArray: <T>(value: [T] | []) => Option<T>
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Some, None, fromArray } from '@adrielus/option'
|
||||
|
||||
fromArray([7]) // Some(7)
|
||||
fromArray([]) // None
|
||||
```
|
||||
|
||||
**_This is still work in progress, right now only covering about 60% of the library. Contributions are welcome_**
|
68
typescript/option/package.json
Normal file
68
typescript/option/package.json
Normal file
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"name": "@adrielus/option",
|
||||
"version": "0.0.0-development",
|
||||
"description": "Typescript version of fsharps Option module",
|
||||
"main": "dist/bundle.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"typings": "dist/index.esm.d.ts",
|
||||
"browser": "dist/bundle.umd.js",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "rollup -c rollup.config.ts",
|
||||
"test": "mocha -r ts-node/register src/**/*.test.ts"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"typescript",
|
||||
"fsharp",
|
||||
"fp",
|
||||
"functional-programming",
|
||||
"monad",
|
||||
"immutable",
|
||||
"stateless",
|
||||
"classless",
|
||||
"option",
|
||||
"typesafe",
|
||||
"functor",
|
||||
"pure",
|
||||
"option",
|
||||
"some",
|
||||
"just",
|
||||
"none",
|
||||
"nothing",
|
||||
"maybe",
|
||||
"nullable"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^11.0.0",
|
||||
"@rollup/plugin-node-resolve": "^6.0.0",
|
||||
"@types/chai": "^4.2.7",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.12.21",
|
||||
"@types/sinon": "^7.5.1",
|
||||
"@wessberg/rollup-plugin-ts": "^1.1.83",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^6.2.2",
|
||||
"rimraf": "^3.0.0",
|
||||
"rollup": "^1.27.14",
|
||||
"rollup-plugin-filesize": "^6.2.1",
|
||||
"rollup-plugin-terser": "^5.1.3",
|
||||
"semantic-release": "^15.14.0",
|
||||
"sinon": "^8.0.1",
|
||||
"ts-node": "^8.5.4",
|
||||
"typescript": "^3.7.4"
|
||||
},
|
||||
"author": "Matei Adriel",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@thi.ng/compose": "^1.3.6",
|
||||
"tslib": "^1.10.0",
|
||||
"utility-types": "^3.10.0"
|
||||
}
|
||||
}
|
4317
typescript/option/pnpm-lock.yaml
Normal file
4317
typescript/option/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
58
typescript/option/rollup.config.ts
Normal file
58
typescript/option/rollup.config.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { terser } from 'rollup-plugin-terser'
|
||||
import { resolve } from 'path'
|
||||
import ts from '@wessberg/rollup-plugin-ts'
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import _package from './package.json'
|
||||
import filesize from 'rollup-plugin-filesize'
|
||||
|
||||
const inputFile = resolve(__dirname, 'src/index.ts')
|
||||
|
||||
const dev = Boolean(process.env.ROLLUP_WATCH)
|
||||
|
||||
const commonPlugins = [nodeResolve(), commonjs()]
|
||||
|
||||
export default [
|
||||
{
|
||||
input: inputFile,
|
||||
output: [
|
||||
{
|
||||
file: _package.main,
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
},
|
||||
{
|
||||
file: _package.browser,
|
||||
sourcemap: true,
|
||||
format: 'umd',
|
||||
name: 'Option'
|
||||
}
|
||||
],
|
||||
plugins: [...commonPlugins, ts(), !dev && terser()]
|
||||
},
|
||||
{
|
||||
input: inputFile,
|
||||
output: [
|
||||
{
|
||||
file: _package.module,
|
||||
format: 'esm',
|
||||
sourcemap: true
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
...commonPlugins,
|
||||
ts({
|
||||
tsconfig: {
|
||||
declaration: true,
|
||||
...require(resolve(__dirname, 'tsconfig.json'))[
|
||||
'compilerOptions'
|
||||
]
|
||||
}
|
||||
}),
|
||||
!dev && terser(),
|
||||
filesize({
|
||||
showBrotliSize: true
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
32
typescript/option/src/helpers/bind.test.ts
Normal file
32
typescript/option/src/helpers/bind.test.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { expect } from 'chai'
|
||||
import { bind } from './bind'
|
||||
import { Some, None } from '../types'
|
||||
import { constantly } from '@thi.ng/compose'
|
||||
|
||||
describe('The bind helper', () => {
|
||||
it('should return None for any callback when given None', () => {
|
||||
// act
|
||||
const result = bind(Some, None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
describe('When given Some', () => {
|
||||
it('should return None if the callback returns None', () => {
|
||||
// act
|
||||
const result = bind(constantly(None), Some(3))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
it('should return Some if the callback returns Some', () => {
|
||||
// act
|
||||
const result = bind(x => Some(x + 1), Some(3))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(Some(4))
|
||||
})
|
||||
})
|
||||
})
|
10
typescript/option/src/helpers/bind.ts
Normal file
10
typescript/option/src/helpers/bind.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Binder } from '../internalTypes'
|
||||
import { Option, None } from '../types'
|
||||
import { unwrap } from './unwrap'
|
||||
|
||||
export const bind = <T, U>(
|
||||
binder: Binder<T, U>,
|
||||
option: Option<T>
|
||||
): Option<U> => {
|
||||
return unwrap(None, binder, option)
|
||||
}
|
31
typescript/option/src/helpers/bindAsync.test.ts
Normal file
31
typescript/option/src/helpers/bindAsync.test.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { expect } from 'chai'
|
||||
import { Some, None } from '../types'
|
||||
import { bindAsync } from './bindAsync'
|
||||
|
||||
describe('The bindAsync helper', () => {
|
||||
it('should return None for any callback when given None', async () => {
|
||||
// act
|
||||
const result = await bindAsync(async v => Some(v), None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
describe('When given Some', () => {
|
||||
it('should return None if the callback returns None', async () => {
|
||||
// act
|
||||
const result = await bindAsync(async _ => None, Some(3))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
it('should return Some if the callback returns Some', async () => {
|
||||
// act
|
||||
const result = await bindAsync(async x => Some(x + 1), Some(3))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(Some(4))
|
||||
})
|
||||
})
|
||||
})
|
10
typescript/option/src/helpers/bindAsync.ts
Normal file
10
typescript/option/src/helpers/bindAsync.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Mapper } from '../internalTypes'
|
||||
import { Option, None } from '../types'
|
||||
import { unwrap } from './unwrap'
|
||||
|
||||
export const bindAsync = <T, U>(
|
||||
binder: Mapper<T, Promise<Option<U>>>,
|
||||
option: Option<T>
|
||||
): Promise<Option<U>> => {
|
||||
return unwrap(Promise.resolve(None), binder, option)
|
||||
}
|
26
typescript/option/src/helpers/combine.test.ts
Normal file
26
typescript/option/src/helpers/combine.test.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { expect } from 'chai'
|
||||
import { combine } from './combine'
|
||||
import { someX } from '../../test/constants'
|
||||
import { None } from '../types'
|
||||
import { isSome } from './isSome'
|
||||
|
||||
describe('The combine helepr', () => {
|
||||
it('should return None if the iterable contains any Nones', () => {
|
||||
// act
|
||||
const result = combine([someX, someX, None, someX, someX])
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
it("should return Some when the iterable doesn't contain any None", () => {
|
||||
// arrange
|
||||
const items = Array(50).fill(someX)
|
||||
|
||||
// act
|
||||
const result = combine(items)
|
||||
|
||||
// act
|
||||
expect(isSome(result)).to.be.true
|
||||
})
|
||||
})
|
19
typescript/option/src/helpers/combine.ts
Normal file
19
typescript/option/src/helpers/combine.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Option, None, Some } from '../types'
|
||||
import { isNone } from './isNone'
|
||||
|
||||
/**
|
||||
* If every Option in the list is present, return all of the values unwrapped.
|
||||
* If there are any Nones, the whole function fails and returns None.
|
||||
*
|
||||
* @param iterable The iterable to combine.
|
||||
*/
|
||||
export const combine = <T>(iterable: Iterable<Option<T>>) => {
|
||||
const set = new Set(iterable)
|
||||
|
||||
if (set.has(None)) {
|
||||
return None
|
||||
}
|
||||
|
||||
const array = Array.from(set) as T[]
|
||||
return array as Option<T[]>
|
||||
}
|
22
typescript/option/src/helpers/count.test.ts
Normal file
22
typescript/option/src/helpers/count.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { expect } from 'chai'
|
||||
import { Some, None } from '../types'
|
||||
import { count } from './count'
|
||||
import { x } from '../../test/constants'
|
||||
|
||||
describe('The count helper', () => {
|
||||
it('should return 1 when given Some', () => {
|
||||
// act
|
||||
const result = count(Some(x))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(1)
|
||||
})
|
||||
|
||||
it('should return 0 when given None', () => {
|
||||
// act
|
||||
const result = count(None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(0)
|
||||
})
|
||||
})
|
4
typescript/option/src/helpers/count.ts
Normal file
4
typescript/option/src/helpers/count.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { isSome } from './isSome'
|
||||
import { Option } from '../types'
|
||||
|
||||
export const count = <T>(option: Option<T>) => Number(isSome(option))
|
33
typescript/option/src/helpers/exists.test.ts
Normal file
33
typescript/option/src/helpers/exists.test.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { expect } from 'chai'
|
||||
import { exists } from './exists'
|
||||
import { constantly } from '@thi.ng/compose'
|
||||
import { None, Some } from '../types'
|
||||
import { x } from '../../test/constants'
|
||||
|
||||
describe('The exists helper', () => {
|
||||
it('should return false when given None', () => {
|
||||
// act
|
||||
const result = exists(constantly(true), None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(false)
|
||||
})
|
||||
|
||||
describe('When given Some', () => {
|
||||
it('should return true if the callback returns true', () => {
|
||||
// act
|
||||
const result = exists(constantly(true), Some(x))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(true)
|
||||
})
|
||||
|
||||
it('should return false if the callback returns false', () => {
|
||||
// act
|
||||
const result = exists(constantly(false), Some(x))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
7
typescript/option/src/helpers/exists.ts
Normal file
7
typescript/option/src/helpers/exists.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { Predicate } from '../internalTypes'
|
||||
import { Option } from '../types'
|
||||
|
||||
export const exists = <T>(predicate: Predicate<T>, option: Option<T>) => {
|
||||
return unwrap(false, predicate, option)
|
||||
}
|
30
typescript/option/src/helpers/external.ts
Normal file
30
typescript/option/src/helpers/external.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
export * from './bind'
|
||||
export * from './bindAsync'
|
||||
export * from './combine'
|
||||
export * from './count'
|
||||
export * from './exists'
|
||||
export * from './filter'
|
||||
export * from './first'
|
||||
export * from './flat'
|
||||
export * from './fold'
|
||||
export * from './foldback'
|
||||
export * from './forall'
|
||||
export * from './fromArray'
|
||||
export * from './fromNullable'
|
||||
export * from './get'
|
||||
export * from './isNone'
|
||||
export * from './isSome'
|
||||
export * from './iter'
|
||||
export * from './map'
|
||||
export * from './mapAsync'
|
||||
export * from './oneOf'
|
||||
export * from './optionify'
|
||||
export * from './or'
|
||||
export * from './orLazy'
|
||||
export * from './toArray'
|
||||
export * from './toNullable'
|
||||
export * from './unpack'
|
||||
export * from './unwrap'
|
||||
export * from './values'
|
||||
export * from './withDefault'
|
||||
export * from './withDefaultLazy'
|
47
typescript/option/src/helpers/filter.test.ts
Normal file
47
typescript/option/src/helpers/filter.test.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { expect } from 'chai'
|
||||
import { constantly } from '@thi.ng/compose'
|
||||
import { filter } from './filter'
|
||||
import { None, Some } from '../types'
|
||||
import { someX } from '../../test/constants'
|
||||
|
||||
describe('The filter helper', () => {
|
||||
describe('When the predicate returns true', () => {
|
||||
const predicate = constantly(true)
|
||||
|
||||
it('should return None when given None', () => {
|
||||
// act
|
||||
const result = filter(predicate, None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
it('should return Some(x) when given Some(x)', () => {
|
||||
// act
|
||||
const result = filter(predicate, someX)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(someX)
|
||||
})
|
||||
})
|
||||
|
||||
describe('When the predicate returns false', () => {
|
||||
const predicate = constantly(false)
|
||||
|
||||
it('should return None when given Some', () => {
|
||||
// act
|
||||
const result = filter(predicate, someX)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
it('should return None when given None', () => {
|
||||
// act
|
||||
const result = filter(predicate, None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
})
|
||||
})
|
7
typescript/option/src/helpers/filter.ts
Normal file
7
typescript/option/src/helpers/filter.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { Some, None, Option } from '../types'
|
||||
import { Predicate } from '../internalTypes'
|
||||
|
||||
export const filter = <T>(predicate: Predicate<T>, option: Option<T>) => {
|
||||
return unwrap(None, v => (predicate(v) ? Some(v) : None), option)
|
||||
}
|
32
typescript/option/src/helpers/first.test.ts
Normal file
32
typescript/option/src/helpers/first.test.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { constantly } from '@thi.ng/compose'
|
||||
import { expect } from 'chai'
|
||||
import { someX } from '../../test/constants'
|
||||
import { None, Option } from '../types'
|
||||
import { first } from './first'
|
||||
|
||||
describe('The first helper', () => {
|
||||
it('should return the first Some if there is any', () => {
|
||||
// act
|
||||
const head = first([someX, None])
|
||||
const middle = first([None, someX, None])
|
||||
const tail = first([None, someX])
|
||||
|
||||
// assert
|
||||
expect(head).to.equal(someX)
|
||||
expect(middle).to.equal(someX)
|
||||
expect(tail).to.equal(someX)
|
||||
})
|
||||
|
||||
it("should return None if there isn't any Some", () => {
|
||||
// arrange
|
||||
const array: Option<unknown>[] = Array(50)
|
||||
.fill(1)
|
||||
.map(constantly(None))
|
||||
|
||||
// act
|
||||
const result = first(array)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
})
|
17
typescript/option/src/helpers/first.ts
Normal file
17
typescript/option/src/helpers/first.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { isSome } from './isSome'
|
||||
import { Option, None } from '../types'
|
||||
|
||||
/**
|
||||
* Returns the first Some in an iterable. If there isn't any returns None.
|
||||
*
|
||||
* @param elemenets The elements to find the first Some in.
|
||||
*/
|
||||
export const first = <T>(elemenets: Iterable<Option<T>>) => {
|
||||
for (const option of elemenets) {
|
||||
if (isSome(option)) {
|
||||
return option
|
||||
}
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
25
typescript/option/src/helpers/flat.test.ts
Normal file
25
typescript/option/src/helpers/flat.test.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { expect } from 'chai'
|
||||
import { flat } from './flat'
|
||||
import { None, Some } from '../types'
|
||||
import { someX } from '../../test/constants'
|
||||
|
||||
describe('The flat helper', () => {
|
||||
it('should return None when given None', () => {
|
||||
// act
|
||||
const result = flat(None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
it('should return the inner Some(x) when given Some(Some(x))', () => {
|
||||
// arrange
|
||||
const value = Some(someX)
|
||||
|
||||
// act
|
||||
const result = flat(value)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(someX)
|
||||
})
|
||||
})
|
7
typescript/option/src/helpers/flat.ts
Normal file
7
typescript/option/src/helpers/flat.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { bind } from './bind'
|
||||
import { identity } from '@thi.ng/compose'
|
||||
import { Option } from '../types'
|
||||
|
||||
export const flat = <T>(option: Option<Option<T>>): Option<T> => {
|
||||
return bind(identity, option)
|
||||
}
|
11
typescript/option/src/helpers/fold.ts
Normal file
11
typescript/option/src/helpers/fold.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { Option } from '../types'
|
||||
import { Folder } from '../internalTypes'
|
||||
|
||||
export const fold = <T, U>(
|
||||
folder: Folder<T, U>,
|
||||
initial: U,
|
||||
option: Option<T>
|
||||
) => {
|
||||
return unwrap(initial, v => folder(initial, v), option)
|
||||
}
|
11
typescript/option/src/helpers/foldback.ts
Normal file
11
typescript/option/src/helpers/foldback.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { Option } from '../types'
|
||||
import { BackFolder } from '../internalTypes'
|
||||
|
||||
export const foldback = <T, U>(
|
||||
folder: BackFolder<T, U>,
|
||||
option: Option<T>,
|
||||
initial: U
|
||||
) => {
|
||||
return unwrap(initial, v => folder(v, initial), option)
|
||||
}
|
7
typescript/option/src/helpers/forall.ts
Normal file
7
typescript/option/src/helpers/forall.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { Predicate } from '../internalTypes'
|
||||
import { Option } from '../types'
|
||||
|
||||
export const forall = <T>(predicate: Predicate<T>, option: Option<T>) => {
|
||||
return unwrap(true, predicate, option)
|
||||
}
|
5
typescript/option/src/helpers/fromArray.ts
Normal file
5
typescript/option/src/helpers/fromArray.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { None, Some, Option } from '../types'
|
||||
|
||||
export const fromArray = <T>(value: [T] | []): Option<T> => {
|
||||
return value[0] === undefined ? None : Some(value[0])
|
||||
}
|
6
typescript/option/src/helpers/fromNullable.ts
Normal file
6
typescript/option/src/helpers/fromNullable.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { Nullable } from '../internalTypes'
|
||||
import { Some, None, Option } from '../types'
|
||||
|
||||
export const fromNullable = <T>(value: Nullable<T>): Option<T> => {
|
||||
return value === null ? None : Some(value)
|
||||
}
|
22
typescript/option/src/helpers/get.test.ts
Normal file
22
typescript/option/src/helpers/get.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { expect } from 'chai'
|
||||
import { get } from './get'
|
||||
import { None } from '../types'
|
||||
import { someX, x } from '../../test/constants'
|
||||
|
||||
describe('The get helper', () => {
|
||||
it('should throw when given None', () => {
|
||||
// act
|
||||
const callable = () => get(None)
|
||||
|
||||
// assert
|
||||
expect(callable).to.throw()
|
||||
})
|
||||
|
||||
it('should return the innter value when given Some', () => {
|
||||
// act
|
||||
const result = get(someX)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(x)
|
||||
})
|
||||
})
|
10
typescript/option/src/helpers/get.ts
Normal file
10
typescript/option/src/helpers/get.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Option } from '../types'
|
||||
import { isSome } from './isSome'
|
||||
|
||||
export const get = <T>(option: Option<T>): T => {
|
||||
if (isSome(option)) {
|
||||
return option as T
|
||||
}
|
||||
|
||||
throw new Error(`Cannot get value of None`)
|
||||
}
|
22
typescript/option/src/helpers/isNone.test.ts
Normal file
22
typescript/option/src/helpers/isNone.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { expect } from 'chai'
|
||||
import { isNone } from './isNone'
|
||||
import { None } from '../types'
|
||||
import { someX } from '../../test/constants'
|
||||
|
||||
describe('The isNone helper', () => {
|
||||
it('should return false when given Some', () => {
|
||||
// act
|
||||
const result = isNone(someX)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return true when given None', () => {
|
||||
// act
|
||||
const result = isNone(None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(true)
|
||||
})
|
||||
})
|
4
typescript/option/src/helpers/isNone.ts
Normal file
4
typescript/option/src/helpers/isNone.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { Option } from '../types'
|
||||
import { none } from '../internals'
|
||||
|
||||
export const isNone = <T>(option: Option<T>) => option.__brand === none
|
22
typescript/option/src/helpers/isSome.test.ts
Normal file
22
typescript/option/src/helpers/isSome.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { expect } from 'chai'
|
||||
import { None } from '../types'
|
||||
import { someX } from '../../test/constants'
|
||||
import { isSome } from './isSome'
|
||||
|
||||
describe('The isSome helper', () => {
|
||||
it('should return true when given Some', () => {
|
||||
// act
|
||||
const result = isSome(someX)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(true)
|
||||
})
|
||||
|
||||
it('should return false when given None', () => {
|
||||
// act
|
||||
const result = isSome(None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(false)
|
||||
})
|
||||
})
|
4
typescript/option/src/helpers/isSome.ts
Normal file
4
typescript/option/src/helpers/isSome.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { Option } from '../types'
|
||||
import { isNone } from './isNone'
|
||||
|
||||
export const isSome = <T>(option: Option<T>) => !isNone(option)
|
9
typescript/option/src/helpers/iter.ts
Normal file
9
typescript/option/src/helpers/iter.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Mapper } from '../internalTypes'
|
||||
import { Option } from '../types'
|
||||
import { isSome } from './isSome'
|
||||
|
||||
export const iter = <T>(mapper: Mapper<T, void>, option: Option<T>) => {
|
||||
if (isSome(option)) {
|
||||
mapper(option as T)
|
||||
}
|
||||
}
|
10
typescript/option/src/helpers/map.ts
Normal file
10
typescript/option/src/helpers/map.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { Mapper } from '../internalTypes'
|
||||
import { Option, Some, None } from '../types'
|
||||
|
||||
export const map = <T, U>(
|
||||
mapper: Mapper<T, U>,
|
||||
option: Option<T>
|
||||
): Option<U> => {
|
||||
return unwrap(None, v => Some(mapper(v)), option)
|
||||
}
|
14
typescript/option/src/helpers/mapAsync.ts
Normal file
14
typescript/option/src/helpers/mapAsync.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Option, None, Some } from '../types'
|
||||
import { Mapper } from '../internalTypes'
|
||||
import { unwrap } from './unwrap'
|
||||
|
||||
export const mapAsync = <T, U>(
|
||||
mapper: Mapper<T, Promise<U>>,
|
||||
option: Option<T>
|
||||
) => {
|
||||
return unwrap(
|
||||
Promise.resolve(None),
|
||||
value => mapper(value).then(Some),
|
||||
option
|
||||
)
|
||||
}
|
55
typescript/option/src/helpers/oneOf.test.ts
Normal file
55
typescript/option/src/helpers/oneOf.test.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { constantly } from '@thi.ng/compose'
|
||||
import { expect } from 'chai'
|
||||
import { spy } from 'sinon'
|
||||
import { x, alwaysSomeX } from '../../test/constants'
|
||||
import { None, Some } from '../types'
|
||||
import { oneOf } from './oneOf'
|
||||
|
||||
const alwaysSome = <T>(v: T) => constantly(Some(v))
|
||||
|
||||
describe('The oneOf helper', () => {
|
||||
it('should return None on an empty array', () => {
|
||||
// act
|
||||
const result = oneOf(x, [])
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(None)
|
||||
})
|
||||
|
||||
it('should return the result of the first function which evaluates to Some', () => {
|
||||
// arrange
|
||||
const alwaysNone = constantly(None)
|
||||
|
||||
// act
|
||||
const head = oneOf(x, [alwaysSome('head'), alwaysNone])
|
||||
const middle = oneOf(x, [alwaysNone, alwaysSome('middle'), alwaysNone])
|
||||
const tail = oneOf(x, [alwaysNone, alwaysSome('tail')])
|
||||
|
||||
// assert
|
||||
expect(head).to.equal(Some('head'))
|
||||
expect(middle).to.equal(Some('middle'))
|
||||
expect(tail).to.equal(Some('tail'))
|
||||
})
|
||||
|
||||
it('should not evaluate any more functions after it found the result', () => {
|
||||
// arrange
|
||||
const func = spy(alwaysSomeX)
|
||||
|
||||
// act
|
||||
oneOf(x, [alwaysSomeX, func])
|
||||
|
||||
// assert
|
||||
expect(func.called).to.be.false
|
||||
})
|
||||
|
||||
it('should pass the provided input to the functions', () => {
|
||||
// arrange
|
||||
const func = spy(alwaysSomeX)
|
||||
|
||||
// act
|
||||
oneOf(x, [func])
|
||||
|
||||
// assert
|
||||
expect(func.calledWith(x)).to.be.true
|
||||
})
|
||||
})
|
23
typescript/option/src/helpers/oneOf.ts
Normal file
23
typescript/option/src/helpers/oneOf.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Binder } from '../internalTypes'
|
||||
import { isSome } from './isSome'
|
||||
import { None } from '../types'
|
||||
|
||||
/**
|
||||
* Try a list of functions against a value.
|
||||
* Return the value of the first call that succeeds (aka returns Some).
|
||||
* If no function retursn Some this will default to None.
|
||||
*
|
||||
* @param input The input to pass to the functions.
|
||||
* @param functions Iterable of functions to try against the input.
|
||||
*/
|
||||
export const oneOf = <T, U>(input: T, functions: Binder<T, U>[]) => {
|
||||
for (const func of functions) {
|
||||
const result = func(input)
|
||||
|
||||
if (isSome(result)) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
17
typescript/option/src/helpers/optionify.test.ts
Normal file
17
typescript/option/src/helpers/optionify.test.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { expect } from 'chai'
|
||||
import { optionify } from './optionify'
|
||||
import { fromNullable } from './fromNullable'
|
||||
|
||||
describe('The optionify helper', () => {
|
||||
it('should create a function which returns an option instead of a nullable', () => {
|
||||
// arrange
|
||||
const func = (a: number, b: number) => (a > b ? a + b : null)
|
||||
|
||||
// act
|
||||
const result = optionify(func)
|
||||
|
||||
// assert
|
||||
expect(result(1, 2)).to.equal(fromNullable(func(1, 2)))
|
||||
expect(result(2, 1)).to.equal(fromNullable(func(2, 1)))
|
||||
})
|
||||
})
|
15
typescript/option/src/helpers/optionify.ts
Normal file
15
typescript/option/src/helpers/optionify.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { fromNullable } from './fromNullable'
|
||||
|
||||
/**
|
||||
* Takes a function which returns a nullable and creates
|
||||
* a function which returns an Option.
|
||||
* In functional programming this would be the same as
|
||||
* composing the function with fromNullable.
|
||||
*
|
||||
* @param f The function to optionify
|
||||
*/
|
||||
export const optionify = <T extends unknown[], U>(
|
||||
f: (...args: T) => U | null
|
||||
) => {
|
||||
return (...args: T) => fromNullable(f(...args))
|
||||
}
|
26
typescript/option/src/helpers/or.test.ts
Normal file
26
typescript/option/src/helpers/or.test.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { expect } from 'chai'
|
||||
import { or } from './or'
|
||||
import { someX } from '../../test/constants'
|
||||
import { None } from '../types'
|
||||
|
||||
describe('The or helper', () => {
|
||||
describe('When the first argument is None', () => {
|
||||
it('should return the second argument', () => {
|
||||
// act
|
||||
const orSome = or(None, someX)
|
||||
const orNone = or(None, None)
|
||||
|
||||
// assert
|
||||
expect(orSome).to.equal(someX)
|
||||
expect(orNone).to.equal(None)
|
||||
})
|
||||
})
|
||||
|
||||
it("should return the first argument when it's not None", () => {
|
||||
// act
|
||||
const result = or(someX, None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(someX)
|
||||
})
|
||||
})
|
20
typescript/option/src/helpers/or.ts
Normal file
20
typescript/option/src/helpers/or.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Option } from '../types'
|
||||
import { isSome } from './isSome'
|
||||
|
||||
/**
|
||||
* Returns the first value that is present, like the boolean ||.
|
||||
* Both values will be computed.
|
||||
* There is no short-circuiting.
|
||||
* If your second argument is expensive to calculate and
|
||||
* you need short circuiting, use orLazy instead.
|
||||
*
|
||||
* @param a The first argument.
|
||||
* @param b The second argument.
|
||||
*/
|
||||
export const or = <T>(a: Option<T>, b: Option<T>): Option<T> => {
|
||||
if (isSome(a)) {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
48
typescript/option/src/helpers/orLazy.test.ts
Normal file
48
typescript/option/src/helpers/orLazy.test.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { expect } from 'chai'
|
||||
import { constantly } from '@thi.ng/compose'
|
||||
import { orLazy } from './orLazy'
|
||||
import { someX } from '../../test/constants'
|
||||
import { None } from '../types'
|
||||
import { spy } from 'sinon'
|
||||
|
||||
describe('The orLazy helper', () => {
|
||||
it("should return the first argument if it's Some", () => {
|
||||
// act
|
||||
const result = orLazy(someX, constantly(None))
|
||||
|
||||
// asser
|
||||
expect(result).to.equal(someX)
|
||||
})
|
||||
|
||||
it('should return the return of the second argument if the first is None', () => {
|
||||
// act
|
||||
const orSome = orLazy(None, constantly(someX))
|
||||
const orNone = orLazy(None, constantly(None))
|
||||
|
||||
// assert
|
||||
expect(orSome).to.equal(someX)
|
||||
expect(orNone).to.equal(None)
|
||||
})
|
||||
|
||||
it('should not evaluate the second argument if the first one is Some', () => {
|
||||
// arrange
|
||||
const func = spy(constantly(someX))
|
||||
|
||||
// act
|
||||
orLazy(someX, func)
|
||||
|
||||
// assert
|
||||
expect(func.called).to.be.false
|
||||
})
|
||||
|
||||
it('should evaluate the second argument if the first one is None', () => {
|
||||
// arrange
|
||||
const func = spy(constantly(someX))
|
||||
|
||||
// act
|
||||
orLazy(None, func)
|
||||
|
||||
// assert
|
||||
expect(func.called).to.be.true
|
||||
})
|
||||
})
|
17
typescript/option/src/helpers/orLazy.ts
Normal file
17
typescript/option/src/helpers/orLazy.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { isSome } from './isSome'
|
||||
import { Option } from '../types'
|
||||
|
||||
/**
|
||||
* Lazy version of or.
|
||||
* The second argument will only be evaluated if the first argument is Nothing.
|
||||
*
|
||||
* @param a The first argument.
|
||||
* @param b The second argument.
|
||||
*/
|
||||
export const orLazy = <T>(a: Option<T>, b: () => Option<T>) => {
|
||||
if (isSome(a)) {
|
||||
return a
|
||||
}
|
||||
|
||||
return b()
|
||||
}
|
6
typescript/option/src/helpers/toArray.ts
Normal file
6
typescript/option/src/helpers/toArray.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { Option } from '../types'
|
||||
|
||||
export const toArray = <T>(option: Option<T>) => {
|
||||
return unwrap([], v => [v], option)
|
||||
}
|
7
typescript/option/src/helpers/toNullable.ts
Normal file
7
typescript/option/src/helpers/toNullable.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { identity } from '@thi.ng/compose'
|
||||
import { Option } from '../types'
|
||||
|
||||
export const toNullable = <T>(option: Option<T>) => {
|
||||
return unwrap(null, identity, option)
|
||||
}
|
61
typescript/option/src/helpers/unpack.test.ts
Normal file
61
typescript/option/src/helpers/unpack.test.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { expect } from 'chai'
|
||||
import { None } from '../types'
|
||||
import { alwaysX, x, someX } from '../../test/constants'
|
||||
import { constantly } from '@thi.ng/compose'
|
||||
import { spy } from 'sinon'
|
||||
import { unpack } from './unpack'
|
||||
|
||||
describe('The unpack helper', () => {
|
||||
describe('When given None', () => {
|
||||
it('should return the default when given None', () => {
|
||||
// act
|
||||
const result = unpack(constantly(0), constantly(1), None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(0)
|
||||
})
|
||||
|
||||
it('should call the lazy default', () => {
|
||||
// arrange
|
||||
const func = spy(alwaysX)
|
||||
|
||||
// act
|
||||
unpack(func, alwaysX, None)
|
||||
|
||||
// assert
|
||||
expect(func.called).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('When given Some', () => {
|
||||
it('should return the return of the mapper', () => {
|
||||
// act
|
||||
const result = unpack(constantly(0), constantly(1), someX)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(1)
|
||||
})
|
||||
|
||||
it('should not call the lazy default', () => {
|
||||
// arrange
|
||||
const func = spy(alwaysX)
|
||||
|
||||
// act
|
||||
unpack(func, alwaysX, someX)
|
||||
|
||||
// assert
|
||||
expect(func.called).to.be.false
|
||||
})
|
||||
|
||||
it('should pass the inner value to the mapper', () => {
|
||||
// arrange
|
||||
const mapper = spy(alwaysX)
|
||||
|
||||
// act
|
||||
unpack(alwaysX, mapper, someX)
|
||||
|
||||
// assert
|
||||
expect(mapper.calledWith(x)).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
20
typescript/option/src/helpers/unpack.ts
Normal file
20
typescript/option/src/helpers/unpack.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Lazy, Mapper } from '../internalTypes'
|
||||
import { Option } from '../types'
|
||||
import { withDefaultLazy } from './withDefaultLazy'
|
||||
import { map } from './map'
|
||||
|
||||
/**
|
||||
* Like unwrap, but the default value is lazy,
|
||||
* and will only be computed if the Option is None.
|
||||
*
|
||||
* @param _default The lazy value to use in case option is None.
|
||||
* @param mapper The function to pass the inner value to.
|
||||
* @param option The option to unpack.
|
||||
*/
|
||||
export const unpack = <T, U>(
|
||||
_default: Lazy<U>,
|
||||
mapper: Mapper<T, U>,
|
||||
option: Option<T>
|
||||
) => {
|
||||
return withDefaultLazy(_default, map(mapper, option))
|
||||
}
|
37
typescript/option/src/helpers/unwrap.test.ts
Normal file
37
typescript/option/src/helpers/unwrap.test.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { constantly, identity } from '@thi.ng/compose'
|
||||
import { expect } from 'chai'
|
||||
import { spy } from 'sinon'
|
||||
import { someX, x } from '../../test/constants'
|
||||
import { None } from '../types'
|
||||
import { unwrap } from './unwrap'
|
||||
|
||||
describe('The unwrap helper', () => {
|
||||
it('should return the default when given None', () => {
|
||||
// act
|
||||
const result = unwrap(0, constantly(1), None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(0)
|
||||
})
|
||||
|
||||
describe('When given Some', () => {
|
||||
it('should return the result of the mapper ', () => {
|
||||
// act
|
||||
const result = unwrap(0, constantly(1), someX)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(1)
|
||||
})
|
||||
|
||||
it('should pass the inner value to the mapper', () => {
|
||||
// arrange
|
||||
const mapper = spy(identity)
|
||||
|
||||
// act
|
||||
unwrap(0, mapper, someX)
|
||||
|
||||
// assert
|
||||
expect(mapper.calledWith(x)).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
23
typescript/option/src/helpers/unwrap.ts
Normal file
23
typescript/option/src/helpers/unwrap.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Option } from '../types'
|
||||
import { Mapper } from '../internalTypes'
|
||||
import { isSome } from './isSome'
|
||||
|
||||
/**
|
||||
* Apply the function to the value in the Option and return it unwrapped.
|
||||
* If the Option is None, use the default value instead.
|
||||
*
|
||||
* @param _default The default value to use.
|
||||
* @param mapper Function to apply to the inner value.
|
||||
* @param option Option to unwrap.
|
||||
*/
|
||||
export const unwrap = <T, U>(
|
||||
_default: U,
|
||||
caseSome: Mapper<T, U>,
|
||||
option: Option<T>
|
||||
) => {
|
||||
if (isSome(option)) {
|
||||
return caseSome(option as T)
|
||||
}
|
||||
|
||||
return _default
|
||||
}
|
19
typescript/option/src/helpers/values.test.ts
Normal file
19
typescript/option/src/helpers/values.test.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { expect } from 'chai'
|
||||
import { values } from './values'
|
||||
import { Some, None } from '../types'
|
||||
|
||||
describe('The values helper', () => {
|
||||
it('should ignore all None values', () => {
|
||||
// arrange
|
||||
const items = Array(50)
|
||||
.fill(1)
|
||||
.map((_, i) => (i % 2 ? Some(i) : None))
|
||||
|
||||
// act
|
||||
const result = values(items)
|
||||
|
||||
// assert
|
||||
expect(result).to.not.contain(None)
|
||||
expect(result, "ensure it didn't clear everything").to.not.be.empty
|
||||
})
|
||||
})
|
11
typescript/option/src/helpers/values.ts
Normal file
11
typescript/option/src/helpers/values.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Option } from '../types'
|
||||
import { toArray } from './toArray'
|
||||
|
||||
/**
|
||||
* Take all the values that are present, throwing away any None
|
||||
*
|
||||
* @param iterable The iterable to collect the values from.
|
||||
*/
|
||||
export const values = <T>(iterable: Iterable<Option<T>>) => {
|
||||
return Array.from(iterable).flatMap(toArray)
|
||||
}
|
22
typescript/option/src/helpers/withDefault.test.ts
Normal file
22
typescript/option/src/helpers/withDefault.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { expect } from 'chai'
|
||||
import { withDefault } from './withDefault'
|
||||
import { x } from '../../test/constants'
|
||||
import { None, Some } from '../types'
|
||||
|
||||
describe('The withDefault helper', () => {
|
||||
it('should return the default when given None', () => {
|
||||
// act
|
||||
const result = withDefault(x, None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(x)
|
||||
})
|
||||
|
||||
it('should return x when given Some(x)', () => {
|
||||
// act
|
||||
const result = withDefault(0, Some(1))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(1)
|
||||
})
|
||||
})
|
13
typescript/option/src/helpers/withDefault.ts
Normal file
13
typescript/option/src/helpers/withDefault.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { unwrap } from './unwrap'
|
||||
import { identity } from '@thi.ng/compose'
|
||||
import { Option } from '../types'
|
||||
|
||||
/**
|
||||
* Provide a default value, turning an optional value into a normal value.
|
||||
*
|
||||
* @param _default The default value to use.
|
||||
* @param option The option to get the default of.
|
||||
*/
|
||||
export const withDefault = <T>(_default: T, option: Option<T>) => {
|
||||
return unwrap(_default, identity, option)
|
||||
}
|
50
typescript/option/src/helpers/withDefaultLazy.test.ts
Normal file
50
typescript/option/src/helpers/withDefaultLazy.test.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { expect } from 'chai'
|
||||
import { withDefaultLazy } from './withDefaultLazy'
|
||||
import { None, Some } from '../types'
|
||||
import { alwaysX, x, someX } from '../../test/constants'
|
||||
import { constantly } from '@thi.ng/compose'
|
||||
import { spy } from 'sinon'
|
||||
|
||||
describe('The withDefaultLazy helper', () => {
|
||||
describe('When given None', () => {
|
||||
it('should return the default when given None', () => {
|
||||
// act
|
||||
const result = withDefaultLazy(alwaysX, None)
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(x)
|
||||
})
|
||||
|
||||
it('should call the lazy default', () => {
|
||||
// arrange
|
||||
const func = spy(constantly(x))
|
||||
|
||||
// act
|
||||
withDefaultLazy(func, None)
|
||||
|
||||
// assert
|
||||
expect(func.called).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('When given Some', () => {
|
||||
it('should return the inner value', () => {
|
||||
// act
|
||||
const result = withDefaultLazy(constantly(0), Some(1))
|
||||
|
||||
// assert
|
||||
expect(result).to.equal(1)
|
||||
})
|
||||
|
||||
it('should not call the lazy default', () => {
|
||||
// arrange
|
||||
const func = spy(constantly(x))
|
||||
|
||||
// act
|
||||
withDefaultLazy(func, someX)
|
||||
|
||||
// assert
|
||||
expect(func.called).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
16
typescript/option/src/helpers/withDefaultLazy.ts
Normal file
16
typescript/option/src/helpers/withDefaultLazy.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Option } from '../types'
|
||||
import { isSome } from './isSome'
|
||||
import { Lazy } from '../internalTypes'
|
||||
import { get } from './get'
|
||||
|
||||
/**
|
||||
* Same as withDefault but the default is only evaluated when the option is None.
|
||||
*
|
||||
* @param _default Function returning the default value to use.
|
||||
* @param option The option to get the default of.
|
||||
*/
|
||||
export const withDefaultLazy = <T>(_default: Lazy<T>, option: Option<T>) => {
|
||||
if (isSome(option)) {
|
||||
return get(option)
|
||||
} else return _default()
|
||||
}
|
2
typescript/option/src/index.ts
Normal file
2
typescript/option/src/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './helpers/external'
|
||||
export * from './types'
|
9
typescript/option/src/internalTypes.ts
Normal file
9
typescript/option/src/internalTypes.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Option } from './types'
|
||||
|
||||
export type Mapper<T, U> = (v: T) => U
|
||||
export type Binder<T, U> = (v: T) => Option<U>
|
||||
export type Predicate<T> = (v: T) => boolean
|
||||
export type Folder<T, U> = (s: U, v: T) => U
|
||||
export type BackFolder<T, U> = (v: T, s: U) => U
|
||||
export type Nullable<T> = T | null
|
||||
export type Lazy<T> = () => T
|
1
typescript/option/src/internals.ts
Normal file
1
typescript/option/src/internals.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const none = Symbol('none')
|
17
typescript/option/src/types.ts
Normal file
17
typescript/option/src/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { identity } from '@thi.ng/compose'
|
||||
import { Brand } from 'utility-types'
|
||||
import { none } from './internals'
|
||||
|
||||
// This is never actually used outside of typing so we can just declare it
|
||||
declare const some: unique symbol
|
||||
|
||||
type None = Brand<void, typeof none>
|
||||
type Some<T> = Brand<T, typeof some>
|
||||
|
||||
export type Option<T> = Some<T> | None
|
||||
|
||||
export const None = {
|
||||
__brand: none,
|
||||
toString: () => 'None'
|
||||
} as None
|
||||
export const Some = identity as <T>(value: T) => Option<T>
|
11
typescript/option/test/constants.ts
Normal file
11
typescript/option/test/constants.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { constantly } from '@thi.ng/compose'
|
||||
import { Some } from '../src'
|
||||
|
||||
// general value to pass around
|
||||
export const x = Symbol('x')
|
||||
|
||||
// same as x but for some
|
||||
export const someX = Some(x)
|
||||
|
||||
export const alwaysX = constantly(x)
|
||||
export const alwaysSomeX = constantly(someX)
|
15
typescript/option/tsconfig.json
Normal file
15
typescript/option/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"lib": ["esnext", "dom", "es2015.iterable"],
|
||||
"moduleResolution": "node",
|
||||
"strictNullChecks": true,
|
||||
"sourceMap": true,
|
||||
"downlevelIteration": true,
|
||||
"target": "es6",
|
||||
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src", "sandbox", "test", "package.json"],
|
||||
"exclude": ["dist"]
|
||||
}
|
Loading…
Reference in a new issue