Giter VIP home page Giter VIP logo

baccano's Introduction

Baccano

A railway-oriented programming helper library.

forthebadge forthebadge forthebadge

npm npm

Intro to Railway-oriented Programming

The term was coined by Scott Wlaschin of F# for Fun and Profit. Here's his talk on the subject. This method of programming allows us to deal with errors functionally in our applications. It involves having two paths (or tracks) if you will, one is the success path, and one is the error path. You start with the success path and when an error occurs, expected or not, you move to the error path. It's easier to understand if you try it out.

Getting Started

Importing the library

To use the library, first import it:

In Node:

const { compose, fromUnary, SomeError, Success } = require('baccano')

As ES Module:

import { compose, fromUnary, SomeError, Success } from 'baccano'

On the browser:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/baccano.min.js"></script>
<script>
    const { compose, fromUnary, SomeError, Success } = Baccano
</script>

Using the library

Let's learn how to use the library by creating a pipeline of mathematical operations.

Define Errors

Features should not be the only things to be considered when planning software. Errors, or anything that can go wrong should also be planned. Hence, we have to define the possible errors that might occur in a particular pipeline of functions. Normally we'd use a type or a variant for this but we're in JavaScript so I suggest using Symbols for them. We'll be using division in the pipeline so we have to plan for a division by zero case.

const DIVISON_BY_ZERO = Symbol.for('DIVISION_BY_ZERO')

Define Functions

Now we'll define the functions that we will use. In Railway-Oriented Programming, a function should either return a success with the value, or some error with the error message. This would be easy in type-safe functional programming languages but that is not the case with JavaScript so we'll need some helpers from the library.

import { SomeError, Success } from 'baccano'

After importing our helper functions, let's define the functions we're going to use.

const divideBy = n => x => {
  if (n === 0) {
    return SomeError(DIVISON_BY_ZERO, "Cannot divide by zero.")
  } else {
    return Success(n / x)
  }
}

const plusOne = x => {
  return Success(x + 1)
}

In this case, we create a function divideBy which takes a number and returns a function that accepts a number and divides it by the previous number. If the previous number is zero, we return a DIVISON_BY_ZERO error using the SomeError function, which takes a value that represents the error and the error message. Else, we return a success using the Success function which accepts a value.

The plusOne function just takes a number and returns a Success with the number incremented by one.

Convert to ROP-compatible functions

If you noticed, our functions accept a single value and return a single value, which could either be success or error. In Railway-Oriented Programming, functions should accept two values and return two values, which represent the happy/success path and the error path. So we have to convert them into compatible functions. For that, we need the fromUnary function.

import { fromUnary, SomeError, Success } from 'baccano'

const compatibleDivideByZero = fromUnary(divideBy(0))
const compatiblePlusOne = fromUnary(plusOne)

Now we can use these functions in our pipeline.

Composing functions into a pipeline

Now that we have compatible functions, let's compose them into a single function using the pipeline function.

import { fromUnary, pipeline, SomeError, Success } from 'baccano'

const pipeline = compose(compatiblePlusOne, compatibleDivideByZero, compatiblePlusOne)

This pipeline function is an asynchronous function which accepts a value and returns a Promise that resolves into the value after it has been run through the series of functions.

We run the pipeline like this:

(async () => {

  // Get result of the pipeline
  const result = await pipeline(2)

  // Display end value
  console.log(result.value) // 4

  // Display errors
  console.log(result.errors) // [ { message: 'Cannot divide by zero.', type: Symbol(DIVISON_BY_ZERO) } ]
})()

That's it for this example!

Complete Example Code

Here is the complete example code:

// Import library
import { compose, fromUnary, SomeError, Success } from 'baccano'
// or const { compose, fromUnary, SomeError, Success } = Baccano

// Define Errors
const DIVISON_BY_ZERO = Symbol.for('DIVISION_BY_ZERO')

const divideBy = n => x => {
  if (n === 0) {
    return SomeError(DIVISON_BY_ZERO, "Cannot divide by zero.")
  } else {
    return Success(n / x)
  }
}

const plusOne = x => {
  return Success(x + 1)
}

// Take unary functions and convert them to compatible functions
const compatibleDivideByZero = fromUnary(divideBy(0))
const compatiblePlusOne = fromUnary(plusOne)

// Create pipeline of functions
const pipeline = compose(compatiblePlusOne, compatibleDivideByZero, compatiblePlusOne)

(async () => {

  // Get result of the pipeline
  const result = await pipeline(2)

  // Display end value
  console.log(result.value) // 4

  // Display errors
  console.log(result.errors) // [ { message: 'Cannot divide by zero.', type: Symbol(DIVISON_BY_ZERO) } ]
})()

Notes

  • I named the library Baccano because when I thought about trains and railways, I thought of the Baccano anime. @egoist is not the only one fond of anime references LOL.

Roadmap

  • Handling Asynchronous functions
  • Parallel execution
  • Lazy execution

License

MIT

baccano's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

mendaomn

baccano's Issues

There are two minified index files

Before attempting a PR to fix this, I just want to make sure this is not intentional.

I noticed you have both an index.js minified file and a index.min.js one. If you didn't mean to have both, I think we can easily fix that by changing this line in the rollup config file

from this:

{
      file: 'index.js',
      format: 'cjs'
},

to this:

{
      file: 'index.min.js',
      format: 'cjs'
},

What do you think?

The library requires a polyfill

While working at #4 I noticed the library does require a polyfill to work properly (regeneratorRuntime), should we add it to the README?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.