├── 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
--------------------------------------------------------------------------------