Skip to content

Commit

Permalink
Port examples to new structure
Browse files Browse the repository at this point in the history
  • Loading branch information
rzeigler committed Aug 25, 2019
1 parent 31efcac commit d1da78c
Show file tree
Hide file tree
Showing 21 changed files with 873 additions and 783 deletions.
8 changes: 4 additions & 4 deletions benchmarks/internals/RunSuite.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint @typescript-eslint/no-triple-slash-reference:off */
/* eslint @typescript-eslint/triple-slash-reference:off */
/* eslint @typescript-eslint/explicit-function-return-type:off */
/// <reference path="./Global.d.ts" />

/* tslint:disable: no-unbound-method */
import {Suite} from "benchmark"
import {FutureInstance} from "fluture"

import { IO } from "../../src/wave";
import { Wave } from "../../src/wave";
import * as wave from "../../src/wave";
import {noop} from "fearless-io/src/internals/Noop"
import {UIO} from "fearless-io"
Expand All @@ -22,7 +22,7 @@ export const RunSuite = (
bluebird(): PromiseLike<unknown>;
fio(): UIO<unknown>;
fluture(): FutureInstance<unknown, unknown>;
waveguide(): IO<never, unknown>;
waveguide(): Wave<never, unknown>;
native?(): void;
}
) => {
Expand All @@ -37,7 +37,7 @@ export const RunSuite = (
}

suite
.add("waveguide", (cb: IDefer) => wave.runR(test.waveguide(), {}, () => cb.resolve()), {defer: true})
.add("waveguide", (cb: IDefer) => wave.run(test.waveguide(), () => cb.resolve()), {defer: true})
.add(
"FIO",
(cb: IDefer) => fioRuntime.execute(test.fio(), () => cb.resolve()),
Expand Down
44 changes: 22 additions & 22 deletions examples/01-introduction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@
// limitations under the License.

import * as wave from "../src/wave"
import { IO } from "../src/wave";
import { Wave } from "../src/wave";
import { log } from "../src/console";
import * as fs from "fs";
import { left, right } from "fp-ts/lib/Either";
import { pipe } from "fp-ts/lib/pipeable";

/**
* An IO is a description of program that can be run to produce a result or possibly fail.
* A Wave is a description of program that can be run to produce a result or possibly fail.
* In this way, it is conceptually similar to the type <A>() => Promise<A>.
* It allows use to interact with the real world without side effects because an IO doesn't
* It allows use to interact with the real world without side effects because an Wave doesn't
* do anything on its own.
*
* Here, we produce a synchronous effect that when executed will read the pid of the process.
* Process ids never change (so this is being very defensive) but we wrap it in IO because this will
* Process ids never change (so this is being very defensive) but we wrap it in Wave because this will
* produce a different result from program execution to program execution so in the strictest sense
* it is not referentially transparent.
*/
Expand All @@ -42,7 +42,7 @@ const pid = wave.sync(() => process.pid)
* This ensures the file begins opening, it will complete and we don't leak resources.
*
*/
const openFile = (path: string, flags: string): IO<NodeJS.ErrnoException, number> => wave.uninterruptible(
const openFile = (path: string, flags: string): Wave<NodeJS.ErrnoException, number> => wave.uninterruptible(
wave.async((callback) => {
fs.open(path, flags,
(err, fd) => {
Expand All @@ -60,7 +60,7 @@ const openFile = (path: string, flags: string): IO<NodeJS.ErrnoException, number
/**
* Here we close a file handle
*/
const closeFile = (handle: number): IO<NodeJS.ErrnoException, void> => wave.uninterruptible(
const closeFile = (handle: number): Wave<NodeJS.ErrnoException, void> => wave.uninterruptible(
wave.async((callback) => {
fs.close(handle, (err) => {
if (err) {
Expand All @@ -75,7 +75,7 @@ const closeFile = (handle: number): IO<NodeJS.ErrnoException, void> => wave.unin
/**
* We can also use a file handle to write content
*/
const write = (handle: number, content: string): IO<NodeJS.ErrnoException, number> => wave.uninterruptible(
const write = (handle: number, content: string): Wave<NodeJS.ErrnoException, number> => wave.uninterruptible(
wave.async((callback) => {
fs.write(handle, content, (err, written) => {
if (err) {
Expand All @@ -92,7 +92,7 @@ const write = (handle: number, content: string): IO<NodeJS.ErrnoException, numbe
* Effect types are all about working with statements as though they were values.
* We can package these 3 operations up into a resource safe file writing mechanism to manage the file handle resource
*/
const writeToFile = (path: string, content: string): IO<NodeJS.ErrnoException, void> =>
const writeToFile = (path: string, content: string): Wave<NodeJS.ErrnoException, void> =>
pipe(
wave.bracket(openFile(path, "w"), closeFile, (handle) => write(handle, content)),
/**
Expand All @@ -106,13 +106,13 @@ const writeToFile = (path: string, content: string): IO<NodeJS.ErrnoException, v
/**
* The true power of waveguide (and other IO monads) is that we can treat statements like values
* We can manipulate them and stitch them together in a side-effect free fashion.
* Chain stitches together IOs so that the result of the first IO is used to create the IO to continue with.
* Here we are going to create an IO that uses the result of pid to write a process file
* Chain stitches together IOs so that the result of the first Wave is used to create the Wave to continue with.
* Here we are going to create an Wave that uses the result of pid to write a process file
*/

const writePidFile: IO<NodeJS.ErrnoException, void> = wave.chain(pid, (p) => writeToFile("pid", p.toString()));
const writePidFile: Wave<NodeJS.ErrnoException, void> = wave.chain(pid, (p) => writeToFile("pid", p.toString()));

const deletePidFile: IO<NodeJS.ErrnoException, void> = wave.uninterruptible(wave.async((callback) => {
const deletePidFile: Wave<NodeJS.ErrnoException, void> = wave.uninterruptible(wave.async((callback) => {
fs.unlink("pid", (err) => {
if (err) {
callback(left(err));
Expand All @@ -131,26 +131,26 @@ const getTime = wave.sync(() => new Date());
* If you dislike the stairstep that is happening here, check out fp-ts-contrib's Do which
* waveguide works with and makes the stairstep go away
*/
const logPid: IO<NodeJS.ErrnoException, void> =
const logPid: Wave<NodeJS.ErrnoException, void> =
wave.chain(pid,
(p) => wave.chain(getTime,
(now) => log(`its ${now} and the pid is still ${p}`))
)

/**
* Again, because IOs are just data structures, we can manipulate them like values.
* We can, for instance construct an IO that repeatedly executes an IO on an interval
* We can, for instance construct an Wave that repeatedly executes an Wave on an interval
*/
function repeatEvery<E, A>(io: IO<E, A>, s: number): IO<E, A> {
function repeatEvery<E, A>(io: Wave<E, A>, s: number): Wave<E, A> {
// We use chain here for the laziness because this is an infinitely sized structure
return wave.applySecondL(wave.delay(io, s * 1000), () => repeatEvery(io, s));
}

const logPidForever = repeatEvery(logPid, 1);

/**
* IO also expose handling cancellation and errors in a safe fashion.
* Here we are going to create an IO that writes the pid file, logs the pid repeatedly forever,
* Wave also expose handling cancellation and errors in a safe fashion.
* Here we are going to create an Wave that writes the pid file, logs the pid repeatedly forever,
* but is sure to delete the pid file
*/
const run = wave.chainError(
Expand All @@ -166,7 +166,7 @@ const run = wave.chainError(

/**
* We haven't actually done anything yet.
* While IO provides helpful things like run, runToPromise, and friends here we will drive the run loop outselves
* While Wave provides helpful things like run, runToPromise, and friends here we will drive the run loop outselves
* and wire process signals to IOs interruption mechanism
*
* In the future, this will be in the waveguide-node and waveguide-browser packages (the implementation is different per platform)
Expand All @@ -177,9 +177,9 @@ const run = wave.chainError(
*/
import { makeDriver } from "../src/driver";
import { ExitTag } from "../src/exit";
function main(io: IO<never, void>): void {
function main(wave: Wave<never, void>): void {
// We need a driver to run the io
const driver = makeDriver<wave.DefaultR, never, void>();
const driver = makeDriver<never, void>();
// If we receive signals, we should interrupt
// These will cause the runloop to switch to its interrupt handling
process.on("SIGINT", () => driver.interrupt());
Expand All @@ -193,11 +193,11 @@ function main(io: IO<never, void>): void {
process.exit(0);
}
});
driver.start({}, io);
driver.start(wave);
}

/**
* Now that we have a 'main' function, we can execute our IO.
* Now that we have a 'main' function, we can execute our Wave.
* Notice how the pid file exists while the process is active but is gracefully cleared up at shutdown
* This file is built by npm run test-build to ./build/examples/01-introduction.js
*/
Expand Down
44 changes: 22 additions & 22 deletions examples/02-resources-and-failures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,54 @@
* As we saw in 01-introduction it is possible to use the bracket operators to
* ensure that cleanup actions happen in the face of failures.
* Nested brackets can be somewhat awkward to work with.
* Waveguide provides the Resource type that allows the bundling up of resource that can be consumed.
* Waveguide provides the Managed type that allows the bundling up of resource that can be consumed.
*
* Here we will implement a 'cat' utility that reads from one file and writes to another
*/

import { openFile, closeFile, read, write, main } from "./common";
import { Resource } from "../src/resource";
import * as rsrc from "../src/resource";
import { Managed } from "../src/managed";
import * as managed from "../src/managed";
import { pipe } from "fp-ts/lib/pipeable";
import * as o from "fp-ts/lib/Option";
import * as wave from "../src/wave";
import { IO } from "../src/wave";
import { Wave } from "../src/wave";


/**
* First, lets define IOs that access argv so we can read the two files we need
*/
const argvN = (n: number): IO<never, string | null> => wave.sync(() => process.argv[n] ? process.argv[n] : null);
const argvN = (n: number): Wave<never, string | null> => wave.sync(() => process.argv[n] ? process.argv[n] : null);

const inFilePath = pipe(
argvN(2),
wave.lift(o.fromNullable),
wave.chainWith(wave.fromOptionWith(() => new Error("usage: node 02-resources-and-failures.js <in> <out>"))),
wave.chainWith((o) => wave.encaseOption(o, () => new Error("usage: node 02-resources-and-failures.js <in> <out>"))),
wave.orAbort // here we use orAbort to force the error to a terminal one because there is nothing we can really do in the failure case
);

const outFilePath = pipe(
argvN(3),
wave.lift(o.fromNullable),
wave.chainWith(wave.fromOptionWith(() => new Error("usage: node 02-resources-and-failures.js <in> <out>"))),
wave.chainWith((o) => wave.encaseOption(o, () => new Error("usage: node 02-resources-and-failures.js <in> <out>"))),
wave.orAbort
);

type Errno = NodeJS.ErrnoException;

/**
* Now lets define the resources that define our input and output file handles.
* We use rsrc.suspend as the outer call because we need to perform IO in order to get the name of the file to open
* This allows us to create a resource from an IO of a resource
* We use managed.suspend as the outer call because we need to perform Wave in order to get the name of the file to open
* This allows us to create a resource from an Wave of a resource
*/
const inFileHandle: Resource<Errno, number> = rsrc.suspend(
const inFileHandle: Managed<Errno, number> = managed.suspend(
wave.map(
inFilePath,
(path) =>
// Once we have the path to open, we can create the resource itself
// Bracket is similar to wave.bracket except it doesn't have consume logic
// We are defering defining how we will consume this resource
rsrc.bracket(
managed.bracket(
openFile(path, "r"),
closeFile
)
Expand All @@ -73,11 +73,11 @@ const inFileHandle: Resource<Errno, number> = rsrc.suspend(
/**
* This is basically the same as inFileHandle only we use "w" as the open mode
*/
const outFileHandle: Resource<Errno, number> = rsrc.suspend(
const outFileHandle: Managed<Errno, number> = managed.suspend(
wave.map(
outFilePath,
(path) =>
rsrc.bracket(
managed.bracket(
openFile(path, "w"),
closeFile
)
Expand All @@ -86,24 +86,24 @@ const outFileHandle: Resource<Errno, number> = rsrc.suspend(


/**
* Resource are also little programs that can produce resources in a safe manner.
* Managed are also little programs that can produce resources in a safe manner.
* Thus, we can use chain and map to glue them together
*/
const handles: Resource<Errno, [number, number]> =
rsrc.chain(inFileHandle, (inh) => rsrc.map(outFileHandle, (outh) => [inh, outh]));
const handles: Managed<Errno, [number, number]> =
managed.chain(inFileHandle, (inh) => managed.map(outFileHandle, (outh) => [inh, outh]));
// or you could use
// again, not that this call to zip is perfectly safe because like IO,
// Resource doesn't do anything until it is run
rsrc.zip(inFileHandle, outFileHandle)
// again, not that this call to zip is perfectly safe because like Wave,
// Managed doesn't do anything until it is run
managed.zip(inFileHandle, outFileHandle)


/**
* Now that we have our resources, we need a way of consuming them
*/
const cat = (blocksz: number) => (handles: [number, number]): IO<Errno, void> => {
const cat = (blocksz: number) => (handles: [number, number]): Wave<Errno, void> => {
const [inHandle, outHandle] = handles;

function copy(): IO<Errno, void> {
function copy(): Wave<Errno, void> {
return wave.chain(
read(inHandle, blocksz),
([buffer, ct]) =>
Expand All @@ -121,7 +121,7 @@ const cat = (blocksz: number) => (handles: [number, number]): IO<Errno, void> =>
/**
* We can now wire everything together
*/
const run: IO<Errno, void> = rsrc.use(handles, cat(1028));
const run: Wave<Errno, void> = managed.use(handles, cat(1028));

/**
* An finally, we actually do something
Expand Down
Loading

0 comments on commit d1da78c

Please sign in to comment.