├── docs ├── .nojekyll ├── _sidebar.md ├── _coverpage.md ├── index.html ├── sw.js ├── basics.md ├── equality.md ├── lib │ ├── codesandbox.js │ └── prism.css ├── interoperability.md ├── numbers.md ├── async.md └── ordering.md ├── .prettierrc ├── .gitignore ├── README.md ├── package.json ├── tsconfig.json └── LICENSE /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Recipes 2 | 3 | These "how-to" recipes are meant to help you apply [fp-ts](https://gcanti.github.io/fp-ts/) with practical examples. 4 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Basics](basics.md) 2 | - [Async: Promises and Tasks](async.md) 3 | - [Determine equality](equality.md) 4 | - [Determine order](ordering.md) 5 | - [Work with numbers](numbers.md) 6 | - [Work with non-functional code](interoperability.md) 7 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | # _fp-ts_ recipes 2 | 3 | > A collection of practical examples for using the [fp-ts](https://gcanti.github.io/fp-ts/) library 4 | 5 | - Learn to use _fp-ts_ by example 6 | - Discover functional programming patterns 7 | - Explore each code example in a live sandbox 8 | 9 | [GitHub](https://github.com/grossbart/fp-ts-recipes/) 10 | [Get Started](basics.md) 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fp-ts-recipes", 3 | "description": "Practical examples for using the fp-ts TypeScript library.", 4 | "version": "1.0.0", 5 | "author": "Peter Gassner ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "docsify serve docs", 9 | "test": "literate-ts docs/*.md" 10 | }, 11 | "dependencies": { 12 | "fp-ts": "^2.9.3" 13 | }, 14 | "devDependencies": { 15 | "docsify-cli": "^4.4.2", 16 | "literate-ts": "^1.1.1", 17 | "prettier": "^2.2.1", 18 | "typescript": "^4.1.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "baseUrl": ".", 5 | "paths": { 6 | "fp-ts": ["node_modules/fp-ts/es6"], 7 | "fp-ts/function": ["node_modules/fp-ts/es6/function"], 8 | "fp-ts/Eq": ["node_modules/fp-ts/es6/Eq"], 9 | "fp-ts/Option": ["node_modules/fp-ts/es6/Option"], 10 | }, 11 | 12 | "module": "commonjs", 13 | "moduleResolution": "node", 14 | "target": "es2015", 15 | "lib": ["es2015", "dom"], 16 | 17 | "strict": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noImplicitReturns": true, 20 | "noUncheckedIndexedAccess": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "stripInternal": true, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Peter Gassner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fp-ts recipes 7 | 8 | 9 | 10 | 11 |
12 | 17 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/sw.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * docsify sw.js 3 | * =========================================================== 4 | * Copyright 2016 @huxpro 5 | * Licensed under Apache 2.0 6 | * Register service worker. 7 | * ========================================================== */ 8 | 9 | const RUNTIME = 'docsify' 10 | const HOSTNAME_WHITELIST = [ 11 | self.location.hostname, 12 | 'fonts.gstatic.com', 13 | 'fonts.googleapis.com', 14 | 'cdn.jsdelivr.net' 15 | ] 16 | 17 | // The Util Function to hack URLs of intercepted requests 18 | const getFixedUrl = (req) => { 19 | var now = Date.now() 20 | var url = new URL(req.url) 21 | 22 | // 1. fixed http URL 23 | // Just keep syncing with location.protocol 24 | // fetch(httpURL) belongs to active mixed content. 25 | // And fetch(httpRequest) is not supported yet. 26 | url.protocol = self.location.protocol 27 | 28 | // 2. add query for caching-busting. 29 | // Github Pages served with Cache-Control: max-age=600 30 | // max-age on mutable content is error-prone, with SW life of bugs can even extend. 31 | // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. 32 | // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 33 | if (url.hostname === self.location.hostname) { 34 | url.search += (url.search ? '&' : '?') + 'cache-bust=' + now 35 | } 36 | return url.href 37 | } 38 | 39 | /** 40 | * @Lifecycle Activate 41 | * New one activated when old isnt being used. 42 | * 43 | * waitUntil(): activating ====> activated 44 | */ 45 | self.addEventListener('activate', event => { 46 | event.waitUntil(self.clients.claim()) 47 | }) 48 | 49 | /** 50 | * @Functional Fetch 51 | * All network requests are being intercepted here. 52 | * 53 | * void respondWith(Promise r) 54 | */ 55 | self.addEventListener('fetch', event => { 56 | // Skip some of cross-origin requests, like those for Google Analytics. 57 | if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) { 58 | // Stale-while-revalidate 59 | // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale 60 | // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1 61 | const cached = caches.match(event.request) 62 | const fixedUrl = getFixedUrl(event.request) 63 | const fetched = fetch(fixedUrl, { cache: 'no-store' }) 64 | const fetchedCopy = fetched.then(resp => resp.clone()) 65 | 66 | // Call respondWith() with whatever we get first. 67 | // If the fetch fails (e.g disconnected), wait for the cache. 68 | // If there’s nothing in cache, wait for the fetch. 69 | // If neither yields a response, return offline pages. 70 | event.respondWith( 71 | Promise.race([fetched.catch(_ => cached), cached]) 72 | .then(resp => resp || fetched) 73 | .catch(_ => { /* eat any errors */ }) 74 | ) 75 | 76 | // Update the cache with the version we fetched (only for ok status) 77 | event.waitUntil( 78 | Promise.all([fetchedCopy, caches.open(RUNTIME)]) 79 | .then(([response, cache]) => response.ok && cache.put(event.request, response)) 80 | .catch(_ => { /* eat any errors */ }) 81 | ) 82 | } 83 | }) 84 | -------------------------------------------------------------------------------- /docs/basics.md: -------------------------------------------------------------------------------- 1 | # Basics 2 | 3 | _fp-ts_ is a library for _typed functional programming_ in TypeScript. The code examples presented here help you get familiar with the library and its style of programming. You can learn more about using _fp-ts_ on the [fp-ts website](https://gcanti.github.io/fp-ts/). 4 | 5 | ## Import patterns 6 | 7 | When browsing the _fp-ts_ docs, you will find various import patterns. To help you better understand the pros and cons of each pattern, we collected some of them below so you can find the best fit for your project. 8 | 9 | ### Top-level imports (recommended) 10 | 11 | If you’re just getting started with _fp-ts_, we recommend that you use the top-level exports of the library. This is the pattern we use for the _fp-ts_ recipes presented on this website. 12 | 13 | - **Pros:** Easy to use; no naming conflicts; auto-imports 14 | - **Cons:** More verbose; not [tree-shakeable](https://en.wikipedia.org/wiki/Tree_shaking) 15 | 16 | 17 | 18 | ```ts 19 | import { option } from "fp-ts"; 20 | const value: option.Option = option.some(1); 21 | ``` 22 | 23 | When using this pattern, be aware that the value `option` in the following two imports _is not the same:_ 24 | 25 | 1. `import { option } from 'fp-ts'` is not the same as 26 | 2. `import { option } from 'fp-ts/Option'` 27 | 28 | The former refers to the Option _module_, while the latter refers to the Option [typeclass instance](https://joyofhaskell.com/posts/2017-03-15-typeclasses-in-translation.html). 29 | 30 | ?> In the import example **1.**, use `option.option` to retrieve the typeclass instance. 31 | 32 | ### Module imports 33 | 34 | Many examples in the _fp-ts_ docs import from the individual modules directly as seen below. 35 | 36 | - **Pros:** Precise; tree-shakeable; auto-imports 37 | - **Cons:** Low-level; longer import statements; naming conflicts 38 | 39 | 40 | 41 | ```ts 42 | import { Option, some } from "fp-ts/Option"; 43 | const value: Option = some(1); 44 | ``` 45 | 46 | ### Namespaces 47 | 48 | A middle ground that is also seen in many _fp-ts_ examples is importing a module into a namespace. 49 | 50 | - **Pros:** Concise 51 | - **Cons:** Low-level; difficult for newcomers; no auto-imports; not tree-shakeable 52 | 53 | 54 | 55 | ```ts 56 | import * as O from "fp-ts/Option"; 57 | const value: O.Option = O.some(1); 58 | ``` 59 | 60 | ## Pipe and Flow 61 | 62 | _fp-ts_ is all about composing functions. For this, _fp-ts_ provides the `pipe` function and its variation `flow`. 63 | 64 | `pipe` takes a value as its first argument and then threads it through the remaining functions, from left-to-right. We could nest these functions instead, as shown below for `one`, but that quickly becomes impractical. 65 | 66 | ```ts 67 | import { pipe } from "fp-ts/function"; 68 | const add5 = (x: number) => x + 5; 69 | const multiply2 = (x: number) => x * 2; 70 | 71 | const one = multiply2(add5(3)); // Ok 72 | const two = pipe(3, add5, multiply2); // Better 73 | 74 | console.log(one, two); // 16, 16 75 | ``` 76 | 77 | `flow` is a variation of `pipe` and can make your code more concise. But be aware of the problems with the [pointfree style](https://wiki.haskell.org/Pointfree) that this allows: it can obfuscate your code if you’re not careful. 78 | 79 | ```ts 80 | import { flow, pipe } from "fp-ts/function"; 81 | const add5 = (x: number) => x + 5; 82 | 83 | const runPipe = (x: number) => pipe(x, add5); 84 | const runFlow = flow(add5); 85 | 86 | console.log(runPipe(3), runFlow(3)); // 8, 8 87 | ``` 88 | 89 | You can learn more about `pipe` and `flow` in the [Practical Guide to fp-ts](https://rlee.dev/practical-guide-to-fp-ts-part-1). 90 | -------------------------------------------------------------------------------- /docs/equality.md: -------------------------------------------------------------------------------- 1 | # How to determine if two things are equal 2 | 3 | With _fp-ts_ you can test whether two values are equal using a `Eq`. You can also compose equality functions to test deep structures and create your own definitions of equality. 4 | 5 | We show the most common usages here, but if you need more ways to check for equality, be sure to read the [Eq](https://gcanti.github.io/fp-ts/modules/Eq.ts) documentation page. 6 | 7 | ## Primitive equality 8 | 9 | ```ts 10 | import { boolean, date, number, string } from "fp-ts"; 11 | 12 | boolean.Eq.equals(true, true); // true 13 | date.Eq.equals(new Date("1984-01-27"), new Date("1984-01-27")); // true 14 | number.Eq.equals(3, 3); // true 15 | string.Eq.equals("Cyndi", "Cyndi"); // true 16 | ``` 17 | 18 | ## Compare structures 19 | 20 | ```ts 21 | import { number } from "fp-ts"; 22 | import { Eq, struct } from "fp-ts/Eq"; 23 | 24 | type Point = { 25 | x: number; 26 | y: number; 27 | }; 28 | 29 | const eqPoint: Eq = struct({ 30 | x: number.Eq, 31 | y: number.Eq, 32 | }); 33 | 34 | eqPoint.equals({ x: 0, y: 0 }, { x: 0, y: 0 }); // true 35 | ``` 36 | 37 | This structure can be combined further: 38 | 39 | ```ts 40 | import { number } from "fp-ts"; 41 | import { Eq, struct } from "fp-ts/Eq"; 42 | 43 | type Point = { 44 | x: number; 45 | y: number; 46 | }; 47 | 48 | const eqPoint: Eq = struct({ 49 | x: number.Eq, 50 | y: number.Eq, 51 | }); 52 | 53 | type Vector = { 54 | from: Point; 55 | to: Point; 56 | }; 57 | 58 | const eqVector: Eq = struct({ 59 | from: eqPoint, 60 | to: eqPoint, 61 | }); 62 | 63 | eqVector.equals( 64 | { from: { x: 0, y: 0 }, to: { x: 0, y: 0 } }, 65 | { from: { x: 0, y: 0 }, to: { x: 0, y: 0 } } 66 | ); // true 67 | ``` 68 | 69 | ## Compare arrays 70 | 71 | ```ts 72 | import { array, string } from "fp-ts"; 73 | 74 | const eqArrayOfStrings = array.getEq(string.Eq); 75 | 76 | eqArrayOfStrings.equals(["Time", "After", "Time"], ["Time", "After", "Time"]); // true 77 | ``` 78 | 79 | Test the equality of structures nested within arrays: 80 | 81 | ```ts 82 | import { array, number } from "fp-ts"; 83 | import { Eq, struct } from "fp-ts/Eq"; 84 | 85 | type Point = { 86 | x: number; 87 | y: number; 88 | }; 89 | 90 | const eqPoint: Eq = struct({ 91 | x: number.Eq, 92 | y: number.Eq, 93 | }); 94 | 95 | const eqArrayOfPoints = array.getEq(eqPoint); 96 | 97 | eqArrayOfPoints.equals( 98 | [ 99 | { x: 0, y: 0 }, 100 | { x: 4, y: 0 }, 101 | ], 102 | [ 103 | { x: 0, y: 0 }, 104 | { x: 4, y: 0 }, 105 | ] 106 | ); // true 107 | ``` 108 | 109 | ## Custom definitions 110 | 111 | In this example, two users are equal if their `userId` field is equal. 112 | 113 | ```ts 114 | import { eq, number } from "fp-ts"; 115 | 116 | type User = { 117 | userId: number; 118 | name: string; 119 | }; 120 | 121 | const eqUserId = eq.contramap((user: User) => user.userId)(number.Eq); 122 | 123 | eqUserId.equals({ userId: 1, name: "Giulio" }, { userId: 1, name: "Giulio Canti" }); // true 124 | eqUserId.equals({ userId: 1, name: "Giulio" }, { userId: 2, name: "Giulio" }); // false 125 | ``` 126 | 127 | ## More `Eq` instances 128 | 129 | Many data types provide `Eq` instances. Here's [Option](https://gcanti.github.io/fp-ts/modules/Option.ts): 130 | 131 | ```ts 132 | import { option, number } from "fp-ts"; 133 | 134 | const E = option.getEq(number.Eq); 135 | 136 | E.equals(option.some(3), option.some(3)); // true 137 | E.equals(option.none, option.some(4)); // false 138 | E.equals(option.none, option.none); // true 139 | ``` 140 | 141 | It works similarly for [Either](https://gcanti.github.io/fp-ts/modules/Either.ts) and other types where it is possible to determine equality: 142 | 143 | ```ts 144 | import { either, number, string } from "fp-ts"; 145 | 146 | const E = either.getEq(string.Eq, number.Eq); 147 | 148 | E.equals(either.right(3), either.right(3)); // true 149 | E.equals(either.left("3"), either.right(3)); // false 150 | E.equals(either.left("3"), either.left("3")); // true 151 | ``` 152 | -------------------------------------------------------------------------------- /docs/lib/codesandbox.js: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // Helper functions for CodeSandbox 3 | // @see https://github.com/codesandbox/codesandbox-importers/blob/master/packages/import-utils/src/api/define.ts 4 | 5 | (function loadLzString() { 6 | const el = document.createElement("script"); 7 | el.type = "text/javascript"; 8 | el.src = "//cdn.jsdelivr.net/npm/lz-string"; 9 | const s = document.getElementsByTagName("script")[0]; 10 | s.parentNode.insertBefore(el, s); 11 | })(); 12 | 13 | function compress(input) { 14 | return LZString.compressToBase64(input) 15 | .replace(/\+/g, `-`) // Convert '+' to '-' 16 | .replace(/\//g, `_`) // Convert '/' to '_' 17 | .replace(/=+$/, ``); // Remove ending '=' 18 | } 19 | 20 | function getParameters(parameters) { 21 | return compress(JSON.stringify(parameters)); 22 | } 23 | 24 | // ----------------------------------------------------------------------------- 25 | // Styles 26 | 27 | (function addStyles() { 28 | const style = document.createElement("style"); 29 | document.head.append(style); 30 | style.textContent = ` 31 | .docsify-codesandbox-button { 32 | cursor: pointer; 33 | transition: all 0.25s ease; 34 | position: absolute; 35 | z-index: 1; 36 | top: 0; 37 | right: 0; 38 | overflow: visible; 39 | padding: 0.65em 0.8em; 40 | border: 0; 41 | border-radius: 0; 42 | outline: 0; 43 | font-size: 1em; 44 | background: #808080; 45 | background: var(--theme-color, #808080); 46 | color: #fff; 47 | opacity: 0; 48 | } 49 | 50 | .docsify-codesandbox-button:focus, 51 | pre:hover .docsify-codesandbox-button { 52 | opacity: 1; 53 | } 54 | `; 55 | })(); 56 | 57 | // ----------------------------------------------------------------------------- 58 | // Plugin 59 | 60 | function docsifyCodesandboxPlugin(hook) { 61 | const getFiles = (content) => 62 | getParameters({ 63 | files: { 64 | "package.json": { 65 | content: { 66 | main: "index.html", 67 | scripts: { 68 | start: "parcel index.html --open", 69 | build: "parcel build index.html", 70 | }, 71 | dependencies: { 72 | "fp-ts": "^2.9", 73 | }, 74 | devDependencies: { 75 | "parcel-bundler": "^1.6.1", 76 | }, 77 | }, 78 | }, 79 | "tsconfig.json": { 80 | content: { 81 | compilerOptions: { 82 | module: "commonjs", 83 | moduleResolution: "node", 84 | target: "es2015", 85 | lib: ["es2015", "dom"], 86 | 87 | strict: true, 88 | noUnusedParameters: true, 89 | noUnusedLocals: true, 90 | noUncheckedIndexedAccess: true, 91 | noImplicitReturns: true, 92 | noFallthroughCasesInSwitch: true, 93 | }, 94 | }, 95 | }, 96 | "index.html": { 97 | content: "", 98 | }, 99 | "src/index.ts": { content }, 100 | }, 101 | }); 102 | 103 | hook.doneEach(() => { 104 | Array.from(document.querySelectorAll("pre[data-lang]")).forEach((el) => { 105 | const content = el.querySelector("code").textContent; 106 | el.insertAdjacentHTML( 107 | "beforeend", 108 | [ 109 | "
", 110 | "", 111 | ``, 112 | "", 113 | "
", 114 | ].join("") 115 | ); 116 | }); 117 | }); 118 | } 119 | 120 | window.$docsify = window.$docsify || {}; 121 | window.$docsify.plugins = [docsifyCodesandboxPlugin].concat(window.$docsify.plugins || []); 122 | -------------------------------------------------------------------------------- /docs/interoperability.md: -------------------------------------------------------------------------------- 1 | # How to work with non-functional code using fp-ts 2 | 3 | Sometimes you are forced to interoperate with code not written in a functional style, let's see how to deal with it. 4 | 5 | ## Sentinels 6 | 7 | - **Use case** – an API that may fail and returns a special value of the codomain. 8 | - **Example** – `Array.prototype.findIndex` 9 | - **Solution** – [Option](https://gcanti.github.io/fp-ts/modules/Option.ts) 10 | 11 | 12 | 13 | ```ts 14 | import { option } from "fp-ts"; 15 | 16 | function findIndex(as: Array, predicate: (a: A) => boolean): option.Option { 17 | const index = as.findIndex(predicate); 18 | return index === -1 ? option.none : option.some(index); 19 | } 20 | ``` 21 | 22 | ## undefined and null 23 | 24 | - **Use case** – an API that may fail and returns undefined (or null). 25 | - **Example** – `Array.prototype.find` 26 | - **Solution** – [Option](https://gcanti.github.io/fp-ts/modules/Option.ts), [fromNullable](https://gcanti.github.io/fp-ts/modules/Option.ts#fromnullable-function) 27 | 28 | 29 | 30 | ```ts 31 | import { option } from "fp-ts"; 32 | 33 | function find(as: Array, predicate: (a: A) => boolean): option.Option { 34 | return option.fromNullable(as.find(predicate)); 35 | } 36 | ``` 37 | 38 | ## Exceptions 39 | 40 | - **Use case** – an API that may throw. 41 | - **Example** – `JSON.parse` 42 | - **Solution** – [Either](https://gcanti.github.io/fp-ts/modules/Either.ts), [tryCatch](https://gcanti.github.io/fp-ts/modules/Either.ts#trycatch-function) 43 | 44 | 45 | 46 | ```ts 47 | import { either } from "fp-ts"; 48 | 49 | function parse(s: string): either.Either { 50 | return either.tryCatch( 51 | () => JSON.parse(s), 52 | (reason) => new Error(String(reason)) 53 | ); 54 | } 55 | ``` 56 | 57 | ## Random values 58 | 59 | - **Use case** – an API that returns a non deterministic value. 60 | - **Example** – `Math.random` 61 | - **Solution** – [IO](https://gcanti.github.io/fp-ts/modules/IO.ts) 62 | 63 | 64 | 65 | ```ts 66 | import { io } from "fp-ts"; 67 | 68 | const random: io.IO = () => Math.random(); 69 | ``` 70 | 71 | ## Synchronous side effects 72 | 73 | - **Use case** – an API that reads and/or writes to a global state. 74 | - **Example** – `localStorage.getItem` 75 | - **Solution** – [IO](https://gcanti.github.io/fp-ts/modules/IO.ts) 76 | 77 | 78 | 79 | ```ts 80 | import { io, option } from "fp-ts"; 81 | 82 | function getItem(key: string): io.IO> { 83 | return () => option.fromNullable(localStorage.getItem(key)); 84 | } 85 | ``` 86 | 87 | - **Use case** – an API that reads and/or writes to a global state and may throw. 88 | - **Example** – `localStorage.setItem` 89 | - **Solution** – [IOEither](https://gcanti.github.io/fp-ts/modules/IOEither.ts), [tryCatch](https://gcanti.github.io/fp-ts/modules/IOEither.ts#trycatch-function) 90 | 91 | 92 | 93 | ```ts 94 | import { ioEither } from "fp-ts"; 95 | 96 | function setItem(key: string, value: string): ioEither.IOEither { 97 | return ioEither.tryCatch( 98 | () => localStorage.setItem(key, value), 99 | (reason) => new Error(String(reason)) 100 | ); 101 | } 102 | ``` 103 | 104 | ## Asynchronous side effects 105 | 106 | - **Use case** – an API that performs an asynchronous computation. 107 | - **Example** – waiting 108 | - **Solution** – [Task](https://gcanti.github.io/fp-ts/modules/Task.ts) 109 | 110 | 111 | 112 | ```ts 113 | import { task } from "fp-ts"; 114 | 115 | const wait: task.Task = () => 116 | new Promise((resolve) => { 117 | setTimeout(resolve, 1000); 118 | }); 119 | ``` 120 | 121 | - **Use case** – an API that performs an asynchronous computation and may reject. 122 | - **Example** – fetch 123 | - **Solution** – [TaskEither](https://gcanti.github.io/fp-ts/modules/TaskEither.ts), [tryCatch](https://gcanti.github.io/fp-ts/modules/TaskEither.ts#trycatch-function) 124 | 125 | 126 | 127 | ```ts 128 | import { taskEither } from "fp-ts"; 129 | 130 | function get(url: string): taskEither.TaskEither { 131 | return taskEither.tryCatch( 132 | () => fetch(url).then((res) => res.text()), 133 | (reason) => new Error(String(reason)) 134 | ); 135 | } 136 | ``` 137 | 138 | --- 139 | 140 | _This content was originally published as a [blog post](https://dev.to/gcanti/interoperability-with-non-functional-code-using-fp-ts-432e) on February 12, 2019._ 141 | -------------------------------------------------------------------------------- /docs/numbers.md: -------------------------------------------------------------------------------- 1 | # Working with numbers 2 | 3 | _fp-ts_ is not a math library, but there are some good facilities we can use to work with numbers. Because the examples below use abstract concepts, e.g. [Monoid](https://gcanti.github.io/fp-ts/modules/Monoid.ts)s, many of the examples below would work with other types of data, not just numbers. 4 | 5 | ## Min/max 6 | 7 | ```ts 8 | import { Bounded } from "fp-ts/number"; 9 | import { concatAll, min, max } from "fp-ts/Monoid"; 10 | 11 | const minVal = concatAll(min(Bounded)); 12 | const maxVal = concatAll(max(Bounded)); 13 | 14 | minVal([5, 2, 3]); // 2 15 | maxVal([5, 2, 3]); // 5 16 | ``` 17 | 18 | ## Sums and products 19 | 20 | ```ts 21 | import { MonoidSum, MonoidProduct } from "fp-ts/number"; 22 | import { concatAll } from "fp-ts/Monoid"; 23 | 24 | const sum = concatAll(MonoidSum); 25 | const product = concatAll(MonoidProduct); 26 | 27 | sum([1, 2, 3, 4]); // 10 28 | product([1, 2, 3, 4]); // 24 29 | ``` 30 | 31 | ## Working with nested structures 32 | 33 | ### Object 34 | 35 | ```ts 36 | import { MonoidSum } from "fp-ts/number"; 37 | import { concatAll, struct } from "fp-ts/Monoid"; 38 | 39 | type Point = { 40 | x: number; 41 | y: number; 42 | }; 43 | 44 | const monoidPoint = struct({ 45 | x: MonoidSum, 46 | y: MonoidSum, 47 | }); 48 | 49 | const monoidPoints = concatAll(monoidPoint); 50 | 51 | monoidPoint.concat({ x: 0, y: 3 }, { x: 2, y: 4 }); // { x: 2, y: 7 } 52 | monoidPoints([ 53 | { x: 2, y: 2 }, 54 | { x: 2, y: 2 }, 55 | { x: 2, y: 2 }, 56 | ]); // { x: 6, y: 6 } 57 | ``` 58 | 59 | To check whether the resulting `Point` is positive, create a predicate: 60 | 61 | ```ts 62 | import { getMonoid } from "fp-ts/function"; 63 | import { MonoidAll } from "fp-ts/boolean"; 64 | 65 | type Point = { 66 | x: number; 67 | y: number; 68 | }; 69 | 70 | const monoidPredicate = getMonoid(MonoidAll)(); 71 | 72 | const isPositiveX = (p: Point) => p.x >= 0; 73 | const isPositiveY = (p: Point) => p.y >= 0; 74 | 75 | const isPositiveXY = monoidPredicate.concat(isPositiveX, isPositiveY); 76 | 77 | isPositiveXY({ x: 1, y: 1 }); // true 78 | isPositiveXY({ x: 1, y: -1 }); // false 79 | isPositiveXY({ x: -1, y: 1 }); // false 80 | isPositiveXY({ x: -1, y: -1 }); // false 81 | ``` 82 | 83 | ### Tuple 84 | 85 | ```ts 86 | import { MonoidSum } from "fp-ts/number"; 87 | import { concatAll, tuple } from "fp-ts/Monoid"; 88 | 89 | type Point = [number, number]; 90 | 91 | const monoidPoint = tuple(MonoidSum, MonoidSum); 92 | 93 | const monoidPoints = concatAll(monoidPoint); 94 | 95 | monoidPoint.concat([0, 3], [2, 4]); // [2, 7] 96 | monoidPoints([ 97 | [2, 2], 98 | [2, 2], 99 | [2, 2], 100 | ]); // [6, 6] 101 | ``` 102 | 103 | Same for `Tuples` 104 | 105 | ```ts 106 | import { getMonoid } from "fp-ts/function"; 107 | import { MonoidAll } from "fp-ts/boolean"; 108 | 109 | type Point = [number, number]; 110 | 111 | const monoidPredicate = getMonoid(MonoidAll)(); 112 | 113 | const isPositiveX = (p: Point) => p[0] >= 0; 114 | const isPositiveY = (p: Point) => p[1] >= 0; 115 | 116 | const isPositiveXY = monoidPredicate.concat(isPositiveX, isPositiveY); 117 | 118 | isPositiveXY([1, 1]); // true 119 | isPositiveXY([1, -1]); // false 120 | isPositiveXY([-1, 1]); // false 121 | isPositiveXY([-1, -1]); // false 122 | ``` 123 | 124 | ## Working with optional values 125 | 126 | ```ts 127 | import { some, none, Applicative } from "fp-ts/Option"; 128 | import { concatAll } from "fp-ts/Monoid"; 129 | import { MonoidSum, MonoidProduct } from "fp-ts/number"; 130 | import { getApplicativeMonoid } from "fp-ts/Applicative"; 131 | 132 | const sum = concatAll(getApplicativeMonoid(Applicative)(MonoidSum)); 133 | const product = concatAll(getApplicativeMonoid(Applicative)(MonoidProduct)); 134 | 135 | sum([some(2), none, some(4)]); // none 136 | sum([some(2), some(3), some(4)]); // some(9) 137 | 138 | product([some(2), none, some(4)]); // none 139 | product([some(2), some(3), some(4)]); // some(24) 140 | ``` 141 | 142 | This also works for [Either](https://gcanti.github.io/fp-ts/modules/Either.ts)s, but note that folding on `Left` values does not work the same way as folding on `Right` values. 143 | 144 | ```ts 145 | import { left, right, Applicative } from "fp-ts/Either"; 146 | import { concatAll } from "fp-ts/Monoid"; 147 | import { MonoidSum, MonoidProduct } from "fp-ts/number"; 148 | import { getApplicativeMonoid } from "fp-ts/Applicative"; 149 | 150 | const sum = concatAll(getApplicativeMonoid(Applicative)(MonoidSum)); 151 | const product = concatAll(getApplicativeMonoid(Applicative)(MonoidProduct)); 152 | 153 | sum([right(2), left(3), right(4)]); // left(3) 154 | sum([right(2), right(3), right(4)]); // right(9) 155 | 156 | product([right(2), left(3), left(4)]); // left(3) <- it's the first left value 157 | product([right(2), right(3), right(4)]); // right(24) 158 | ``` 159 | -------------------------------------------------------------------------------- /docs/lib/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Coldark Theme for Prism.js 3 | * Theme variation: Cold 4 | * Tested with HTML, CSS, JS, JSON, PHP, YAML, Bash script 5 | * @author Armand Philippot 6 | * @homepage https://github.com/ArmandPhilippot/coldark-prism 7 | * @license MIT 8 | */ 9 | 10 | .markdown-section code { 11 | font-size: 0.8em; /* Override default theme */ 12 | } 13 | 14 | .token.comment, 15 | .token.prolog, 16 | .token.doctype, 17 | .token.cdata { 18 | color: #3c526d; 19 | } 20 | 21 | .token.punctuation { 22 | color: #111b27; 23 | } 24 | 25 | .token.delimiter.important, 26 | .token.selector .parent, 27 | .token.tag, 28 | .token.tag .token.punctuation { 29 | color: #006d6d; 30 | } 31 | 32 | .token.attr-name, 33 | .token.boolean, 34 | .token.boolean.important, 35 | .token.number, 36 | .token.constant, 37 | .token.selector .token.attribute { 38 | color: #755f00; 39 | } 40 | 41 | .token.class-name, 42 | .token.key, 43 | .token.parameter, 44 | .token.property, 45 | .token.property-access, 46 | .token.variable { 47 | color: #005a8e; 48 | } 49 | 50 | .token.attr-value, 51 | .token.inserted, 52 | .token.color, 53 | .token.selector .token.value, 54 | .token.string, 55 | .token.string .token.url-link { 56 | color: #116b00; 57 | } 58 | 59 | .token.builtin, 60 | .token.keyword-array, 61 | .token.package, 62 | .token.regex { 63 | color: #af00af; 64 | } 65 | 66 | .token.function, 67 | .token.selector .token.class, 68 | .token.selector .token.id { 69 | color: #7c00aa; 70 | } 71 | 72 | .token.atrule .token.rule, 73 | .token.combinator, 74 | .token.keyword, 75 | .token.operator, 76 | .token.pseudo-class, 77 | .token.pseudo-element, 78 | .token.selector, 79 | .token.unit { 80 | color: #a04900; 81 | } 82 | 83 | .token.deleted, 84 | .token.important { 85 | color: #c22f2e; 86 | } 87 | 88 | .token.keyword-this, 89 | .token.this { 90 | color: #005a8e; 91 | } 92 | 93 | .token.important, 94 | .token.keyword-this, 95 | .token.this, 96 | .token.bold { 97 | font-weight: bold; 98 | } 99 | 100 | .token.delimiter.important { 101 | font-weight: inherit; 102 | } 103 | 104 | .token.italic { 105 | font-style: italic; 106 | } 107 | 108 | .token.entity { 109 | cursor: help; 110 | } 111 | 112 | /* overrides color-values for the Show Invisibles plugin 113 | * https://prismjs.com/plugins/show-invisibles/ 114 | */ 115 | .token.tab:not(:empty):before, 116 | .token.cr:before, 117 | .token.lf:before, 118 | .token.space:before { 119 | color: #3c526d; 120 | } 121 | 122 | /* overrides color-values for the Toolbar plugin 123 | * https://prismjs.com/plugins/toolbar/ 124 | */ 125 | div.code-toolbar > .toolbar a, 126 | div.code-toolbar > .toolbar button { 127 | color: #e3eaf2; 128 | background: #005a8e; 129 | } 130 | 131 | div.code-toolbar > .toolbar a:hover, 132 | div.code-toolbar > .toolbar a:focus, 133 | div.code-toolbar > .toolbar button:hover, 134 | div.code-toolbar > .toolbar button:focus { 135 | color: #e3eaf2; 136 | background: #005a8eda; 137 | text-decoration: none; 138 | } 139 | 140 | div.code-toolbar > .toolbar span, 141 | div.code-toolbar > .toolbar span:hover, 142 | div.code-toolbar > .toolbar span:focus { 143 | color: #e3eaf2; 144 | background: #3c526d; 145 | } 146 | 147 | /* overrides color-values for the Line Highlight plugin 148 | * http://prismjs.com/plugins/line-highlight/ 149 | */ 150 | .line-highlight { 151 | background: #8da1b92f; 152 | background: linear-gradient(to right, #8da1b92f 70%, #8da1b925); 153 | } 154 | 155 | .line-highlight:before, 156 | .line-highlight[data-end]:after { 157 | background-color: #3c526d; 158 | color: #e3eaf2; 159 | box-shadow: 0 1px #8da1b9; 160 | } 161 | 162 | pre[id].linkable-line-numbers span.line-numbers-rows > span:hover:before { 163 | background-color: #3c526d1f; 164 | } 165 | 166 | /* overrides color-values for the Line Numbers plugin 167 | * http://prismjs.com/plugins/line-numbers/ 168 | */ 169 | .line-numbers .line-numbers-rows { 170 | border-right: 1px solid #8da1b97a; 171 | background: #d0dae77a; 172 | } 173 | 174 | .line-numbers-rows > span:before { 175 | color: #3c526dda; 176 | } 177 | 178 | /* overrides color-values for the Match Braces plugin 179 | * https://prismjs.com/plugins/match-braces/ 180 | */ 181 | .rainbow-braces .token.punctuation.brace-level-1, 182 | .rainbow-braces .token.punctuation.brace-level-5, 183 | .rainbow-braces .token.punctuation.brace-level-9 { 184 | color: #755f00; 185 | } 186 | 187 | .rainbow-braces .token.punctuation.brace-level-2, 188 | .rainbow-braces .token.punctuation.brace-level-6, 189 | .rainbow-braces .token.punctuation.brace-level-10 { 190 | color: #af00af; 191 | } 192 | 193 | .rainbow-braces .token.punctuation.brace-level-3, 194 | .rainbow-braces .token.punctuation.brace-level-7, 195 | .rainbow-braces .token.punctuation.brace-level-11 { 196 | color: #005a8e; 197 | } 198 | 199 | .rainbow-braces .token.punctuation.brace-level-4, 200 | .rainbow-braces .token.punctuation.brace-level-8, 201 | .rainbow-braces .token.punctuation.brace-level-12 { 202 | color: #7c00aa; 203 | } 204 | 205 | /* overrides color-values for the Diff Highlight plugin 206 | * https://prismjs.com/plugins/diff-highlight/ 207 | */ 208 | pre.diff-highlight > code .token.deleted:not(.prefix), 209 | pre > code.diff-highlight .token.deleted:not(.prefix) { 210 | background-color: #c22f2e1f; 211 | } 212 | 213 | pre.diff-highlight > code .token.inserted:not(.prefix), 214 | pre > code.diff-highlight .token.inserted:not(.prefix) { 215 | background-color: #116b001f; 216 | } 217 | 218 | /* overrides color-values for the Command Line plugin 219 | * https://prismjs.com/plugins/command-line/ 220 | */ 221 | .command-line-prompt { 222 | border-right: 1px solid #8da1b97a; 223 | } 224 | 225 | .command-line-prompt > span:before { 226 | color: #3c526dda; 227 | } 228 | -------------------------------------------------------------------------------- /docs/async.md: -------------------------------------------------------------------------------- 1 | # Async tasks 2 | 3 | ## Tasks that always succeed 4 | 5 | If you're working with asynchronous tasks that are guaranteed to succeed, use [Task](https://gcanti.github.io/fp-ts/modules/Task.ts). 6 | 7 | ```ts 8 | import { task } from "fp-ts"; 9 | 10 | const deepThought: task.Task = () => Promise.resolve(42); 11 | 12 | deepThought().then((n) => { 13 | console.log(`The answer is ${n}.`); 14 | }); 15 | ``` 16 | 17 | ## Tasks that may fail 18 | 19 | If you're working with asynchronous tasks that may fail, use [TaskEither](https://gcanti.github.io/fp-ts/modules/TaskEither.ts). If the JSON in this example is malformed (try it!), an "I'm sorry" message is displayed. 20 | 21 | ```ts 22 | import { either, taskEither } from "fp-ts"; 23 | import { pipe } from "fp-ts/function"; 24 | 25 | const fetchGreeting = taskEither.tryCatch( 26 | () => new Promise((resolve) => resolve(JSON.parse('{ "name": "Carol" }'))), 27 | (reason) => new Error(String(reason)) 28 | ); 29 | 30 | fetchGreeting() 31 | .then((e) => 32 | pipe( 33 | e, 34 | either.fold( 35 | (err) => `I'm sorry, I don't know who you are. (${err.message})`, 36 | (x) => `Hello, ${x.name}!` 37 | ) 38 | ) 39 | ) 40 | .then(console.log); 41 | ``` 42 | 43 | ## Work with a list of tasks in parallel 44 | 45 | JavaScript provides `Promises.all` to wait for a list of Promises. 46 | 47 | ```ts 48 | Promise.all([Promise.resolve(1), Promise.resolve(2)]).then(console.log); // [1, 2] 49 | ``` 50 | 51 | With `Task`s you can achieve the same using `sequence`. Both the `Promise.all` and the `sequence` approach run in parallel and wait until all results have arrived before they proceed. 52 | 53 | ```ts 54 | import { task } from "fp-ts"; 55 | 56 | const tasks = [task.of(1), task.of(2)]; 57 | task.sequenceArray(tasks)().then(console.log); // [ 1, 2 ] 58 | ``` 59 | 60 | ## Run a list of tasks in sequence 61 | 62 | If you need to run a list of `Task`s in sequence, i.e. you have to wait for one `Task` to finish before you run the second `Task`, you can use the `taskSeq` instance. 63 | 64 | ```ts 65 | import { task } from "fp-ts"; 66 | import { pipe } from "fp-ts/function"; 67 | 68 | const log = (x: A) => { 69 | console.log(x); 70 | return x; 71 | }; 72 | 73 | const tasks = [ 74 | pipe(task.delay(200)(task.of("first")), task.map(log)), 75 | pipe(task.delay(100)(task.of("second")), task.map(log)), 76 | ]; 77 | 78 | // Parallel: logs 'second' then 'first' 79 | task.sequenceArray(tasks)(); 80 | 81 | // Sequential: logs 'first' then 'second' 82 | task.sequenceSeqArray(tasks)(); 83 | ``` 84 | 85 | ## Work with tasks with different type 86 | 87 | !> What if the types are different? We can't use `sequence` anymore. 88 | 89 | 90 | ```ts 91 | import { task } from "fp-ts"; 92 | 93 | const tasks = [task.of(1), task.of("hello")]; 94 | task.sequenceArray(tasks); 95 | // ~~~~~ Argument of type '(Task | Task)[]' is not assignable to parameter of type 'Task[]'. 96 | // Type 'Task | Task' is not assignable to type 'Task'. 97 | // Type 'Task' is not assignable to type 'Task'. 98 | // Type 'string' is not assignable to type 'number'. 99 | ``` 100 | 101 | 102 | We can use `sequenceT` or `sequenceS` instead. 103 | 104 | ```ts 105 | import { apply, task } from "fp-ts"; 106 | 107 | apply.sequenceT(task.ApplyPar)(task.of(1), task.of("hello")); // type is task.Task<[number, string]> 108 | 109 | apply.sequenceS(task.ApplyPar)({ a: task.of(1), b: task.of("hello") }); // type is task.Task<{ a: number; b: string; }> 110 | ``` 111 | 112 | ## Work with a list of dependent tasks 113 | 114 | If you need the result of on task before you can continue with the next, you can `chain` the tasks like so: 115 | 116 | ```ts 117 | import { task } from "fp-ts"; 118 | import { pipe } from "fp-ts/function"; 119 | 120 | pipe( 121 | task.of(2), 122 | task.chain((result) => task.of(result * 3)), 123 | task.chain((result) => task.of(result + 4)) 124 | )().then(console.log); // 10 125 | ``` 126 | 127 | ## Traverse: map and sequence 128 | 129 | If you have a list of items that you need to `map` over before running them in `sequence`, you can use `traverse`, which is a shortcut for doing both operations in one step. 130 | 131 | ```ts 132 | import { task } from "fp-ts"; 133 | import { pipe } from "fp-ts/function"; 134 | 135 | const checkPathExists = (path: string) => () => 136 | new Promise((resolve) => { 137 | resolve({ path, exists: !path.startsWith('/no/') }) 138 | }); 139 | 140 | const program = pipe( 141 | ["/bin", "/no/real/path"], 142 | task.traverseArray(checkPathExists) 143 | ) 144 | 145 | program().then(console.log); // [ { path: '/bin', exists: true }, { path: '/no/real/path', exists: false } ] 146 | ``` 147 | 148 | ## Comparison with `Promise` methods 149 | 150 | Following is a table comparing `Task`/`TaskEither` with `Promise`. It assumes the following imports: 151 | 152 | 153 | ```ts 154 | import { array as A, monoid as M, task as T, taskEither as TE } from "fp-ts"; 155 | ``` 156 | 157 | |   | Promise | Task | TaskEither | 158 | | :------------------------------------------------------------------------------------------------------------ | :--------------------------------- | :------------------------------------ | :------------------------------------------------------ | 159 | | **Resolve to success** | `Promise.resolve(value)` | `T.task.of(value)` | `TE.taskEither.of(value)` or `TE.right(value)` | 160 | | **Resolve to failure** | `Promise.reject(value)` | N/A | `TE.left(value)` | 161 | | **Transform the result of a task with the function `f`** | `promise.then(f)` | `T.task.map(task, f)` | `T.taskEither.map(taskEither, f)` | 162 | | **Perform a task depending on the result of a previous one** | `promise.then(r => getPromise(r))` | `T.task.chain(task, r => getTask(r))` | `T.taskEither.chain(taskEither, r => getTaskEither(r))` | 163 | | **Execute an array of tasks in parallel** | `Promise.all(promises)` | `A.sequence(T.task)(tasks)` | `A.sequence(TE.taskEither)(taskEithers)` | 164 | | **Execute an array of tasks in parallel, collecting all failures and successes** | `Promise.allSettled(promises)` | N/A | `A.sequence(T.task)(taskEithers)`, which would maintain the order of the failures and successes.

Or, `A.wilt(T.ApplicativePar)(id)(taskEithers)`, which will not maintain result order, but will collect all failures and successes | 165 | | **Execute an array of tasks and succeed/fail with a single value as soon as one of the tasks succeeds/fails** | `Promise.race(promises)` | `M.fold(T.getRaceMonoid())(tasks)` | `M.fold(T.getRaceMonoid())(taskEithers)` | 166 | -------------------------------------------------------------------------------- /docs/ordering.md: -------------------------------------------------------------------------------- 1 | # How to determine the order of data 2 | 3 | If you need to decide on the order of two values, you can make use of the `compare` method provided by `Ord` instances. Ordering builds on [equality](equality.md). 4 | 5 | Note that `compare` returns an [Ordering](https://gcanti.github.io/fp-ts/modules/Ordering.ts), which is one of these values `-1 | 0 | 1`. We say that 6 | 7 | - `x < y` if and only if `compare(x, y)` is equal to `-1` 8 | - `x` is equal to `y` if and only if `compare(x, y)` is equal to `0` 9 | - `x > y` if and only if `compare(x, y)` is equal to `1` 10 | 11 | We show the most common usages here, but if you need more ways to order your data, be sure to read the [Ord](https://gcanti.github.io/fp-ts/modules/Ord.ts) documentation page. 12 | 13 | ## Primitive comparisons 14 | 15 | ```ts 16 | import * as number from "fp-ts/number"; 17 | import * as string from "fp-ts/string"; 18 | import * as boolean from "fp-ts/boolean"; 19 | import * as date from "fp-ts/Date"; 20 | 21 | number.Ord.compare(4, 5); // -1 22 | number.Ord.compare(5, 5); // 0 23 | number.Ord.compare(6, 5); // 1 24 | 25 | boolean.Ord.compare(true, false); // 1 26 | date.Ord.compare(new Date("1984-01-27"), new Date("1978-09-23")); // 1 27 | string.Ord.compare("Cyndi", "Debbie"); // -1 28 | ``` 29 | 30 | Note that all `Ord` instances also define the `equals` method, because it is a prerequisite to be able to compare data. 31 | 32 | ```ts 33 | import * as Ord from "fp-ts/Ord"; 34 | import * as boolean from "fp-ts/boolean"; 35 | 36 | Ord.equals(boolean.Ord)(false)(false); // true 37 | ``` 38 | 39 | ## Custom comparisons 40 | 41 | You can create custom comparisons using `fromCompare` like so: 42 | 43 | ```ts 44 | import * as Ord from "fp-ts/Ord"; 45 | 46 | const strlenOrd = Ord.fromCompare((a: string, b: string) => 47 | a.length < b.length ? -1 : a.length > b.length ? 1 : 0 48 | ); 49 | strlenOrd.compare("Hi", "there"); // -1 50 | strlenOrd.compare("Goodbye", "friend"); // 1 51 | ``` 52 | 53 | But most of the time, you can achieve the same result in a simpler way with `contramap`: 54 | 55 | ```ts 56 | import * as Ord from "fp-ts/Ord"; 57 | import * as number from "fp-ts/number"; 58 | 59 | const strlenOrd = Ord.contramap((s: string) => s.length)(number.Ord); 60 | strlenOrd.compare("Hi", "there"); // -1 61 | strlenOrd.compare("Goodbye", "friend"); // 1 62 | ``` 63 | 64 | ## Min, max, clamp 65 | 66 | Take the smaller (`min`) or larger (`max`) element of two, or take the one closest to the given boundaries (`clamp`). 67 | 68 | ```ts 69 | import * as Ord from "fp-ts/Ord"; 70 | import * as number from "fp-ts/number"; 71 | import * as string from "fp-ts/string"; 72 | 73 | Ord.min(number.Ord)(5, 2); // 2 74 | Ord.max(number.Ord)(5, 2); // 5 75 | 76 | Ord.clamp(number.Ord)(3, 7)(2); // 3 77 | Ord.clamp(string.Ord)("Bar", "Boat")("Ball"); // Bar 78 | ``` 79 | 80 | ## Less than, greater than, or in between? 81 | 82 | ```ts 83 | import * as Ord from "fp-ts/Ord"; 84 | import * as number from "fp-ts/number"; 85 | 86 | Ord.lt(number.Ord)(4, 7); // true 87 | Ord.geq(number.Ord)(6, 6); // true 88 | 89 | Ord.between(number.Ord)(6, 9)(7); // true 90 | Ord.between(number.Ord)(6, 9)(6); // true 91 | Ord.between(number.Ord)(6, 9)(9); // true 92 | Ord.between(number.Ord)(6, 9)(12); // false 93 | ``` 94 | 95 | ## Sort an array 96 | 97 | ```ts 98 | import * as A from "fp-ts/Array"; 99 | import * as number from "fp-ts/number"; 100 | 101 | const sortByNumber = A.sort(number.Ord); 102 | sortByNumber([3, 1, 2]); // [1, 2, 3] 103 | ``` 104 | 105 | Sort array of arrays based on `sum`: 106 | 107 | ```ts 108 | import * as A from "fp-ts/Array"; 109 | import * as Ord from "fp-ts/Ord"; 110 | import * as number from "fp-ts/number"; 111 | 112 | const arrayOfArrays = [ 113 | [3, 2, 1], // 6 114 | [9, 7, 6, 8], // 30 115 | [1, 4], // 5 116 | ]; 117 | 118 | const arraySumOrd = Ord.contramap(A.reduce(0, number.SemigroupSum.concat))(number.Ord); 119 | 120 | A.sort(arraySumOrd)(arrayOfArrays); // [ [ 1, 4 ], [ 3, 2, 1 ], [ 9, 7, 6, 8 ] ] 121 | ``` 122 | 123 | Sort an array of objects: 124 | 125 | ```ts 126 | import * as A from "fp-ts/Array"; 127 | import * as Ord from "fp-ts/Ord"; 128 | import * as number from "fp-ts/number"; 129 | 130 | type Planet = { 131 | name: string; 132 | diameter: number; // km 133 | distance: number; // AU from Sun 134 | }; 135 | 136 | const planets: Array = [ 137 | { name: "Earth", diameter: 12756, distance: 1 }, 138 | { name: "Jupiter", diameter: 142800, distance: 5.203 }, 139 | { name: "Mars", diameter: 6779, distance: 1.524 }, 140 | { name: "Mercury", diameter: 4879.4, distance: 0.39 }, 141 | { name: "Neptune", diameter: 49528, distance: 30.06 }, 142 | { name: "Saturn", diameter: 120660, distance: 9.539 }, 143 | { name: "Uranus", diameter: 51118, distance: 19.18 }, 144 | { name: "Venus", diameter: 12104, distance: 0.723 }, 145 | { name: "Nibiru", diameter: 142400, distance: 409 }, 146 | { name: "Nibira", diameter: 142400, distance: 409 }, 147 | ]; 148 | 149 | const diameterOrd = Ord.contramap((x: Planet) => x.diameter)(number.Ord); 150 | const distanceOrd = Ord.contramap((x: Planet) => x.distance)(number.Ord); 151 | 152 | console.log("distance", A.sort(distanceOrd)(planets)); // Mercury, Venus, Earth, Mars, ... 153 | console.log("diameter", A.sort(diameterOrd)(planets)); // Mercury, Mars, Venus, Earth, ... 154 | ``` 155 | 156 | Sort array with two `Ord` instances using `Semigroup`. To combine more than two `Ord` instances use `Monoid`. 157 | 158 | ```ts 159 | import { concatAll } from "fp-ts/Monoid"; 160 | import * as A from "fp-ts/Array"; 161 | import * as Ord from "fp-ts/Ord"; 162 | import * as number from "fp-ts/number"; 163 | import * as string from "fp-ts/string"; 164 | 165 | type Planet = { 166 | name: string; 167 | diameter: number; // km 168 | distance: number; // AU from Sun 169 | }; 170 | 171 | const planets: Array = [ 172 | { name: "Earth", diameter: 12756, distance: 1 }, 173 | { name: "Jupiter", diameter: 142800, distance: 5.203 }, 174 | { name: "Mars", diameter: 6779, distance: 1.524 }, 175 | { name: "Mercury", diameter: 4879.4, distance: 0.39 }, 176 | { name: "Neptune", diameter: 49528, distance: 30.06 }, 177 | { name: "Saturn", diameter: 120660, distance: 9.539 }, 178 | { name: "Uranus", diameter: 51118, distance: 19.18 }, 179 | { name: "Venus", diameter: 12104, distance: 0.723 }, 180 | { name: "Nibiru", diameter: 142400, distance: 409 }, 181 | { name: "Nibira", diameter: 142400, distance: 409 }, 182 | ]; 183 | 184 | const diameterOrd = Ord.contramap((x: Planet) => x.diameter)(number.Ord); 185 | const distanceOrd = Ord.contramap((x: Planet) => x.distance)(number.Ord); 186 | const nameOrd = Ord.contramap((x: Planet) => x.name)(string.Ord); 187 | 188 | const S = Ord.getSemigroup(); 189 | const M = Ord.getMonoid(); 190 | 191 | const diameterDistanceOrd = S.concat(diameterOrd, distanceOrd); // combine 2 Ord 192 | const diameterDistanceNameOrd = concatAll(M)([diameterOrd, distanceOrd, nameOrd]); // combine 3 Ord 193 | 194 | console.log("diameter-distance order", A.sort(diameterDistanceOrd)(planets)); // Mercury, Mars, Venus, Earth, ... , Nibiru, Nibira, Jupiter 195 | console.log("diameter-distance-name order", A.sort(diameterDistanceNameOrd)(planets)); // Mercury, Mars, Venus, Nibiru, ... , Nibira, Nibiru, Jupiret 196 | ``` 197 | 198 | ## More Ord instances 199 | 200 | Many data types provide `Ord` instances. Here's [Option](https://gcanti.github.io/fp-ts/modules/Option.ts): 201 | 202 | ```ts 203 | import { option } from "fp-ts"; 204 | import * as Ord from "fp-ts/Ord"; 205 | import * as number from "fp-ts/number"; 206 | 207 | const O = option.getOrd(number.Ord); 208 | O.compare(option.none, option.none); // 0 209 | O.compare(option.none, option.some(1)); // -1 210 | O.compare(option.some(1), option.none); // 1 211 | O.compare(option.some(1), option.some(2)); // -1 212 | O.compare(option.some(1), option.some(1)); // 0 213 | ``` 214 | 215 | It works similarly for [Tuple](https://gcanti.github.io/fp-ts/modules/Tuple.ts)s and other types where it is possible to determine order: 216 | 217 | ```ts 218 | import * as Ord from "fp-ts/Ord"; 219 | import * as number from "fp-ts/number"; 220 | import * as string from "fp-ts/string"; 221 | 222 | const tuple = Ord.tuple(string.Ord, number.Ord); 223 | tuple.compare(["A", 10], ["A", 12]); // -1 224 | tuple.compare(["A", 10], ["A", 4]); // 1 225 | tuple.compare(["A", 10], ["B", 4]); // -1 226 | ``` 227 | --------------------------------------------------------------------------------