Skip to content

Commit 71fb906

Browse files
docs: update readme (#202)
1 parent d8764b0 commit 71fb906

File tree

2 files changed

+376
-7
lines changed

2 files changed

+376
-7
lines changed

README.md

Lines changed: 373 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,388 @@ typescriptMonads.maybe(someRemoteValue).tapSome(console.log)
4949
* [Logger](#logger)
5050

5151
# Maybe
52-
TODO
52+
53+
The `Maybe` monad represents values that may or may not exist. It's a safe way to handle potentially null or undefined values without resorting to null checks throughout your code.
54+
55+
```typescript
56+
import { maybe, none } from 'typescript-monads'
57+
58+
// Creating Maybe instances
59+
const someValue = maybe(42) // Maybe with a value
60+
const noValue = maybe(null) // Maybe with no value (None)
61+
const alsoNoValue = none<number>() // Explicitly create a None
62+
63+
// Safe value access
64+
someValue.valueOr(0) // 42
65+
noValue.valueOr(0) // 0
66+
someValue.valueOrCompute(() => expensiveCalculation()) // 42 (computation skipped)
67+
noValue.valueOrCompute(() => expensiveCalculation()) // result of computation
68+
69+
// Conditional execution with pattern matching
70+
someValue.match({
71+
some: val => console.log(`Got a value: ${val}`),
72+
none: () => console.log('No value present')
73+
}) // logs: "Got a value: 42"
74+
75+
// Side effects with tap
76+
someValue.tap({
77+
some: val => console.log(`Got ${val}`),
78+
none: () => console.log('Nothing to see')
79+
})
80+
81+
// Conditional side effects
82+
someValue.tapSome(val => console.log(`Got ${val}`))
83+
noValue.tapNone(() => console.log('Nothing here'))
84+
85+
// Chaining operations (only executed for Some values)
86+
maybe(5)
87+
.map(n => n * 2) // maybe(10)
88+
.filter(n => n > 5) // maybe(10)
89+
.flatMap(n => maybe(n + 1)) // maybe(11)
90+
91+
// Transforming to other types
92+
maybe(5).toResult('No value found') // Ok(5)
93+
maybe(null).toResult('No value found') // Fail('No value found')
94+
95+
// Working with RxJS (with rxjs optional dependency)
96+
import { maybeToObservable } from 'typescript-monads'
97+
maybeToObservable(maybe(5)) // Observable that emits 5 and completes
98+
maybeToObservable(none()) // Observable that completes without emitting
99+
```
53100

54101
## List
55-
TODO
102+
103+
The `List` monad is a lazily evaluated collection with chainable operations. It provides many of the common list processing operations found in functional programming languages.
104+
105+
```typescript
106+
import { List } from 'typescript-monads'
107+
108+
// Creating Lists
109+
const fromValues = List.of(1, 2, 3, 4, 5)
110+
const fromIterable = List.from([1, 2, 3, 4, 5])
111+
const numbersFromRange = List.range(1, 10) // 1 to 10
112+
const infiniteNumbers = List.integers() // All integers (use with take!)
113+
const empty = List.empty<number>()
114+
115+
// Basic operations
116+
fromValues.toArray() // [1, 2, 3, 4, 5]
117+
fromValues.headOrUndefined() // 1
118+
fromValues.headOr(0) // 1
119+
empty.headOr(0) // 0
120+
121+
// Transformations
122+
fromValues
123+
.map(n => n * 2) // [2, 4, 6, 8, 10]
124+
.filter(n => n > 5) // [6, 8, 10]
125+
.take(2) // [6, 8]
126+
.drop(1) // [8]
127+
128+
// LINQ-style operations
129+
fromValues.sum() // 15
130+
fromValues.all(n => n > 0) // true
131+
fromValues.any(n => n % 2 === 0) // true
132+
fromValues.where(n => n % 2 === 0) // [2, 4]
133+
134+
// Conversion
135+
fromValues.toDictionary() // { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }
136+
const users = List.of(
137+
{ id: 'a', name: 'Alice' },
138+
{ id: 'b', name: 'Bob' }
139+
)
140+
users.toDictionary('id') // { 'a': { id: 'a', name: 'Alice' }, 'b': { id: 'b', name: 'Bob' } }
141+
```
56142

57143
## Either
58-
TODO
144+
145+
The `Either` monad represents values that can be one of two possible types. It's often used to represent a value that can be either a success (Right) or a failure (Left).
146+
147+
```typescript
148+
import { either } from 'typescript-monads'
149+
150+
// Creating Either instances
151+
const rightValue = either<string, number>(undefined, 42) // Right value
152+
const leftValue = either<string, number>('error', undefined) // Left value
153+
154+
// Checking which side is present
155+
rightValue.isRight() // true
156+
rightValue.isLeft() // false
157+
leftValue.isRight() // false
158+
leftValue.isLeft() // true
159+
160+
// Pattern matching
161+
rightValue.match({
162+
right: val => `Success: ${val}`,
163+
left: err => `Error: ${err}`
164+
}) // "Success: 42"
165+
166+
// Side effects with tap
167+
rightValue.tap({
168+
right: val => console.log(`Got right: ${val}`),
169+
left: err => console.log(`Got left: ${err}`)
170+
})
171+
172+
// Transformation (only applies to Right values)
173+
rightValue.map(n => n * 2) // Either with Right(84)
174+
leftValue.map(n => n * 2) // Either with Left('error') unchanged
175+
176+
// Chaining (flatMap only applies to Right values)
177+
rightValue.flatMap(n => either(undefined, n + 10)) // Either with Right(52)
178+
leftValue.flatMap(n => either(undefined, n + 10)) // Either with Left('error') unchanged
179+
```
59180

60181
## Reader
61-
TODO
182+
183+
The `Reader` monad represents a computation that depends on some external configuration or environment. It's useful for dependency injection.
184+
185+
```typescript
186+
import { reader } from 'typescript-monads'
187+
188+
// Define a configuration type
189+
interface Config {
190+
apiUrl: string
191+
apiKey: string
192+
}
193+
194+
// Create readers that depend on this configuration
195+
const getApiUrl = reader<Config, string>(config => config.apiUrl)
196+
const getApiKey = reader<Config, string>(config => config.apiKey)
197+
198+
// Compose readers to build more complex operations
199+
const getAuthHeader = getApiKey.map(key => `Bearer ${key}`)
200+
201+
// Create a reader for making an API request
202+
const fetchData = reader<Config, Promise<Response>>(config => {
203+
return fetch(`${config.apiUrl}/data`, {
204+
headers: {
205+
'Authorization': `Bearer ${config.apiKey}`
206+
}
207+
})
208+
})
209+
210+
// Execute the reader with a specific configuration
211+
const config: Config = {
212+
apiUrl: 'https://api.example.com',
213+
apiKey: 'secret-key-123'
214+
}
215+
216+
const apiUrl = getApiUrl.run(config) // 'https://api.example.com'
217+
const authHeader = getAuthHeader.run(config) // 'Bearer secret-key-123'
218+
fetchData.run(config).then(response => {
219+
// Handle API response
220+
})
221+
```
62222

63223
## Result
64-
TODO
224+
225+
The `Result` monad represents operations that can either succeed with a value or fail with an error. It's similar to Either but with more specific semantics for success/failure.
226+
227+
```typescript
228+
import { ok, fail, catchResult } from 'typescript-monads'
229+
230+
// Creating Result instances
231+
const success = ok<number, string>(42) // Success with value 42
232+
const failure = fail<number, string>('error') // Failure with error 'error'
233+
234+
// Safely catching exceptions
235+
const result = catchResult<number, Error>(
236+
() => {
237+
// Code that might throw
238+
if (Math.random() > 0.5) {
239+
throw new Error('Failed')
240+
}
241+
return 42
242+
}
243+
)
244+
245+
// Checking result type
246+
success.isOk() // true
247+
success.isFail() // false
248+
failure.isOk() // false
249+
failure.isFail() // true
250+
251+
// Extracting values safely
252+
success.unwrapOr(0) // 42
253+
failure.unwrapOr(0) // 0
254+
success.unwrap() // 42
255+
// failure.unwrap() // Throws error
256+
257+
// Convert to Maybe
258+
success.maybeOk() // Maybe with Some(42)
259+
failure.maybeOk() // Maybe with None
260+
success.maybeFail() // Maybe with None
261+
failure.maybeFail() // Maybe with Some('error')
262+
263+
// Pattern matching
264+
success.match({
265+
ok: val => `Success: ${val}`,
266+
fail: err => `Error: ${err}`
267+
}) // "Success: 42"
268+
269+
// Transformations
270+
success.map(n => n * 2) // Ok(84)
271+
failure.map(n => n * 2) // Fail('error') unchanged
272+
failure.mapFail(e => `${e}!`) // Fail('error!')
273+
274+
// Chaining
275+
success.flatMap(n => ok(n + 10)) // Ok(52)
276+
failure.flatMap(n => ok(n + 10)) // Fail('error') unchanged
277+
278+
// Side effects
279+
success.tap({
280+
ok: val => console.log(`Success: ${val}`),
281+
fail: err => console.log(`Error: ${err}`)
282+
})
283+
success.tapOk(val => console.log(`Success: ${val}`))
284+
failure.tapFail(err => console.log(`Error: ${err}`))
285+
286+
// Chaining with side effects
287+
success
288+
.tapOkThru(val => console.log(`Success: ${val}`))
289+
.map(n => n * 2)
290+
291+
// Converting to promises
292+
import { resultToPromise } from 'typescript-monads'
293+
resultToPromise(success) // Promise that resolves to 42
294+
resultToPromise(failure) // Promise that rejects with 'error'
295+
```
65296

66297
## State
67-
TODO
298+
299+
The `State` monad represents computations that can read and transform state. It's useful for threading state through a series of operations.
300+
301+
```typescript
302+
import { state } from 'typescript-monads'
303+
304+
// Define a state type
305+
interface AppState {
306+
count: number
307+
name: string
308+
}
309+
310+
// Initial state
311+
const initialState: AppState = {
312+
count: 0,
313+
name: ''
314+
}
315+
316+
// Create operations that work with the state
317+
const incrementCount = state<AppState, number>(s =>
318+
[{ ...s, count: s.count + 1 }, s.count + 1]
319+
)
320+
321+
const setName = (name: string) => state<AppState, void>(s =>
322+
[{ ...s, name }, undefined]
323+
)
324+
325+
const getCount = state<AppState, number>(s => [s, s.count])
326+
327+
// Compose operations
328+
const operation = incrementCount
329+
.flatMap(() => setName('Alice'))
330+
.flatMap(() => getCount)
331+
332+
// Run the state operation
333+
const result = operation.run(initialState)
334+
console.log(result.state) // { count: 1, name: 'Alice' }
335+
console.log(result.value) // 1
336+
```
68337

69338
## Logger
70-
TODO
339+
340+
The `Logger` monad lets you collect logs during a computation. It's useful for debugging or creating audit trails.
341+
342+
```typescript
343+
import { logger, tell } from 'typescript-monads'
344+
345+
// Create a logger with initial logs and value
346+
const initialLogger = logger<string, number>(['Starting process'], 0)
347+
348+
// Add logs and transform value
349+
const result = initialLogger
350+
.flatMap(val => {
351+
return logger(['Incrementing value'], val + 1)
352+
})
353+
.flatMap(val => {
354+
return logger(['Doubling value'], val * 2)
355+
})
356+
357+
// Extract all logs and final value
358+
result.runUsing(({ logs, value }) => {
359+
console.log('Logs:', logs) // ['Starting process', 'Incrementing value', 'Doubling value']
360+
console.log('Final value:', value) // 2
361+
})
362+
363+
// Start with a single log entry
364+
const startLogger = tell<string>('Beginning')
365+
366+
// Add a value to the logger
367+
const withValue = logger.startWith<string, number>('Starting with value:', 42)
368+
369+
// Extract results using pattern matching
370+
const output = result.runUsing(({ logs, value }) => {
371+
return {
372+
history: logs.join('\n'),
373+
result: value
374+
}
375+
})
376+
```
377+
378+
# Integration with Other Libraries
379+
380+
## RxJS Integration
381+
382+
This library offers RxJS integration with the `Maybe` and `Result` monads:
383+
384+
```typescript
385+
import { maybeToObservable } from 'typescript-monads'
386+
import { resultToObservable } from 'typescript-monads'
387+
import { of } from 'rxjs'
388+
import { flatMap } from 'rxjs/operators'
389+
390+
// Convert Maybe to Observable
391+
of(maybe(5)).pipe(
392+
flatMap(maybeToObservable)
393+
).subscribe(val => console.log(val)) // logs 5 and completes
394+
395+
// Convert Result to Observable
396+
of(ok(42)).pipe(
397+
flatMap(resultToObservable)
398+
).subscribe(
399+
val => console.log(`Success: ${val}`),
400+
err => console.error(`Error: ${err}`)
401+
)
402+
```
403+
404+
## Promise Integration
405+
406+
You can convert `Result` monads to promises:
407+
408+
```typescript
409+
import { resultToPromise } from 'typescript-monads'
410+
411+
// Convert Result to Promise
412+
resultToPromise(ok(42))
413+
.then(val => console.log(`Success: ${val}`))
414+
.catch(err => console.error(`Error: ${err}`))
415+
416+
// Catch exceptions and convert to Result
417+
async function fetchData() {
418+
try {
419+
const response = await fetch('https://api.example.com/data')
420+
if (!response.ok) {
421+
throw new Error(`HTTP error ${response.status}`)
422+
}
423+
return ok(await response.json())
424+
} catch (error) {
425+
return fail(error)
426+
}
427+
}
428+
```
429+
430+
# Contributing
431+
432+
Contributions are welcome! Please feel free to submit a Pull Request.
433+
434+
# License
435+
436+
MIT

0 commit comments

Comments
 (0)