Skip to content

Commit a065365

Browse files
committed
First version
1 parent 9ed1f33 commit a065365

11 files changed

+2447
-0
lines changed

docs/option.md

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# `Option<T>`
2+
3+
This container represents an optional value. You can create a `Option<T>` by calling either of the following constructors:
4+
5+
- `Some('Hello world')`
6+
- `None()`
7+
8+
### Methods
9+
10+
- [Option.some](#optionsome)
11+
- [Option.value](#optionvalue)
12+
- [Option.map()](#optionmap)
13+
- [Option.and()](#optionand)
14+
- [Option.unwrap()](#optionunwrap)
15+
- [Option.unwrapOr()](#optionunwrapor)
16+
17+
### Full example
18+
19+
```javascript
20+
import { Option, Some, None } from "containers-ts";
21+
22+
function toNumber(s: string): Option<number> {
23+
const number = Number(s);
24+
if (isNaN(number)) {
25+
return new None();
26+
}
27+
return new Some(number);
28+
}
29+
30+
function addToString(s: string, n: number): Option<string> {
31+
return toNumber("4").map(n => String(n + 1));
32+
}
33+
34+
console.log(addToString("4", 4));
35+
// Outputs: Some("8")
36+
37+
console.log(addToString("Vertical Strategy", 4));
38+
// Outputs: None
39+
```
40+
41+
## Option.map()
42+
43+
Map `Option<T>` into a new `Option<U>`.
44+
45+
- [.map()](#optionmap) is used to continue work on references that may point to a value or not
46+
47+
### Example
48+
49+
```typescript
50+
const helloWorld = new Some("Hello world");
51+
52+
// Converts Some("Hello world") → Some(11)
53+
const helloWorldLength = helloWorld.map(helloStr => helloStr.length);
54+
55+
// Outputs 'Some(11)'
56+
console.log(helloWorldLength);
57+
```
58+
59+
## Option.and()
60+
61+
Combine `Option<T>` with another `Option<U>` to create `Option<U>`.
62+
63+
- [.and()](#optionand) is similar to [.map()](#optionmap)
64+
- [.and()](#optionand) is useful for when you are combining many `Option` types.
65+
66+
### Example
67+
68+
```typescript
69+
const helloWorld = new Some("Hello world");
70+
71+
// Converts Some("Hello world") → Some(11)
72+
const helloWorldLength = helloWorld.and(helloStr => new Some(helloStr.length));
73+
74+
// Outputs 'Some(11)'
75+
console.log(helloWorldLength);
76+
```
77+
78+
## Option.unwrap()
79+
80+
Returns the internal value of `Option<T>`. Throws if there is no value.
81+
82+
In general, because this function may throw, use of [.unwrap()](#optionunwrap) use is discouraged. Instead, prefer to use an if statment and check [.some](#optionsome) and handle the `None` case explicitly.
83+
84+
- [.unwrap()](#optionunwrap) can be used when an `Option` is guaranteed to hold a value.
85+
- [.unwrap()](#optionunwrap) can be used when writing first versions of software and refactored later
86+
87+
### Example:
88+
89+
```typescript
90+
const helloWorld = new Some("Hello world");
91+
92+
// Outputs 'Hello world'
93+
console.log(helloWorld.unwrap());
94+
```
95+
96+
## Option.unwrapOr(default)
97+
98+
Returns the internal value of `Option<T>` if any, otherwise a default value.
99+
100+
### Example
101+
102+
```typescript
103+
const helloWorld = new Some("Hello world");
104+
105+
// Outputs 'Some("Hello world")'
106+
console.log(helloWorld);
107+
108+
// Outputs 'Hello world'
109+
console.log(helloWorld.unwrap());
110+
```

docs/result.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
### `Result<T, E>`
2+
3+
This container represent the result of an operation that can fail. You can create a `Result<T, E>` by calling either of the following constructors:
4+
5+
- `Ok('Hello world')`
6+
- `Err('Error value')`
7+
8+
You can map optional values into new values:
9+
10+
```typescript
11+
import { Option } from "containers";
12+
13+
const helloWorld = new Some("Hello world");
14+
const helloWorldLength = helloWorld.map(helloStr => helloStr.length);
15+
16+
console.log(helloWorldLength); // Will output 'Some()'
17+
```
18+
19+
Checkout the full documentation for [`Option<T>`](docs/option.md) here:

package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"scripts": {
3+
"test": "ts-node ./node_modules/.bin/tape **/*.test.ts | tap-spec",
4+
"watch-test": "nodemon -e ts -w ./src -x yarn test"
5+
},
6+
"dependencies": {
7+
"@types/tape": "^4.2.32",
8+
"nodemon": "^1.18.4",
9+
"tap-spec": "^5.0.0",
10+
"tape": "^4.9.1",
11+
"ts-node": "^7.0.1",
12+
"typedoc": "^0.12.0",
13+
"typescript": "^3.0.1"
14+
}
15+
}

readme.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Containers
2+
3+
## Installation
4+
5+
```bash
6+
yarn add containers-ts
7+
```
8+
9+
## Content
10+
11+
This module provides the containers:
12+
13+
| Type | Description |
14+
| ------------------------------ | ------------------------------ |
15+
| [Option<T>](docs/option.md) | Represent an optional value |
16+
| [Result<R, E>](docs/result.md) | Represent a result or an error |
17+
18+
Click the individual type to learn more about usage

src/containers/option.test.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as test from "tape";
2+
import { Option, Some, None } from "./option";
3+
4+
test("Option.some", t => {
5+
const none: Option<number> = new None();
6+
const some: Option<number> = new Some(4);
7+
8+
t.equal(none.some, false, "None.some should equal false");
9+
t.equal(some.some, true, "Some.some should equal true");
10+
t.end();
11+
});
12+
13+
function lol(t: Option<number>) {
14+
console.log(t.some);
15+
}
16+
17+
test("Option.map()", t => {
18+
const none: Option<number> = new None();
19+
const some: Option<number> = new Some(4);
20+
21+
const add4 = (value: number) => value + 4;
22+
const toString = (value: number) => String(value);
23+
24+
const noneNewValue = none.map(add4);
25+
const someNewValue = some.map(add4);
26+
27+
t.ok(noneNewValue instanceof None, "None.map() should return None");
28+
t.ok(none instanceof None, "None.map() should not mutate");
29+
30+
t.ok(someNewValue instanceof Some, "Some.map() should return Some");
31+
const someNewValueString = some.map(toString);
32+
t.equal(someNewValue.unwrap(), 8, "Some.map() should apply function");
33+
t.equal(
34+
someNewValueString.unwrap(),
35+
"4",
36+
"Some.map() should be able to map to a different type"
37+
);
38+
t.equal(some.unwrap(), 4, "Some.map() should not mutate");
39+
t.end();
40+
});
41+
42+
test("Option.unwrap()", t => {
43+
const none: Option<number> = new None();
44+
const some: Option<number> = new Some(4);
45+
46+
t.throws(() => none.unwrap(), "None.unwrap() should throw");
47+
t.equal(some.unwrap(), 4, "Some(4).unwrap() should equal 4");
48+
t.end();
49+
});
50+
51+
test("Option.unwrapOr()", t => {
52+
const none: Option<number> = new None();
53+
const some: Option<number> = new Some(4);
54+
55+
t.equal(none.unwrapOr(8), 8, "None.unwrapOr() should return default");
56+
t.equal(some.unwrapOr(8), 4, "Some(4).unwrapOr() should return value");
57+
t.end();
58+
});
59+
60+
test("Option.and()", t => {
61+
const none: Option<number> = new None();
62+
const some: Option<number> = new Some(4);
63+
const toString = (n: number): Option<string> => new Some(String(n));
64+
const returnNone = (n: number): Option<string> => new None();
65+
66+
t.equal(none.and(toString).some, false, "None.and() should return None");
67+
t.equal(none.and(returnNone).some, false, "None.and() should return None");
68+
t.equal(
69+
some.and(toString).unwrap(),
70+
"4",
71+
"Some.and(() => Some) should return new Some()"
72+
);
73+
t.equal(some.and(returnNone).some, false, "Some.and(() => None) return None");
74+
t.end();
75+
});

src/containers/option.ts

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as Result from "./option";
2+
3+
interface OptionInterface<T> {
4+
/**
5+
* `true` if `.value` exists
6+
* `false` if there is not `.value`
7+
*
8+
* This can be used to safely check if there is a value
9+
* before proceding.
10+
*
11+
* ```
12+
// Here we have no `result.value`
13+
if (!result.some) {
14+
return 'No value'
15+
}
16+
// Now the compiler knows that `result.value` must exist
17+
console.log(result.value)
18+
```
19+
*/
20+
readonly some: boolean;
21+
22+
/**
23+
* Maps an `Option<T>` to `Option<U>` by applying a function to a contained value.
24+
*/
25+
/**
26+
* If there is a value, map that value with `f`
27+
* and return an Option with the new value.
28+
*
29+
* @param f A function that takes the value and maps it to a new value
30+
*
31+
*/
32+
map<U>(f: (r: T) => U): Option<U>;
33+
34+
/**
35+
*
36+
* @param f
37+
*/
38+
and<U>(f: (r: T) => Option<U>): Option<U>;
39+
unwrap(): T;
40+
unwrapOr(defaultValue: T): T;
41+
}
42+
43+
/**
44+
* Some value `T`
45+
*/
46+
export class Some<T> implements OptionInterface<T> {
47+
public readonly some: true = true;
48+
49+
/**
50+
* The value `T` contained in this Option
51+
*/
52+
value: T;
53+
/**
54+
* Create a new Option containing `value`
55+
*/
56+
constructor(value: T) {
57+
this.value = value;
58+
}
59+
map<U>(f: (r: T) => U): Option<U> {
60+
return new Some(f(this.value));
61+
}
62+
and<U>(f: (r: T) => Option<U>): Option<U> {
63+
return f(this.value);
64+
}
65+
unwrap() {
66+
return this.value;
67+
}
68+
unwrapOr(defaultValue: T) {
69+
return this.value;
70+
}
71+
}
72+
73+
/**
74+
* No value
75+
*/
76+
export class None<T> implements OptionInterface<T> {
77+
public readonly some: false = false;
78+
79+
constructor() {}
80+
map<U>(f: (r: T) => U): Option<U> {
81+
return new None();
82+
}
83+
and<U>(f: (r: T) => Option<U>): Option<U> {
84+
return new None();
85+
}
86+
unwrap(): never {
87+
throw new Error("Tried to unwrap None");
88+
}
89+
unwrapOr(defaultValue: T) {
90+
return defaultValue;
91+
}
92+
}
93+
94+
/**
95+
* `Option<T>` represents an optional value.
96+
* `Option` can either be `Some<T>` and contain a value
97+
* or the opposite None<T> and not contain a value.
98+
*/
99+
export type Option<T> = Some<T> | None<T>;

0 commit comments

Comments
 (0)