├── index.ts ├── .gitignore ├── package.json ├── lang.ts ├── date.ts ├── yarn.lock ├── tsconfig.json ├── collection.ts ├── functions.ts ├── array.ts └── object.ts /index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-fpts", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@types/lodash": "^4.14.168", 8 | "fp-ts": "^2.9.5", 9 | "fp-ts-contrib": "^0.1.21", 10 | "fp-ts-rxjs": "^0.6.13", 11 | "fp-ts-std": "^0.8.0", 12 | "io-ts": "^2.2.15", 13 | "io-ts-types": "^0.5.15", 14 | "lodash": "^4.17.20", 15 | "monocle-ts": "^2.3.5", 16 | "rxjs": "^6.6.3", 17 | "ts-adt": "^2.0.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lang.ts: -------------------------------------------------------------------------------- 1 | import * as A from 'fp-ts/Array' 2 | import * as ROR from 'fp-ts/ReadonlyRecord' 3 | import * as _ from 'lodash' 4 | 5 | const log = console.log 6 | //castArray 7 | // this is a useless function for typescript because you already know the type 8 | log(_.castArray(1)) 9 | log(_.castArray([1,2,3])) 10 | 11 | // you can make something into an array like this if you like though 12 | log(A.of(1)) 13 | 14 | // clone 15 | var objects = [{ 'a': 1 }, { 'b': 2 }]; 16 | 17 | var shallow = _.clone(objects); 18 | console.log(shallow[0] === objects[0]); 19 | 20 | 21 | -------------------------------------------------------------------------------- /date.ts: -------------------------------------------------------------------------------- 1 | import * as D from 'fp-ts/Date' 2 | import * as IO from 'fp-ts/IO' 3 | import * as T from 'fp-ts/Task' 4 | import { time } from 'fp-ts-contrib/time' 5 | import * as _ from 'lodash' 6 | 7 | const log = console.log 8 | 9 | //Date 10 | log(_.now()) 11 | // => date in milliseconds 12 | log(D.now()) 13 | // => date in milliseconds 14 | 15 | // time something 16 | _.defer(function(stamp) { 17 | log(_.now() - stamp); 18 | }, _.now()) 19 | // => elapsed time 20 | 21 | // time something, and give back the value and how long it took 22 | time(T.task)(T.delay(100)(T.of(0)))().then(log) 23 | // => [0, 102] 24 | 25 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/lodash@^4.14.168": 6 | version "4.14.168" 7 | resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" 8 | integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== 9 | 10 | fp-ts-contrib@^0.1.21: 11 | version "0.1.21" 12 | resolved "https://registry.yarnpkg.com/fp-ts-contrib/-/fp-ts-contrib-0.1.21.tgz#9a2b6c108ad9487922890f46219d5639f3cacc66" 13 | integrity sha512-Wa1wXpeDDVyPiTZaxbas+y6pigKxiZlf/xzUBhFJ5bPNAsOrzNfFeQliu3bIH9ZjYWrt6HIMuNXAejPh6oSSkA== 14 | 15 | fp-ts-rxjs@^0.6.13: 16 | version "0.6.13" 17 | resolved "https://registry.yarnpkg.com/fp-ts-rxjs/-/fp-ts-rxjs-0.6.13.tgz#640848ce3eb752b4006e3d9407e377f447361ef9" 18 | integrity sha512-h5IH0BvkxEkEpZLJo3MMKVJvYAFgftaO39xLq67FOJh1Kwb3jPgiYiJGaSvKomP0ykHF3aJA0iFm7abXPBFIgQ== 19 | 20 | fp-ts-std@^0.8.0: 21 | version "0.8.0" 22 | resolved "https://registry.yarnpkg.com/fp-ts-std/-/fp-ts-std-0.8.0.tgz#d42816c8e92380cae6afb586d93bda30ec4ad2fd" 23 | integrity sha512-Zj6AwS0grCFGCqUYxmFTxfhT3Q3aCRoUNXlwDO7eY3pafj+1SHJBhh2PbsCQtQBMBA7UOfHQ/DqFd83vjIJY1w== 24 | 25 | fp-ts@^2.9.5: 26 | version "2.9.5" 27 | resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.9.5.tgz#6690cd8b76b84214a38fc77cbbbd04a38f86ea90" 28 | integrity sha512-MiHrA5teO6t8zKArE3DdMPT/Db6v2GUt5yfWnhBTrrsVfeCJUUnV6sgFvjGNBKDmEMqVwRFkEePL7wPwqrLKKA== 29 | 30 | io-ts-types@^0.5.15: 31 | version "0.5.15" 32 | resolved "https://registry.yarnpkg.com/io-ts-types/-/io-ts-types-0.5.15.tgz#d4a7cd5eeede8f7cc8bd33950f243a27e3da8313" 33 | integrity sha512-SOKog7vxnrGybn92jxrLC7E6LSRC3nHzS4dadD1BK9cAiWyqXHerpbLq9zR3ZtQCJyNsSalpDPS1I4Jk2yzafg== 34 | 35 | io-ts@^2.2.15: 36 | version "2.2.15" 37 | resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.15.tgz#0b0b19a6f8a64f4a524ad5810d56432789428128" 38 | integrity sha512-ww2ZPrErx5pjCCI/tWRwjlEIDEndnN9kBIxAylXj+WNIH4ZVgaUqFuabGouehkRuvrmvzO5OnZmLf+o50h4izQ== 39 | 40 | lodash@^4.17.20: 41 | version "4.17.20" 42 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" 43 | integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== 44 | 45 | monocle-ts@^2.3.5: 46 | version "2.3.5" 47 | resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-2.3.5.tgz#1ff90b08616254b887e4c2bc5b2a9764fed16723" 48 | integrity sha512-Xm9jdWvqFrlV0k965eY5AlCpWIIUBY2ExzGbEG+byMs+mZI4J7zvaUOLpQ8MTFgkpgyEnu4qUhuZT/Or3QeRiA== 49 | 50 | rxjs@^6.6.3: 51 | version "6.6.3" 52 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" 53 | integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== 54 | dependencies: 55 | tslib "^1.9.0" 56 | 57 | ts-adt@^2.0.2: 58 | version "2.0.2" 59 | resolved "https://registry.yarnpkg.com/ts-adt/-/ts-adt-2.0.2.tgz#3d5026a18bfcd1fcc2262504a5b905f45caf5c25" 60 | integrity sha512-e//wyQHNBOjPN2WwjGOXMeLC1C8hd9bVAmuFQWa2gbzMEel6up1OnhRdezB9pJqJNHrZFm06n+rK39rjJ/Zz/w== 61 | 62 | tslib@^1.9.0: 63 | version "1.14.1" 64 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 65 | integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 66 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": ["ES2015", "DOM"], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /collection.ts: -------------------------------------------------------------------------------- 1 | import * as A from 'fp-ts/Array' 2 | import * as Ap from 'fp-ts/Apply' 3 | import * as A1 from 'fp-ts-std/Array' 4 | import * as ROA from 'fp-ts/ReadonlyArray' 5 | import * as NEA from 'fp-ts/NonEmptyArray' 6 | import * as O from 'fp-ts/Option' 7 | import * as Eq from 'fp-ts/Eq' 8 | import * as Ord from 'fp-ts/Ord' 9 | import * as R from 'fp-ts/Record' 10 | import * as _ from 'lodash' 11 | import * as Map from 'fp-ts/Map' 12 | import * as T from 'fp-ts/Task' 13 | import * as M from 'fp-ts/Monoid' 14 | import * as IO from 'fp-ts/IO' 15 | import * as Rand from 'fp-ts/Random' 16 | import {getFirstSemigroup} from 'fp-ts/Semigroup' 17 | import {pipe, identity, flow} from 'fp-ts/function' 18 | import { lowerFirst } from 'lodash' 19 | import { task } from 'fp-ts/lib/Task' 20 | 21 | const log = console.log 22 | 23 | 24 | 25 | //count by 26 | log(_.countBy([6.1, 4.2, 6.3], Math.floor)) 27 | // "magma" is how we combine things that are the same, in this case we sum them 28 | // the keys are the floor of the values in the array, and we're adding one every time we see another 29 | log(R.fromFoldableMap(M.monoidSum, A.array)([6.1, 4.2, 6.3], (n) => [Math.floor(n).toString(), 1])) 30 | 31 | //every 32 | var u1 = [ 33 | { 'user': 'barney', 'age': 36, 'active': false }, 34 | { 'user': 'fred', 'age': 40, 'active': false } 35 | ]; 36 | 37 | log(_.every(u1, { 'user': 'barney', 'active': false })) 38 | log(pipe(u1, A.every(u => u.user === 'barney' && !u.active))) 39 | 40 | //filter 41 | var u2 = [ 42 | { 'user': 'barney', 'age': 36, 'active': true }, 43 | { 'user': 'fred', 'age': 40, 'active': false } 44 | ]; 45 | 46 | log(_.filter(u2, function(o) { return !o.active; })) 47 | log(pipe(u2, A.filter(({active}) => !active))) 48 | 49 | //find 50 | var u3 = [ 51 | { 'user': 'barney', 'age': 36, 'active': true }, 52 | { 'user': 'fred', 'age': 40, 'active': false }, 53 | { 'user': 'pebbles', 'age': 1, 'active': true } 54 | ]; 55 | 56 | log(_.find(u3, function(o) { return o.age < 40; })) 57 | log(pipe(u3, A.findFirst(({age}) => age < 40))) //returns an option 58 | 59 | //findLast 60 | log(_.findLast([1, 2, 3, 4], function(n) { 61 | return n % 2 == 1; 62 | })) 63 | log(pipe([1,2,3,4], A.findLast(n => n % 2 === 1))) 64 | 65 | //flatmap 66 | const duplicate = (n:A) => [n, n] 67 | 68 | log(_.flatMap([1, 2], duplicate)) 69 | log(A.chain(duplicate)([1,2])) 70 | 71 | //flatmapdeep 72 | const duplicate2 = (n:A) => [[n, n]] 73 | log(_.flatMapDeep([1, 2], duplicate2)) 74 | log(pipe([1,2], A.chain(duplicate2), A.flatten)) 75 | 76 | //flatmapdepth 77 | //doesn't really makes sense, you flatten as many times as you like 78 | const duplicate3 = (n:A) => [[[n, n]]] 79 | log(_.flatMapDepth([1, 2], duplicate3, 2)) 80 | log(pipe([1,2], A.chain(duplicate3), A.flatten)) 81 | 82 | //foreach 83 | // performs side effects, doesn't return a value 84 | _.forEach([1, 2], function(value) { 85 | console.log(value); 86 | }); 87 | 88 | // IO really just means a function that takes no input and returns a result. 89 | // what we have here is a function that takes an A and returns an IO 90 | const log2 = (a:A) => () => console.log(a) 91 | // IO means do something with side effects, like log to the console 92 | // theese two implementations do the same thing, but in slightly different ways 93 | 94 | // run an action for each element in an array and return the results 95 | log(pipe([1,2], IO.traverseArray(log2))()) 96 | // transform an array of IO into an IO of array 97 | log(pipe([1,2], A.map(log2), IO.sequenceArray)()) 98 | 99 | //forEachRight 100 | _.forEachRight([1, 2], function(value) { 101 | console.log(value); 102 | }); 103 | 104 | log(pipe([1,2], A.reverse, IO.traverseArray(log2))()) 105 | 106 | //groupby 107 | log(_.groupBy([6.1, 4.2, 6.3], Math.floor)) 108 | log(R.fromFoldableMap(A.getMonoid(), A.array)([6.1, 4.2, 6.3], n => [Math.floor(n).toString(), [n]])) 109 | 110 | //includes 111 | log(_.includes([1, 2, 3], 1)) 112 | log(A.some(x => x === 1)([1,2,3])) 113 | 114 | //invokemap 115 | log(_.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort')) 116 | log(pipe(([[5, 1, 7], [3, 2, 1]]), A.map(A.sort(Ord.ordNumber)))) 117 | 118 | //keyby 119 | var arr7 = [ 120 | { 'dir': 'left', 'code': 97 }, 121 | { 'dir': 'right', 'code': 100 } 122 | ]; 123 | 124 | log(_.keyBy(arr7, function(o) { 125 | return String.fromCharCode(o.code); 126 | })) 127 | 128 | log(R.fromFoldableMap(getFirstSemigroup<{dir:string, code:number}>(), A.array) 129 | (arr7, (o) => [String.fromCharCode(o.code), o])) 130 | 131 | 132 | //map 133 | log(_.map([4,8], x => x * x)) 134 | log(pipe([4,8], A.map(x => x * x))) 135 | 136 | //orderby 137 | var u4 = [ 138 | { 'user': 'fred', 'age': 48 }, 139 | { 'user': 'barney', 'age': 34 }, 140 | { 'user': 'fred', 'age': 40 }, 141 | { 'user': 'barney', 'age': 36 } 142 | ]; 143 | 144 | interface Person { 145 | user: string 146 | age: number 147 | } 148 | const byName = Ord.ord.contramap(Ord.ordString, (p: Person) => p.user) 149 | const byAge = Ord.ord.contramap(Ord.getDualOrd(Ord.ordNumber), (p: Person) => p.age) 150 | 151 | // Sort by `user` in ascending order and by `age` in descending order. 152 | log(_.orderBy(u4, ['user', 'age'], ['asc', 'desc'])) 153 | log(A.sortBy([byName, byAge])(u4)) 154 | 155 | var u7 = [ 156 | { 'user': 'barney', 'age': 36, 'active': false }, 157 | { 'user': 'fred', 'age': 40, 'active': true }, 158 | { 'user': 'pebbles', 'age': 1, 'active': false } 159 | ]; 160 | 161 | log(_.partition(u7, function(o) { return o.active; })) 162 | log(pipe(u7, A.partition(({active}) => active))) 163 | 164 | //reduce 165 | log(_.reduce([1, 2, 3], function(sum, n) { 166 | return sum + n; 167 | }, 0)) 168 | log(A.reduce(0, M.monoidSum.concat)([1,2,3])) 169 | 170 | //reduceright 171 | var arr8 = [[0, 1], [2, 3], [4, 5]]; 172 | 173 | log(_.reduceRight(arr8, function(flattened:number[], other) { 174 | return flattened.concat(other); 175 | }, [])) 176 | 177 | log(pipe(arr8, A.reduceRight([] as number[], (n, acc) => acc.concat(n)))) 178 | //note that the order of the params is revered compared to standard reduce 179 | log(pipe(arr8, A.reduce([] as number[], (acc, n) => acc.concat(n)))) 180 | 181 | //reject 182 | var u8 = [ 183 | { 'user': 'barney', 'age': 36, 'active': false }, 184 | { 'user': 'fred', 'age': 40, 'active': true } 185 | ]; 186 | 187 | log(_.reject(u8, function(o) { return !o.active; })) 188 | 189 | log(pipe(u8, A1.reject(({active}) => !active))) 190 | 191 | //sample 192 | log(_.sample([1, 2, 3, 4])) 193 | const arr9 = ['a', 'b', 'c', 'd'] 194 | log(pipe(Rand.randomInt(0, arr9.length-1), IO.map(x => arr9[x]))()) 195 | 196 | //shuffle 197 | log(_.shuffle([1, 2, 3, 4])) 198 | 199 | interface shuf { 200 | sort: number 201 | val: A 202 | } 203 | const bysort = (() => Ord.ord.contramap(Ord.ordNumber, (p: shuf) => p.sort))() 204 | 205 | // this really needs to take a seed in the future for better testing 206 | const fpshuffle = (arr: A[]) => 207 | pipe( 208 | A.replicate(arr.length, Rand.random), 209 | IO.sequenceArray, 210 | IO.map(flow( 211 | ROA.zip(arr), 212 | ROA.map(([sort, val]) => ({sort, val})), 213 | ROA.sort(bysort), 214 | ROA.map(x => x.val) 215 | )) 216 | ) 217 | 218 | log(fpshuffle(arr9)()) 219 | 220 | //samplesize - needs shuffle to work so its down here 221 | log(pipe(fpshuffle(arr9), IO.map(ROA.takeLeft(2)))()) 222 | 223 | //size 224 | log(_.size({a: 1, b: 2})) 225 | log(R.size({a: 1, b: 2})) 226 | 227 | //some 228 | var users = [ 229 | { 'user': 'barney', 'active': true }, 230 | { 'user': 'fred', 'active': false } 231 | ]; 232 | 233 | log(_.some(users, { 'user': 'barney', 'active': false })) 234 | const userEq: Eq.Eq<{user:string, active:boolean}> = { 235 | equals: (x,y) => x.user === y.user && x.active === y.active 236 | } 237 | log(pipe(users, A.some(x => userEq.equals(x, { 'user': 'barney', 'active': false })))) 238 | 239 | 240 | //sortby 241 | var u9 = [ 242 | { 'user': 'fred', 'age': 48 }, 243 | { 'user': 'barney', 'age': 36 }, 244 | { 'user': 'fred', 'age': 40 }, 245 | { 'user': 'barney', 'age': 34 } 246 | ]; 247 | 248 | log(_.sortBy(u9, [function(o) { return o.user; }])) 249 | 250 | log(A.sort(byName)(u9)) 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /functions.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | import { from, of, timer } from 'rxjs' 3 | import { take, debounceTime, finalize, skip, throttleTime} from 'rxjs/operators' 4 | import {pipe, flip, tuple, flow} from 'fp-ts/function' 5 | import * as Ob from 'fp-ts-rxjs/Observable' 6 | import * as O from 'fp-ts/Option' 7 | import * as S from 'fp-ts/State' 8 | import * as IO from 'fp-ts/IO' 9 | import * as A from 'fp-ts/Array' 10 | import * as A1 from 'fp-ts-std/Array' 11 | import * as T from 'fp-ts/Task' 12 | import * as C from 'fp-ts/Console' 13 | import {memoize, curry2, curry3} from 'fp-ts-std/Function' 14 | import * as F1 from 'fp-ts-std/Function' 15 | import { eqNumber } from 'fp-ts/lib/Eq' 16 | import { lowerFirst } from 'lodash' 17 | 18 | const log = console.log 19 | 20 | 21 | // after 22 | var saves = ['profile', 'settings', 's', 'd']; 23 | 24 | ['profile', 'settings', 's', 'd'] 25 | 26 | const doAfter = (prefix:string) => () => log(`${prefix} do after`) 27 | 28 | const asyncSave = ({type, complete} : {type:string, complete: () => void}) => done() 29 | // starts invoking on the third call 30 | var done = _.after(3, doAfter("lodash")); 31 | 32 | _.forEach(saves, function(type) { 33 | asyncSave({ 'type': type, 'complete': done }); 34 | }); 35 | 36 | // multiple solutions depending on what you'd like to achieve 37 | 38 | // #1 - track counts 39 | 40 | // In FP we don't mutate state, so that we can write tests more easily. 41 | // instead we keep a running count so that we can feed how many have been 42 | // executed into the call 43 | // since logging is an IO thing we also don't do it inline, but at the end 44 | const myio = IO.fromIO(doAfter("fp-ts")) //whatever IO you want to do 45 | const initialState = (count: number) => tuple([], count) 46 | const invokeAfter = (after: number) => (s: IO.IO[]) => (count: number) => 47 | count >= after ? tuple(A.cons(myio)(s), count+1) : tuple(s, count+1) 48 | 49 | // invokes on the third call 50 | const invokeAfter2 = invokeAfter(2) 51 | 52 | pipe( 53 | initialState, 54 | S.chain(invokeAfter2), 55 | S.chain(invokeAfter2), 56 | S.chain(invokeAfter2), 57 | S.chain(invokeAfter2), 58 | S.evaluate(0), 59 | IO.sequenceArray 60 | )() 61 | 62 | // #2 use finalize to do something at the end of your calls 63 | 64 | pipe( 65 | from(saves), 66 | finalize(() => log('save complete')) 67 | ).subscribe() 68 | 69 | // #3 use skip to make sure you invoke your function after x events 70 | 71 | pipe( 72 | from(saves), 73 | skip(2), 74 | Ob.chain(() => pipe(C.log('fp-ts do after'), Ob.fromIO)) 75 | ).subscribe() 76 | 77 | // ary - honestly this one is wierd for typescript, hard to imagine it being used 78 | 79 | //before - invokes the function before it is called n times 80 | const b4 = _.before(2, () => console.log("before stopping")) 81 | _.forEach([1,2,3,4,5], b4) 82 | 83 | // really you should use streams for this - take will only allow x items from your stream 84 | const stream = from([1,2,3,4,5]) 85 | pipe(stream, take(1)).subscribe(log) 86 | 87 | 88 | // bind - this is what currying is for 89 | const greet = (greeting:string) => (punc:string) => (user:string) => 90 | `${greeting} ${user}${punc}` 91 | 92 | const hellogreeting = greet("hi")("!") 93 | log(hellogreeting("fred")) 94 | 95 | 96 | // curry 97 | var abc = function(a:number, b:number, c:number) { 98 | return [a, b, c]; 99 | }; 100 | 101 | log(_.curry(abc)(1)(2)(3)) 102 | log(curry3(abc)(1)(2)(3)) 103 | 104 | 105 | 106 | // debounce 107 | //emit four strings 108 | const example = of('WAIT', 'ONE', 'SECOND', 'Last will display'); 109 | // emit only once ever second, tales the last value 110 | pipe(example, debounceTime(1000)).subscribe(log) 111 | 112 | 113 | // defer 114 | _.defer(function(text) { 115 | console.log(text); 116 | }, 'deferred 1'); 117 | 118 | // using IO 119 | pipe(T.delay(0)(T.of("defferred 1")), T.chain(flow(C.log, T.fromIO)))() 120 | // not using IO 121 | pipe(T.delay(0)(T.of("defferred 2")))().then(log) 122 | 123 | 124 | // flip 125 | var flipped = _.flip(function(...a) { 126 | return _.toArray(a); 127 | }); 128 | 129 | log(flipped('a', 'b', 'c', 'd')) 130 | // => ['d', 'c', 'b', 'a'] 131 | 132 | log(flip((a, b) => [a, b])(1, 2)) 133 | 134 | //memoize 135 | const foo = (x:number) => { 136 | log("calling foo") 137 | return x; 138 | } 139 | var mfoo = _.memoize(foo); 140 | log(mfoo(3)) 141 | log(mfoo(3)) 142 | 143 | const fpmem = memoize(eqNumber)(foo) 144 | log(fpmem(3)) 145 | log(fpmem(3)) 146 | 147 | // negate 148 | const isEven = (n: number) => n % 2 == 0 149 | 150 | log(_.filter([1, 2, 3, 4, 5, 6], _.negate(isEven))) 151 | 152 | const negate = (f:(a:A)=>boolean) => (a:A) => !f(a) 153 | const notEven = negate(isEven) 154 | log(pipe([1, 2, 3, 4, 5, 6], A.filter(notEven))) 155 | 156 | 157 | // once - as with after and before, we have an issue of state 158 | // see the stateful implementation of after for an idea of 159 | // how to mainain state. Or, use streams which are also nice. 160 | 161 | // overargs 162 | 163 | const doubled = (n:number) => n * 2 164 | const square = (n:number) => n * n 165 | 166 | // no typing info :( 167 | var func = _.overArgs(function(x, y) { 168 | return [x, y]; 169 | }, [square, doubled]); 170 | 171 | log(func(9, 3)) 172 | // => [81, 6] 173 | 174 | 175 | // honestly this is very silly - you can easily apply whatever 176 | // functions to your params before passing them into your other functions 177 | const doargs = (funcs: ((a:A) => A)[]) => (args:A[]) => 178 | pipe(args, A.zip(funcs), A.map(([a, f]) => f(a))) 179 | const args = doargs([square, doubled]) 180 | const func2 = (x?:number, y?:number) => [x, y] 181 | log(func2(...args([9, 3]))) 182 | // => [81, 6] 183 | 184 | // for real, you can just do this 185 | const withFancyParams = (x:number, y:number) => tuple(square(x), doubled(y)) 186 | log(withFancyParams(9,3)) 187 | // => [81, 6] 188 | 189 | 190 | // partial 191 | function greet2(greeting:string, name:string) { 192 | return greeting + ' ' + name; 193 | } 194 | 195 | var sayHelloTo = _.partial(greet2, 'hello'); 196 | log(sayHelloTo('fred')) 197 | 198 | const greet3 = curry2(greet2)('hello') 199 | log(greet3('fred')) 200 | 201 | // partialRight 202 | var greetFred = _.partialRight(greet2, 'fred'); 203 | log(greetFred('hi')) 204 | 205 | const greet4 = F1.flip(curry2(greet2))('fred') 206 | log(greet4('hi')) 207 | 208 | 209 | //rearged - this function is madness, why would you ever do this 210 | 211 | 212 | //rest - the lodash version isn't typesafe, please don't use it 213 | var say = _.rest(function(what:string, names:string) { 214 | return what + ' ' + _.initial(names).join(', ') + 215 | (_.size(names) > 1 ? ', & ' : '') + _.last(names); 216 | }); 217 | 218 | log(say('hello', 'fred', 'barney', 'pebbles')) 219 | 220 | const say2 = (what:string, ...names:string[]) => 221 | `${what} ${pipe(names, 222 | A.init, 223 | O.map(A1.join(", ")), 224 | O.getOrElse(() => '')) 225 | }${names.length > 1 ? ', & ' : ''}${O.getOrElse(() => '')(A.last(names))}` 226 | log(say2('hello', 'fred', 'barney', 'pebbles')) 227 | 228 | // spread - lodash version of spread is also not typesafe, please don't use 229 | var say3 = _.spread(function(who, what) { 230 | return who + ' says ' + what; 231 | }); 232 | 233 | log(say3(['fred', 'hello'])) 234 | const say4 = (who?:string, what?:string) => `${who} says ${what}` 235 | log(say4(...['fred', 'hello'])) 236 | 237 | 238 | // throttle 239 | const throttleExample = of('First will display', 'ONE', 'SECOND', 'Last will not display'); 240 | // emit only once ever second, tales the last value 241 | pipe(throttleExample, throttleTime(1000)).subscribe(log) 242 | 243 | 244 | //unary 245 | log(_.map(['1', '2', '3'], _.unary(_.parseInt))) 246 | 247 | // unary from lodash is basically useless, but unary in 248 | // fp-ts works slightly differently and can be useful. It translates 249 | // a function that takes a variadic number of args into taking an array instead 250 | log(F1.unary(Math.max)([1,2,3])) 251 | 252 | //wrap 253 | var p = _.wrap(_.escape, function(func, text:string) { 254 | return '

' + func(text) + '

'; 255 | }); 256 | 257 | log(p('fred, barney, & pebbles')) 258 | 259 | const p1 = (f: (s:string) => string) => (text:string) => `

${f(text)}

` 260 | 261 | // escaping html isn't really what fp-ts is for, but you can do it like this 262 | //https://stackoverflow.com/a/20403618/1748268 263 | const escapeHTML = (s:string) => 264 | s.replace(/&/g, '&') 265 | .replace(/"/g, '"') 266 | .replace(//g, '>'); 268 | 269 | 270 | log(p1(escapeHTML)('fred, barney, & pebbles')) 271 | 272 | 273 | -------------------------------------------------------------------------------- /array.ts: -------------------------------------------------------------------------------- 1 | import * as A from 'fp-ts/Array' 2 | import * as A1 from 'fp-ts-std/Array' 3 | import * as O from 'fp-ts/Option' 4 | import * as Eq from 'fp-ts/Eq' 5 | import * as R from 'fp-ts/Record' 6 | import * as _ from 'lodash' 7 | import * as M from 'fp-ts/Monoid' 8 | import {getFirstSemigroup} from 'fp-ts/Semigroup' 9 | import {pipe, identity} from 'fp-ts/function' 10 | 11 | const log = console.log 12 | 13 | //chunks 14 | log(_.chunk([1,2,3], 2)) 15 | log(A.chunksOf(2)([1,2,3])) 16 | 17 | //compact 18 | log(_.compact([0, 1, false, 2, '', 3])) 19 | log(A.filter(x => x ? true : false)([0, 1, false, 2, '', 3])) 20 | 21 | log(A.compact([O.some(1), O.zero()])) 22 | 23 | 24 | //concat 25 | log(_.concat([1,2,3], [4,5,6])) 26 | log(A.getMonoid().concat([1,2,3], [4,5,6])) 27 | 28 | //difference 29 | log(_.difference([2, 1], [2, 3])) 30 | log(A.difference(Eq.eqNumber)([2, 1], [2, 3])) 31 | 32 | 33 | //differenceBy 34 | const eqFloor: Eq.Eq = { 35 | equals: (x, y) => Math.floor(x) === Math.floor(y) 36 | } 37 | 38 | const eqProp: Eq.Eq<{x:number}> = { 39 | equals: (x, y) => x.x === y.x 40 | } 41 | 42 | log(_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor)) 43 | log(A.difference(eqFloor)([2.1, 1.2], [2.3, 3.4])) 44 | 45 | //differenceby 46 | log(_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x')) 47 | log(A.difference(eqProp)([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }])) 48 | 49 | //differenceWith 50 | var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] 51 | 52 | log(_.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual)) 53 | 54 | const eq = Eq.getStructEq<{x:number, y:number}>({ 55 | x: Eq.eqNumber, 56 | y: Eq.eqNumber 57 | }) 58 | log(A.difference(eq)(objects, [{ 'x': 1, 'y': 2 }])) 59 | 60 | //drop 61 | log(_.drop([1, 2, 3], 2)) 62 | log(A.dropLeft(2)([1, 2, 3])) 63 | 64 | //drop right 65 | log(_.dropRight([1, 2, 3], 2)) 66 | log(A.dropRight(2)([1,2,3])) 67 | 68 | //drop right while 69 | var users = [ 70 | { 'user': 'barney', 'active': true }, 71 | { 'user': 'fred', 'active': false }, 72 | { 'user': 'pebbles', 'active': false } 73 | ]; 74 | 75 | log(_.dropRightWhile(users, function(o) { return !o.active; })) 76 | log(pipe(users, A1.dropRightWhile((o) => !o.active))) 77 | 78 | //drop while 79 | var users = [ 80 | { 'user': 'barney', 'active': false }, 81 | { 'user': 'fred', 'active': false }, 82 | { 'user': 'pebbles', 'active': true } 83 | ]; 84 | 85 | log(_.dropWhile(users, function(o) { return !o.active; })) 86 | log(pipe(users, A.dropLeftWhile((o) => !o.active))) 87 | 88 | // fill 89 | log(_.fill(Array(3), 2)) 90 | log(A.replicate(3,2)) 91 | 92 | //findindex 93 | var users = [ 94 | { 'user': 'barney', 'active': false }, 95 | { 'user': 'fred', 'active': false }, 96 | { 'user': 'pebbles', 'active': true } 97 | ]; 98 | 99 | log(_.findIndex(users, function(o) { return o.user == 'barney'; })) 100 | log(pipe(users, A.findIndex(o => o.user == 'barney'))) //returns an option 101 | 102 | //findlastindex 103 | var users = [ 104 | { 'user': 'barney', 'active': true }, 105 | { 'user': 'fred', 'active': false }, 106 | { 'user': 'pebbles', 'active': false } 107 | ]; 108 | 109 | log(_.findLastIndex(users, function(o) { return o.user == 'pebbles'; })) 110 | log(pipe(users, A.findLastIndex(o => o.user == 'pebbles'))) 111 | 112 | //flatten 113 | log(_.flatten([1, [2, [3, [4]], 5]])) 114 | log(A.flatten([[1], [2, [3, [4]], 5]])) //actually typesafe 115 | 116 | //flattendeep 117 | //some recursive shit i guess 118 | 119 | //flattendepth 120 | //same as above 121 | 122 | //from pairs 123 | log(_.fromPairs([['a', 1], ['b', 2]])) 124 | log(R.fromFoldable(getFirstSemigroup(), A.Foldable)([['a', 1], ['b', 2]])) 125 | 126 | //head 127 | log(_.head([1, 2, 3])) 128 | log(A.head([1, 2, 3])) // returns an option 129 | 130 | //indexof 131 | log(_.indexOf([1, 2, 1, 2], 2)) 132 | log(A.findIndex(n => n === 2)([1, 2, 1, 2])) // returns an option 133 | 134 | //initial 135 | log(_.initial([1, 2, 3])) 136 | log(A.init([1, 2, 3])) // returns an option 137 | 138 | //intersection 139 | log(_.intersection([2, 1], [2, 3])) 140 | log(pipe([2,1], A.intersection(Eq.eqNumber)([2,3]))) 141 | 142 | //intersectionby 143 | log(_.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor)) 144 | log(pipe([2.1, 1.2], A.intersection(eqFloor)([2.3, 3.4]))) 145 | 146 | //intersectionwith 147 | var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; 148 | var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; 149 | 150 | log(_.intersectionWith(objects, others, _.isEqual)) 151 | log(pipe(objects, A.intersection(eqProp)(others))) 152 | 153 | //join 154 | log(_.join(['a', 'b', 'c'], '-')) 155 | log(A1.join('-')(['a', 'b', 'c'])) 156 | 157 | //last 158 | log(_.last([1, 2, 3])) 159 | log(A.last([1, 2, 3])) 160 | 161 | // last index of 162 | log(_.lastIndexOf([1, 2, 1, 2], 2)) 163 | log(pipe([1, 2, 1, 2], A.findLastIndex(n => n === 2))) 164 | 165 | // nth 166 | log(_.nth(['a', 'b', 'c', 'd'], 1)) 167 | log(A.lookup(1, ['a', 'b', 'c', 'd'])) // returns option 168 | 169 | //pull 170 | var array = ['a', 'b', 'c', 'a', 'b', 'c']; 171 | 172 | log(_.pull(array, 'a', 'c')) 173 | log(pipe(array, A.filter((a) => a !== 'a' && a !== 'c'))) 174 | 175 | //pullall 176 | log(_.pullAll(array, ['a', 'c'])) 177 | log(pipe(array, A.filter((a) => a !== 'a' && a !== 'c'))) 178 | 179 | //pullallby 180 | var arr = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; 181 | 182 | log(_.pullAllBy(arr, [{ 'x': 1 }, { 'x': 3 }], 'x')) 183 | log(pipe(arr, A.filter((a) => a.x !== 1 && a.x !== 3))) 184 | 185 | //pullAllWith 186 | var arr2 = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }]; 187 | 188 | log(_.pullAllWith(arr2, [{ 'x': 3, 'y': 4 }], _.isEqual)) 189 | log(pipe(arr2, A.filter((a) => a.x !== 3 && a.y !== 4))) 190 | 191 | //pullat 192 | var arr3 = ['a', 'b', 'c', 'd']; 193 | log(_.pullAt(arr3, [1, 3])) 194 | var arr4 = ['a', 'b', 'c', 'd'] 195 | log(pipe([1,3], A.reduce([] as O.Option[], (acc, n) => A.snoc(acc, A.lookup(n)(arr4))), A.compact ) ) 196 | 197 | //remove 198 | var arr5 = [1, 2, 3, 4]; 199 | var evens = _.remove(arr5, function(n) { 200 | return n % 2 == 0; 201 | }) 202 | log(evens) 203 | log(arr5) // array was mutated in place :( 204 | const arr6 = [1,2,3,4] 205 | 206 | log(pipe(arr6, A.filter(n => n % 2 !== 0))) 207 | 208 | //reverse 209 | log(_.reverse([1,2,3])) 210 | log(A.reverse([1,2,3])) 211 | 212 | //slice 213 | log(_.slice([1,2,3], 1, 2)) 214 | log(A1.slice(1)(2)([1,2,3])) 215 | 216 | //sorteditems 217 | log(_.sortedIndex([30, 50], 40)) 218 | log(pipe([30, 50], A.findIndex((x => x >= 40)))) 219 | 220 | //sortedindexby 221 | var o1 = [{ 'x': 4 }, { 'x': 5 }]; 222 | log(_.sortedIndexBy(o1, { 'x': 4 }, function(o) { return o.x; })) 223 | log(pipe(o1, A.findIndex((x => x.x >= 4)))) 224 | 225 | //sortedindexof 226 | log(_.sortedIndexOf([4, 5, 5, 5, 6], 5)) 227 | log(A.findIndex(x => x === 5)([4, 5, 5, 5, 6])) 228 | 229 | log(_.sortedLastIndex([4, 5, 5, 5, 6], 5)) 230 | log(pipe(A.findLastIndex(x => x === 5)([4, 5, 5, 5, 6]), O.map(x => x+1))) 231 | 232 | 233 | //sortedLastIndexBy 234 | var o2 = [{ 'x': 4 }, { 'x': 5 }]; 235 | log(_.sortedLastIndexBy(o2, { 'x': 4 }, function(o) { return o.x; })) 236 | log(pipe(o2, A.findLastIndex(x => x.x === 4), O.map(x => x+1))) 237 | 238 | //sortedLastIndexOf 239 | log(_.sortedLastIndexOf([4, 5, 5, 5, 6], 5)) 240 | log(A.findLastIndex(x => x === 5)([4, 5, 5, 5, 6])) 241 | 242 | //sortedUniq 243 | log(_.sortedUniq([1, 1, 2])) 244 | log(A.uniq(Eq.eqNumber)([1, 1, 2])) 245 | 246 | //sortedUniqBy 247 | log(_.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor)) 248 | log(A.uniq(eqFloor)([1.1, 1.2, 2.3, 2.4])) 249 | 250 | //tail 251 | log(_.tail([1, 2, 3])) 252 | log(A.tail([1, 2, 3])) 253 | 254 | //take 255 | log(_.take([1, 2, 3], 2)) 256 | log(A.takeLeft(2)([1,2,3])) 257 | 258 | //takeright 259 | log( _.takeRight([1, 2, 3], 2)) 260 | log(A.takeRight(2)([1,2,3])) 261 | 262 | //takerightwhile 263 | var users = [ 264 | { 'user': 'barney', 'active': true }, 265 | { 'user': 'fred', 'active': false }, 266 | { 'user': 'pebbles', 'active': false } 267 | ]; 268 | 269 | log(_.takeRightWhile(users, function(o) { return !o.active; })) 270 | log(pipe(users, A1.takeRightWhile(({active}) => !active))) 271 | 272 | //takewhile 273 | var users = [ 274 | { 'user': 'barney', 'active': false }, 275 | { 'user': 'fred', 'active': false }, 276 | { 'user': 'pebbles', 'active': true } 277 | ]; 278 | 279 | log(_.takeWhile(users, function(o) { return !o.active; })) 280 | log(pipe(users, A.takeLeftWhile(({active}) => !active))) 281 | 282 | //union 283 | log(_.union([2], [1, 2])) 284 | log(A.union(Eq.eqNumber)([1,2])([2])) 285 | 286 | //unionby 287 | log(_.unionBy([2.1], [1.2, 2.3], Math.floor)) 288 | log(A.union(eqFloor)([1.2, 2.3])([2.1])) 289 | 290 | //unionwith 291 | var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; 292 | var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; 293 | 294 | log(_.unionWith(objects, others, _.isEqual)) 295 | log(A.union(eq)(others)(objects)) 296 | 297 | //uniq 298 | log(_.uniq([2, 1, 2])) 299 | log(A.uniq(Eq.eqNumber)([2, 1, 2])) 300 | 301 | //uniqby 302 | log(_.uniqBy([2.1, 1.2, 2.3], Math.floor)) 303 | log(A.uniq(eqFloor)([2.1, 1.2, 2.3])) 304 | 305 | //uniqwith 306 | var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }]; 307 | 308 | log(_.uniqWith(objects, _.isEqual)) 309 | log(A.uniq(eq)(objects)) 310 | 311 | //unzip - only works with 2 tuples 312 | var zipped : any[] = _.zip(['a', 'b'], [1, 2]); 313 | log(zipped) 314 | log(_.unzip(zipped)) //only works with 2 tuples not n tuples 315 | 316 | //unzipwith - also only works with 2 tuples 317 | var z1 = _.zip([1, 2], [10, 20]) as number[][]; 318 | // => [[1, 10], [2, 20]] 319 | 320 | log(_.unzipWith(z1, _.add)) 321 | log(pipe(z1, A.reduce([0,0] as number[], (acc, n) => [acc[0] + n[0], acc[1] + n[1]]))) 322 | 323 | 324 | //without 325 | log(_.without([2, 1, 2, 3], 1, 2)) 326 | log(A.filter(x => x !== 1 && x !== 2)([2,1,2,3])) 327 | 328 | //xor 329 | log(_.xor([2, 1], [2, 3])) 330 | log(A1.symmetricDifference(Eq.eqNumber)([2,1])([2,3])) 331 | 332 | //xorby 333 | log(_.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor)) 334 | log(A1.symmetricDifference(eqFloor)([2.1,1.2])([2.3,3.4])) 335 | 336 | //xorwith 337 | var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; 338 | var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; 339 | 340 | log(_.xorWith(objects, others, _.isEqual)) 341 | log(A1.symmetricDifference(eq)(objects)(others)) 342 | 343 | //zip - only works with 2 tuples 344 | log(_.zip(['a', 'b'], [1, 2])) 345 | log(A.zip(['a', 'b'], [1, 2])) 346 | 347 | //zip object 348 | log(_.zipObject(['a', 'b'], [1, 2])) 349 | log(R.fromFoldableMap(getFirstSemigroup(), A.array)(A.zip(['a', 'b'], [1, 2]), identity)) 350 | 351 | //zip object deep 352 | log(_.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2])) 353 | log(pipe( 354 | A.zipWith(['c', 'd'], [13, 30], (key, value) => ({[key]: value})), 355 | b => ({ a: { b } }) 356 | )) 357 | 358 | //zipwith 359 | log(_.zipWith([1, 2], [10, 20], function(a, b) { 360 | return a + b; 361 | })) 362 | 363 | log(A.zipWith([1,2], [10, 20], M.monoidSum.concat)) -------------------------------------------------------------------------------- /object.ts: -------------------------------------------------------------------------------- 1 | import { eqNumber, eqString } from 'fp-ts/Eq' 2 | import { getLastSemigroup } from 'fp-ts/Semigroup' 3 | import * as M from 'fp-ts/Map' 4 | import * as R from 'fp-ts/Record' 5 | import * as R1 from 'fp-ts-std/Record' 6 | import * as Semi from 'fp-ts/Semigroup' 7 | import * as Mon from 'fp-ts/Monoid' 8 | import {merge} from 'fp-ts-std/Record' 9 | import * as _ from 'lodash' 10 | import {withFallback} from 'io-ts-types/withFallback' 11 | import * as t from 'io-ts' 12 | import * as O from 'fp-ts/Option' 13 | import * as O1 from 'fp-ts-std/Option' 14 | import * as E1 from 'fp-ts-std/Either' 15 | import * as P from 'monocle-ts/lib/Prism' 16 | import * as Op from 'monocle-ts/lib/Optional' 17 | import * as A from 'fp-ts/Array' 18 | import { pipe, identity } from 'fp-ts/lib/function' 19 | import { fromNumber } from 'fp-ts-std/String' 20 | import * as ROA from 'fp-ts/ReadonlyArray' 21 | 22 | const log = console.log 23 | 24 | // _.assign 25 | log(_.assign({a: 0, b: 2}, {a: 1, c: 3})) // this acutally mutates the object :( 26 | log(merge({a: 0, b: 2})({a: 1, c: 3})) 27 | 28 | // _.assignIn + _.assignInWith 29 | // this is really a prototypical inheritance javascript thing, not a typescript thing 30 | 31 | 32 | // _.assignWtih 33 | // In general, the best way to merge records together is with a semigroup. 34 | // The assignWith example code however is providing default values - something 35 | // that can be done more easily with Options. We'll do both things here 36 | // so that you can choose which one you need for your use case 37 | 38 | // decode a random object, filling in defaults 39 | // codecs are customizable, so you can put any codec you like here. 40 | const ab = t.type({ 41 | a: withFallback(t.number, 0), 42 | b: withFallback(t.number, 1) 43 | }) 44 | 45 | // decoding a value is inherently something that can go wrong, so decode 46 | // returns an Either. Eithers can be `right` which means we succeded in decoding 47 | // or `left` which means we didn't succeed. 48 | log(E1.unsafeUnwrap(ab.decode({}))) // generally a bad idea to unsafeUnwrap, but this is illustrative 49 | log(ab.decode(1)) // this fails because you can't decode a number to an object 50 | log(ab.decode({a: 4, c: 12})) // Defaults b, and keeps a and c. To strip values that aren't 51 | // in the codec, use `strict` instead of `type`. 52 | 53 | 54 | // Semigroups are things that can be combined together. 55 | interface optionAB{ 56 | a?: number, 57 | b?: number, 58 | c?: number 59 | } 60 | // this is an implementation of a semigroup - it defines what happens 61 | // when the `concat` function is called. In this case it will pick the 62 | // first value if its not undefined, otherwise it will return the second value. 63 | const semiundef = { 64 | concat:
(x?: A, y?: A) => x !== undefined ? x : y 65 | } 66 | 67 | // get a semigroup instance that works with the optionAB interface 68 | const semiAB = Semi.getStructSemigroup({ 69 | a: semiundef, 70 | b: semiundef, 71 | c: semiundef 72 | }) 73 | // now we can start smashing optionAB types together 74 | log(semiAB.concat({a: 1}, {a: 2, b:2, c: 3})) 75 | // { a: 1, b: 2, c: 3 } 76 | 77 | // _.at 78 | 79 | var object = { 'a': [{ 'b': { 'c': 3 } }, 4] } 80 | log(_.at(object, ['a[0].b.c', 'a[1]'])) 81 | 82 | 83 | // this implemtnation seems more complex than the lodash one - however, 84 | // the lodash version has very little type information - getting 'undefined' 85 | // generally means you've screwed something up, but what exactly? 86 | // Using Lensing allows us to access nested properties in a composable and 87 | // typesafe way 88 | 89 | // the prisim lets us select between the sum types 90 | const j: P.Prism<{b: {c: number}} | number, number> = { 91 | getOption: (s) => (typeof s === "number") ? O.some(s) : O.some(s.b.c), 92 | reverseGet: identity, 93 | } 94 | 95 | const pp = (index: number) => pipe( 96 | Op.id<{ a: ReadonlyArray<{b: {c: number}} | number> }>(), 97 | Op.prop('a'), 98 | Op.index(index), 99 | Op.compose(P.asOptional(j)), 100 | a => a.getOption 101 | ) 102 | log(A.compact([pp(0)(object), pp(1)(object)])) 103 | 104 | // _.create - again, not really applicable to typescript 105 | 106 | // _.defaults + _.defaultsDeep - see _.assignWith 107 | 108 | // _.findKey 109 | var users = { 110 | 'barney': { 'age': 36, 'active': true }, 111 | 'fred': { 'age': 40, 'active': false }, 112 | 'pebbles': { 'age': 1, 'active': true } 113 | }; 114 | 115 | log(_.findKey(users, function(o) { return o.age < 40; })) 116 | // => 'barney' 117 | 118 | // filter all the records, grab the first key 119 | log(pipe(users, R.filterWithIndex((k, v) => v.age < 40), R.keys, A.head)) 120 | 121 | 122 | // _.findLastKey 123 | log(_.findLastKey(users, function(o) { return o.age < 40; })) 124 | 125 | log(pipe(users, R.filterWithIndex((k, v) => v.age < 40), R.keys, A.last)) 126 | 127 | // _.forIn - this funciton is not really applicable for fp-ts 128 | // you should either be transforming some data into other data 129 | // or transforming data into IO - either way, you don't loop 130 | 131 | // _.forInRight - see above 132 | 133 | // _.forOwn - see above 134 | 135 | // _.forOwnRight - see above 136 | 137 | // _.functions - ?? 138 | 139 | // _.functionsIn - ?? 140 | 141 | // _.get 142 | var obj = { 'a': [{ 'b': { 'c': 3 } }] } 143 | log(_.get(object, 'a[0].b.c')) 144 | 145 | // do it with lenses 146 | const getC = pipe( 147 | Op.id<{ a: readonly { b: { c: number } }[] }>(), 148 | Op.prop('a'), 149 | Op.index(0), 150 | Op.prop('b'), 151 | opt => opt.getOption, 152 | ) 153 | log(getC(obj)) 154 | 155 | // _.has 156 | log(_.has(obj, 'a')) 157 | log(pipe(R.lookup('a')(obj), O.isSome)) 158 | 159 | // with lensing for nested lookups 160 | log(pipe(getC(obj), O.isSome)) 161 | 162 | // _.hasIn - see _.has 163 | 164 | // _.invert 165 | var obj1 = { 'a': 1, 'b': 2, 'c': 1 }; 166 | log(_.invert(obj1)) 167 | 168 | log(R1.invertLast(fromNumber)(obj1)) 169 | 170 | // _.invertBy 171 | var obj2 = { 'a': 1, 'b': 2, 'c': 1 }; 172 | log(_.invertBy(obj2)) 173 | log(_.invertBy(obj2, function(value) { 174 | return 'group' + value; 175 | })) 176 | 177 | log(R1.invertAll(fromNumber)(obj2)) 178 | log(R1.invertAll(n => `group${n}`)(obj2)) 179 | 180 | // _.invoke - this is just a terrible idea :/ 181 | // you can use lensing to get the property you want and then invoke it 182 | 183 | // _.keys 184 | log(_.keys({'a': 1, 'b':2})) 185 | log(R.keys({'a': 1, 'b':2})) 186 | 187 | // _.keysIn - see keys 188 | 189 | // _.mapKeys 190 | log(_.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { 191 | return key + value; 192 | })) 193 | log(pipe({ 'a': 1, 'b': 2 }, R.reduceWithIndex({}, (k, acc, v) => ({...acc, [k+v]: v})))) 194 | 195 | // _.mapValues 196 | log(_.mapValues({ 'a': 1, 'b': 2 }, (v) => v+1)) 197 | log(pipe({ 'a': 1, 'b': 2 }, R.map(v => v+1))) 198 | 199 | // _.merge -- this is nasty, it will just drop values if it doesn't get what it expects 200 | // please don't use the lodash merge, it will lead to subtle bugs :( 201 | var obj3 = { 202 | 'a': [{ 'b': 2 }, { 'd': 4 }, {'f': 6}, {'g': 3}] 203 | }; 204 | 205 | // this is not typesafe, you can see a 5 in there 206 | var other = { 207 | 'a': [{ 'c': 3 }, { 'e': 5 }, {'f': 12}, 5] 208 | }; 209 | 210 | // gives a bad answer, just drops {g: 3} >:| 211 | log(_.merge(obj3, other)) 212 | 213 | 214 | type nested = Record[]> 215 | var obj4: nested = { 216 | 'a': [{ 'b': 2 }, { 'd': 4 }, {'f': 6}, {'g': 3}] 217 | }; 218 | 219 | var other2: nested = { 220 | 'a': [{ 'c': 3 }, { 'e': 5 }, {'f': 12}] 221 | }; 222 | 223 | // ok so i wanted to zip uneven lists but this doesn't exist in fp-ts (yet!) so i just made it real quick 224 | // it uses options to tell you if one side or the other isn't there 225 | const zipWithAll = ( 226 | fa: ReadonlyArray, 227 | fb: ReadonlyArray, 228 | f: (a: O.Option, b: O.Option) => C): ReadonlyArray => { 229 | const fc: Array = [] 230 | const len = Math.max(fa.length, fb.length) 231 | for (let i = 0; i < len; i++) { 232 | fc[i] = f(i < fa.length ? O.some(fa[i]) : O.none, i < fb.length ? O.some(fb[i]) : O.none) 233 | } 234 | return fc 235 | } 236 | 237 | // zip but just gives arrays of size 1 when its uneven 238 | const zipAll = (fa: ReadonlyArray, fb: ReadonlyArray) => 239 | zipWithAll(fa, fb, (a, b) => A.compact<(A|B)>([a, b])) 240 | 241 | // merge two records together - if there is a collision take the first one 242 | const mergeDict = R.getMonoid(Semi.getLastSemigroup()).concat 243 | // convienience func for concating arrays together 244 | const concat = (a:A[]) => (b:A[]) => A.getMonoid().concat(a, b) 245 | 246 | // an instance of semigroup for the nested type 247 | const nestedSemigroup = { 248 | concat: (x: nested, y: nested) : nested => { 249 | const keys = pipe(R.keys(x), concat(R.keys(y)), A.uniq(eqString)) 250 | return pipe(keys, A.reduce({}, (acc, k) => { 251 | const left = R.lookup(k)(x) 252 | const right = R.lookup(k)(y) 253 | if(O.isNone(left) && O.isSome(right)) return {...acc, [k] : O1.unsafeUnwrap(right)} 254 | if(O.isSome(left) && O.isNone(right)) return {...acc, [k] : O1.unsafeUnwrap(left)} 255 | // we know they must both have hit 256 | const mergedItems = pipe( 257 | zipAll(O1.unsafeUnwrap(left), O1.unsafeUnwrap(right)), 258 | ROA.map(A.reduce({} as Record, mergeDict)) 259 | ) 260 | return {...acc, [k]: mergedItems} 261 | })) 262 | } 263 | } 264 | 265 | // this gives you the correct answer without undefined behavior 266 | log(nestedSemigroup.concat(obj4, other2)) 267 | 268 | // omit 269 | var omitobj = { 'a': 1, 'b': '2', 'c': 3 }; 270 | 271 | log(_.omit(omitobj, ['a', 'c'])) 272 | // => { 'b': '2' } 273 | 274 | log(R1.omit(['a', 'c'])(omitobj)) 275 | 276 | // omitby 277 | var omitbyobj = { 'a': 1, 'b': '2', 'c': 3 }; 278 | 279 | log(_.omitBy(omitbyobj, _.isNumber)) 280 | 281 | log(R1.reject(_.isNumber)(omitbyobj)) 282 | 283 | // pick 284 | type MyType = { a: number; b: string; c: number } 285 | var pickobj = { 'a': 1, 'b': '2', 'c': 3 }; 286 | 287 | log(_.pick(pickobj, ['a', 'c'])) 288 | 289 | log(R1.pick()(['a', 'c'])(pickobj)) 290 | 291 | // pickby 292 | var pickbyobj = { 'a': 1, 'b': '2', 'c': 3 }; 293 | 294 | log(_.pickBy(pickbyobj, _.isNumber)) 295 | // => { 'a': 1, 'c': 3 } 296 | 297 | log(R.filterWithIndex((k, v) => _.isNumber(v))(pickbyobj)) 298 | 299 | // result -- see get 300 | 301 | // set 302 | var setobj = { 'a': [{ 'b': { 'c': 3 } }] }; 303 | 304 | _.set(setobj, 'a[0].b.c', 4); 305 | log(JSON.stringify(setobj)); 306 | 307 | 308 | // do it with lenses 309 | var setobjlens = { 'a': [{ 'b': { 'c': 3 } }] }; 310 | const setC = pipe( 311 | Op.id<{ a: readonly { b: { c: number } }[] }>(), 312 | Op.prop('a'), 313 | Op.index(0), 314 | Op.prop('b'), 315 | Op.prop('c'), 316 | Op.modify((c) => 4) 317 | ) 318 | log(JSON.stringify(setC(setobjlens))) 319 | 320 | // setwith - this is an odd function bc its for building out objects with defaults - look into io-ts to do this 321 | 322 | // toPairs 323 | var pairsobj = {a: 1, b: 2} 324 | log(_.toPairs(pairsobj)) 325 | 326 | log(R.collect((k, v) => [k, v])(pairsobj)) 327 | 328 | // toPairsIn - see toPairs 329 | 330 | // transform 331 | 332 | log(_.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result:{[key:string]: string[]}, value, key) { 333 | (result[value] || (result[value] = [])).push(key); 334 | }, {})) 335 | 336 | log( 337 | pipe( 338 | { 'a': 1, 'b': 2, 'c': 1 }, 339 | R.reduceWithIndex({} as {[key:string]: string[]},(k:string, acc, n) => { 340 | return {...acc, [`${n}`]: A.snoc(pipe(O.fromNullable(acc[`${n}`]), O.getOrElse(() => [] as string[])), k)} 341 | }) 342 | )) 343 | 344 | // unset 345 | var unsetobj = { 'a': [{ 'b': { 'c': 7 } }] }; 346 | _.unset(unsetobj, 'a[0].b.c'); 347 | // => true 348 | log(JSON.stringify(unsetobj)) 349 | // => { 'a': [{ 'b': {} }] }; 350 | 351 | var unsetobjlens = { 'a': [{ 'b': { 'c': 7 } }] }; 352 | const filterunset = pipe( 353 | Op.id<{ a: readonly { b: { c: number } | {} }[] }>(), 354 | Op.prop('a'), 355 | Op.index(0), 356 | Op.prop('b'), 357 | opt => opt.set({}) 358 | 359 | ) 360 | log(JSON.stringify(filterunset(unsetobjlens))) 361 | 362 | // update 363 | var updateobj = { 'a': [{ 'b': { 'c': 3 } }] }; 364 | 365 | _.update(updateobj, 'a[0].b.c', function(n) { return n * n; }); 366 | log(JSON.stringify(updateobj)); 367 | 368 | // do it with lenses 369 | var updatelensobj = { 'a': [{ 'b': { 'c': 3 } }] }; 370 | const updateObjLens = pipe( 371 | Op.id<{ a: readonly { b: { c: number } }[] }>(), 372 | Op.prop('a'), 373 | Op.index(0), 374 | Op.prop('b'), 375 | Op.prop('c'), 376 | Op.modify((c) => c*c) 377 | ) 378 | log(JSON.stringify(updateObjLens(updatelensobj))) 379 | 380 | // updatewith - todo, maybe some kind of defaults in lenses? io-ts? 381 | 382 | // values 383 | var valuesobj = {a: 1, b:2} 384 | log(_.values(valuesobj)) 385 | 386 | log(R1.values(valuesobj)) 387 | 388 | // valuesin - see values --------------------------------------------------------------------------------