All Projects → gcanti → Monocle Ts

gcanti / Monocle Ts

Licence: mit
Functional optics: a (partial) porting of Scala monocle

Programming Languages

typescript
32286 projects
optics
23 projects

Projects that are alternatives of or similar to Monocle Ts

Language Ext
C# functional language extensions - a base class library for functional programming
Stars: ✭ 3,964 (+503.35%)
Mutual labels:  lenses, functional-programming
Python Lenses
A python lens library for manipulating deeply nested immutable structures
Stars: ✭ 179 (-72.75%)
Mutual labels:  lenses, functional-programming
Lambda
Functional patterns for Java
Stars: ✭ 737 (+12.18%)
Mutual labels:  lenses, functional-programming
Quicklens
Modify deeply nested case class fields
Stars: ✭ 641 (-2.44%)
Mutual labels:  lenses, functional-programming
Funfix
Functional Programming Library for JavaScript, TypeScript and Flow ✨⚡️
Stars: ✭ 596 (-9.28%)
Mutual labels:  functional-programming
Ramda Adjunct
Ramda Adjunct is the most popular and most comprehensive set of functional utilities for use with Ramda, providing a variety of useful, well tested functions with excellent documentation.
Stars: ✭ 550 (-16.29%)
Mutual labels:  functional-programming
Pampy.js
Pampy.js: Pattern Matching for JavaScript
Stars: ✭ 544 (-17.2%)
Mutual labels:  functional-programming
Bow
🏹 Bow is a cross-platform library for Typed Functional Programming in Swift
Stars: ✭ 538 (-18.11%)
Mutual labels:  functional-programming
Freestyle
A cohesive & pragmatic framework of FP centric Scala libraries
Stars: ✭ 627 (-4.57%)
Mutual labels:  functional-programming
Glance
A visual Haskell
Stars: ✭ 620 (-5.63%)
Mutual labels:  functional-programming
Fkit
A functional programming toolkit for JavaScript.
Stars: ✭ 588 (-10.5%)
Mutual labels:  functional-programming
Fastcore
Python supercharged for the fastai library
Stars: ✭ 565 (-14%)
Mutual labels:  functional-programming
Felix
The Felix Programming Language
Stars: ✭ 609 (-7.31%)
Mutual labels:  functional-programming
Learn Fp
learn-by-doing course/tutorial for functional programming on scala
Stars: ✭ 548 (-16.59%)
Mutual labels:  functional-programming
Clio
Clio is a functional, parallel, distributed programming language.
Stars: ✭ 555 (-15.53%)
Mutual labels:  functional-programming
Moses
Utility library for functional programming in Lua
Stars: ✭ 541 (-17.66%)
Mutual labels:  functional-programming
Bash Oo Framework
Bash Infinity is a modern standard library / framework / boilerplate for Bash
Stars: ✭ 5,247 (+698.63%)
Mutual labels:  functional-programming
Mybatis Dynamic Sql
SQL DSL (Domain Specific Language) for Kotlin and Java. Supports rendering for MyBatis or Spring JDBC Templates
Stars: ✭ 614 (-6.54%)
Mutual labels:  functional-programming
Functional Programming Learning Path
A Learning Path for Functional Programming
Stars: ✭ 582 (-11.42%)
Mutual labels:  functional-programming
Fsharpplus
Extensions for F#
Stars: ✭ 589 (-10.35%)
Mutual labels:  lenses

build status dependency status npm downloads

Motivation

(Adapted from monocle site)

Modifying immutable nested object in JavaScript is verbose which makes code difficult to understand and reason about.

Let's have a look at some examples:

interface Street {
  num: number
  name: string
}
interface Address {
  city: string
  street: Street
}
interface Company {
  name: string
  address: Address
}
interface Employee {
  name: string
  company: Company
}

Let’s say we have an employee and we need to upper case the first character of his company street name. Here is how we could write it in vanilla JavaScript

const employee: Employee = {
  name: 'john',
  company: {
    name: 'awesome inc',
    address: {
      city: 'london',
      street: {
        num: 23,
        name: 'high street'
      }
    }
  }
}

const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1)

const employeeCapitalized = {
  ...employee,
  company: {
    ...employee.company,
    address: {
      ...employee.company.address,
      street: {
        ...employee.company.address.street,
        name: capitalize(employee.company.address.street.name)
      }
    }
  }
}

As we can see copy is not convenient to update nested objects because we need to repeat ourselves. Let's see what could we do with monocle-ts

import { Lens } from 'monocle-ts'

const company = Lens.fromProp<Employee>()('company')
const address = Lens.fromProp<Company>()('address')
const street = Lens.fromProp<Address>()('street')
const name = Lens.fromProp<Street>()('name')

compose takes two Lenses, one from A to B and another one from B to C and creates a third Lens from A to C. Therefore, after composing company, address, street and name, we obtain a Lens from Employee to string (the street name). Now we can use this Lens issued from the composition to modify the street name using the function capitalize

const capitalizeName = company.compose(address).compose(street).compose(name).modify(capitalize)

assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized)

You can use the fromPath API to avoid some boilerplate

import { Lens } from 'monocle-ts'

const name = Lens.fromPath<Employee>()(['company', 'address', 'street', 'name'])

const capitalizeName = name.modify(capitalize)

assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized) // true

Here modify lift a function string => string to a function Employee => Employee. It works but it would be clearer if we could zoom into the first character of a string with a Lens. However, we cannot write such a Lens because Lenses require the field they are directed at to be mandatory. In our case the first character of a string is optional as a string can be empty. So we need another abstraction that would be a sort of partial Lens, in monocle-ts it is called an Optional.

import { Optional } from 'monocle-ts'
import { some, none } from 'fp-ts/lib/Option'

const firstLetterOptional = new Optional<string, string>(
  (s) => (s.length > 0 ? some(s[0]) : none),
  (a) => (s) => (s.length > 0 ? a + s.substring(1) : s)
)

const firstLetter = company.compose(address).compose(street).compose(name).asOptional().compose(firstLetterOptional)

assert.deepStrictEqual(firstLetter.modify((s) => s.toUpperCase())(employee), employeeCapitalized)

Similarly to compose for lenses, compose for optionals takes two Optionals, one from A to B and another from B to C and creates a third Optional from A to C. All Lenses can be seen as Optionals where the optional element to zoom into is always present, hence composing an Optional and a Lens always produces an Optional.

TypeScript compatibility

The stable version is tested against TypeScript 3.5.2, but should run with TypeScript 2.8.0+ too

monocle-ts version required typescript version
2.0.x+ 3.5+
1.x+ 2.8.0+

Note. If you are running < [email protected] you have to polyfill unknown.

You can use unknown-ts as a polyfill.

Documentation

Experimental modules

From [email protected]+ you can use the following experimental modules

  • Iso
  • Lens
  • Prism
  • Optional
  • Traversal
  • At
  • Ix

which implement the same features contained in index.ts but are pipe-based instead of class-based.

Here's the same examples with the new API

interface Street {
  num: number
  name: string
}
interface Address {
  city: string
  street: Street
}
interface Company {
  name: string
  address: Address
}
interface Employee {
  name: string
  company: Company
}

const employee: Employee = {
  name: 'john',
  company: {
    name: 'awesome inc',
    address: {
      city: 'london',
      street: {
        num: 23,
        name: 'high street'
      }
    }
  }
}

const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1)

const employeeCapitalized = {
  ...employee,
  company: {
    ...employee.company,
    address: {
      ...employee.company.address,
      street: {
        ...employee.company.address.street,
        name: capitalize(employee.company.address.street.name)
      }
    }
  }
}

import * as assert from 'assert'
import * as L from 'monocle-ts/lib/Lens'
import { pipe } from 'fp-ts/lib/function'

const capitalizeName = pipe(
  L.id<Employee>(),
  L.prop('company'),
  L.prop('address'),
  L.prop('street'),
  L.prop('name'),
  L.modify(capitalize)
)

assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized)

import * as O from 'monocle-ts/lib/Optional'
import { some, none } from 'fp-ts/lib/Option'

const firstLetterOptional: O.Optional<string, string> = {
  getOption: (s) => (s.length > 0 ? some(s[0]) : none),
  set: (a) => (s) => (s.length > 0 ? a + s.substring(1) : s)
}

const firstLetter = pipe(
  L.id<Employee>(),
  L.prop('company'),
  L.prop('address'),
  L.prop('street'),
  L.prop('name'),
  L.composeOptional(firstLetterOptional)
)

assert.deepStrictEqual(
  pipe(
    firstLetter,
    O.modify((s) => s.toUpperCase())
  )(employee),
  employeeCapitalized
)
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].