npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

functir

v1.3.1

Published

Functional programming library for JavaScript. True FP in JS/TS!

Downloads

36

Readme

About

Functir is a functional programming library for JavaScript. True functional programming in JS!

Installation

Core data types

Core features

Installation

via pnpm:

pnpm add functir

via npm:

npm i functir

Core data types

Box

Box is super simple – it's class that containing some immutable value. You cannot change box wrapped value directly, but you can transform it and get new Box instance.

import { Box } from 'functir'

// 1. Let's create wrapped number value
const wrapped = new Box(1)
console.log(wrapped.value) // 1

// 2. Let's change it (immutable!)
const wrappedTransformed = wrapped.mutate(_ => _ + 100)
console.log(wrappedTransformed.value) // 101

Other types described in future also will also use Box to contain some value via class construction.

Option

Mostly Option data type is used in pattern matching (described in future). Option is primary designed to be one of two values: None or Some(value). Some value is works same as Box – wraps some value.

import { Option, None, Some } from 'functir'

// Both of values is Option<number>
const valueOne: Option<number> = None()
const valueTwo: Option<number> = Some(200)

// You can mutate value of `Some` by converting to `Box`
console.log(valueTwo.asBox) // Box(200)
console.log(valueTwo.asBox.mutate(_ => _ + 5)) // Box(205)

Either

The Either<TLeft, TRight> is very similar to Option, but the value can be Left(value: TLeft) or Right(value: TRight):

import { Either, Left, Right } from 'functir'

// Either<number, string> = Left<number> | Right<string>
// Both of values is Either<number, string>
const valueOne: Either<number, string> = Left(100)
const valueTwo: Either<number, string> = Right('200')

// You can mutate value by converting to `Box`
console.log(valueOne.asBox) // Box(100)
console.log(valueOne.asBox.mutate(_ => _ + 5)) // Box(105)

LikeBox

What does Box, Option, Either have in common? - they are all implements LikeBox interface, but in different variations. We have 3 main variations of LikeBox interface:

  • LikeBox (None type uses it, it doesn't have wrapped value inside)
  • LikeFilledBox (Box type uses it, inherits LikeBox but have wrapped value inside)
  • LikeConvertibleFilledBox (Option & Either types uses it, inherits LikeFilledBox but have asBox converter inside)

How you can use those interfaces? Like that:

import { Box } from 'functir'

class SomeValue extends Box.filled<number> {}

const wrapped = new SomeValue(200)
console.log(wrapped.value) // 200
console.log(wrapped.asBox) // Box(200)

Illustrated code produces for you a LikeConvertibleFilledBox class with wrapped number, that you can use. The same thing does None, Some, Left, Right implementation.

Now let's see what functions does LikeBox provides:

Usage: match/pipe

Shorthand for using pattern matching or piping:

import { Option, None, Some, match, is } from 'functir'

const boxNone: Option<number> = None()
const boxSome: Option<number> = Some(200)

// `.match`/`.pipe` – this functions is works on
// `Box`, `Option`, `Either`, `Box.filled<T>`
// becase they are `LikeBox` implementations

// Pattern matching
boxNone.match([
	is(None, _ => "none"),
	is(Some, _ => "some")
]) // none
boxSome.match([
	is(None, _ => "none"),
	is(Some, _ => "some")
]) // some

// Piping
boxSome.pipe([
	_ => _ + 100, // 200 + 100 = 300
	_ => _ + 50 // 300 + 50 = 350
]) // 350

Usage: flatten

This magic function is just flattens wrapped LikeBox values:

import { Box, Some } from 'functir'

// `Box` inside `Box` nested 
const deep = new Box(new Box(5))
console.log(deep.flatten()) // 5

// Different `LikeBox` implementations nested
const complexDeep = new Box(new Some(new Box(new Some(20))))
console.log(complexDeep.flatten()) // 20

Seq

Seq (Sequence) is helpful data type that you can use as alternative for arrays. Why Seq instead of arrays?

  1. Seq is fully immutable-safe (doesn't provides any mutable methods)
  2. Seq provides a lot of methods that arrays doesn't

Usage is simple:

import { Seq } from 'functir'

const seq = new Seq<number>(1, 2, 3)

console.log(seq.asArray) // [1, 2, 3]

Usage: methods

Seq provides many different immutable methods you can use:

// Just copies current sequence
seq.copy() // Seq(1, 2, 3)
// Converting into array, set, map
seq.asArray // [1, 2, 3]
seq.asSet // Set(1, 2, 3)
seq.asMap // Map({ 0: 1, 1: 2, 2: 3 })
// Adds value to start of seq
seq.prepended(10) // Seq(10, 1, 2, 3)

// Adds value to end of seq
seq.appended(10) // Seq(1, 2, 3, 10)
// Auto sorting, like [].sort()
seq.autoSorted(0, 10) // Seq(1, 2, 3)

// Sort using predicate
seq.sorted((a, b) => (a > b ? -1 : 1)) // Seq(3, 2, 1)
// Reverses values
seq.reversed() // Seq(3, 2, 1)
// Maps values
seq.mapped(_ => _ + 10) // Seq(11, 12, 13)
// Filters values
seq.filtered(_ => _ > 1) // Seq(2, 3)
// Pads from start with value (to length of 6)
seq.padStart(6, -1) // Seq(-1, -1, -1, 1, 2, 3)

// Pads from end with value (to length of 6)
seq.padEnd(6, -1) // Seq(1, 2, 3, -1, -1, -1)
// Get index of item (from start)
new Seq(1, 1, 1).indexOf(1) // 0

// Get index of item (from end)
new Seq(1, 1, 1).lastIndexOf(1) // 2

Trait

Trait is the concept from Scala/Rust languages. Using Trait you can easily create class (DTO/DAO) for some data model:

import { Trait } from 'functir'

// Create a class using trait
// Trait will automatically create immutable fields, constructor for the class
const UserDTO = Trait<{
	readonly nickname: string
	readonly age: number
}>();

// Create instance of trait
// All fields of trait should be passed to constructor
const jake = new UserDTO({
	nickname: 'Jake',
	age: 20
})

// You can convert trait instances into objects or `Box`
console.log(jake.asObject) // { nickname: 'Jake', age: 20 }
console.log(jake.asBox) // Box({ nickname: 'Jake', age: 20 )

Trait of course produces immutable classes (with copy method), you can use that for creating services:

import { Trait, Seq } from 'functir'

/**
 * Immutable service that builds pizza
 */
class PizzaService extends Trait<{
    readonly size: 'sm' | 'md' | 'lg';
    readonly toppings: Seq<'meat' | 'pineapple' | 'cheese'>;
}>() {
	constructor() {
		super({
			size: 'sm',
			toppings: new Seq()
		})
	}

    public setSize = (size: 'sm' | 'md' | 'lg') =>
		this.copy({ size })
	
    public addTopping = (topping: 'meat' | 'pineapple' | 'cheese') =>
        this.copy({ toppings: this.toppings.appended(topping) })
}

// Immutability! Let's build pizza
const myPizza = new PizzaService()
	.setSize('md')
	.addTopping('meat')
	.addTopping('cheese')
	.asObject

console.log(myPizza) // { size: 'md', toppings: ['meat', 'cheese'] }

IO

IO is also another helpful type for defining input and output of function:

import { IO } from 'functir'

// Operation that converts string to number
const toNumber: IO<string, number> = 
	_ => parseInt(_)

console.log(toNumber('123')) // 123

Also if function is async you can use AsyncIO:

import { AsyncIO } from 'functir'

// Operation that async (just return input as output)
const someFunction: AsyncIO<string, string> =
	async _ => _

There's third parameter in IO that we didn't mentioned, it's used to define what Throwable (described below) can be given by function:

import { IO, ThrowableTrait } from 'functir'

// Our own throwable error
class TooLongError extends ThrowableTrait('MyOwnError', 'Value was too long') {}

// Our operation described:
// input = string, output = string, throws = MyOwnError
// if input length > 5 we return it, otherwise TooLongError given
const someFunction: IO<string, string, TooLongError> =
	_ => _.length > 5 ? _ : new TooLongError

Throwable

The Throwable is the helpful type used with IO to annotate what error can happen in a function:

import { Throwable, ThrowableTrait } from 'functir'

// Simple error
const someError: Throwable = new Error('Some custom throwed error')

Also you can create own throwable error class using ThrowableTrait:

import { Throwable, ThrowableTrait } from 'functir'

// Create own throwable error
class CustomError extends ThrowableTrait('CustomError') {}

// Our custom error
const someError2: Throwable = new CustomError('Some super custom error')

Core features

Pattern matching

Pattern matching if well-known pattern that allows to match value to a different cases like in a switch-case, but more clean syntax and ability to check for instances of classes.

Example:

import { match, is, _, Some, None } from 'functir'

// 1 if Some
// 0 if None
// -1 otherwise
const result = match(Some(100))([
	is(Some, _ => 1),
	is(None, _ => 0),
	is(_, _ => -1)
])

console.log(result) // 1

The _ is special symbol used to handle case none of cases is matched.

Piping

Will be described later.