mobily / ts-belt Goto Github PK
View Code? Open in Web Editor NEW🔧 Fast, modern, and practical utility library for FP in TypeScript.
Home Page: https://mobily.github.io/ts-belt
License: MIT License
🔧 Fast, modern, and practical utility library for FP in TypeScript.
Home Page: https://mobily.github.io/ts-belt
License: MIT License
If I'm fetching an array from a server, and need to find an element in the array before returning the result, I would expect that I could return Result<Option<Elem>>
, but instead R.map(A.getBy(/*...*/))
fails to pass type-checks because R.map expects a non-nullable result from its parameter.
Am I misunderstanding how I'm supposed to be achieving this? Is there something wrong with having an Option
inside a Result
?
It would be nice if there was a function to prepend one string to another. There is already S.concat
, so it seems like there may as well be S.prepend
Could not find discussion panel nor any discord/slack/anything channel for any Q&A.
Let me know if there is a better place to ask questions regarding ts-belt.
I'm kinda new to FP in general and I can't shift my usual non FP thinking.
Is there anyway that I can tackle this with FP/ts-belt??
Scenario: Need to find position (index+1) where the sum of values equals to -1
Example data :
let position: number | undefined = undefined;
_.pipe(
[1,1,-1,1,-1,-1,-1,1,1,1],
_.A.reduceWithIndex(0, (acc, next, index) => {
const add = _.N.add(acc)(next);
if (add === -1 && position === undefined) {
position = index + 1;
}
return add;
})
);
Is there a way to break out from reduce loop? I guess there is not.
How I can possibly hold a value not using an global variable but maintaining functional paradigms?
What would be a better approach for this case with ts-belt?
Hi, previous to ts-belt I had been using the pratica library which had this really neat try/catch utility called encase that was particularly handy for JSON parsing. It'd be cool if ts-belt had something like that too. 😎
Creates a data-last pipe function. First function must be always annotated. Other functions are automatically inferred.
R.createPipe(op1, op2, op3)(data)
createPipe(
(x: number) => x * 2,
(x) => x * 3
)(1) // => 6
Hi, I have a use case where I would like to use F.when
, but have the return type be different to the type going in to the function. At the moment the return type for F.when
and F.unless
can only be the input type.
The context here is creating a document in a mongodb database and first checking to see if the document already exists. If it exists (checked via MyModel.exists()
), a document object is returned, if it doesnt exist, null is returned.
So I would like to do something like this:
MyModel.exists({foo:'bar'})
.then(O.fromNullable)
.then(F.when(O.isNone, () => MyModel.create({foo:'bar'})))
First of all, thanks for a solid library - it's really a breeze to use!
While doing a bit of Advent of Code, I noticed unexpected output from A.takeWhile
- essentially it seems to act more like A.filter
Examples:
// ts-belt:
A.takeWhile([3, 5, 3], n => n < 4);
// Returns [3, 3]
I'd expect to get back the single-element list: [3]
.
Ramda (and other similar libraries):
// ramda:
R.takeWhile(n => n < 4, [3, 5, 3])
// Returns [3]
Smells a bit like a bug to me 😉
Also - looking at a bit further, it looks like this case isn't covered by the tests
Btw, the version in question is 3.13.1
Hello,
I'm getting a TS error when using Result
and pipe
and have no idea why!
const processEvent = (
event: MessageEvent<unknown>,
handle: (event: ServerEvent) => void,
error: (error: string) => void
) => {
pipe(
R.fromNullable(event, 'Incoming sse/ws message is null!'),
R.map((event) => event.data), // <--- ERROR HERE
R.flatMap((data: unknown) => {
try {
if (G.isString(data)) return R.Ok(JSON.parse(data));
} catch (_error) {
/* ignored */
}
return R.Error('Invalid incoming sse/ws message format!');
}),
R.flatMap((json: string) => {
try {
return R.Ok(ServerEventSchema.parse(json));
} catch (_error) {
return R.Error('Invalid incoming sse/ws message schema!');
}
}),
R.tapError(error),
R.tap(handle)
);
};
The message I get is the following:
TS2345: Argument of type 'Result<unknown, unknown>' is not assignable to parameter of type '(arg: Result<MessageEvent<unknown>, "Incoming sse/ws message is null!">) => Result<unknown, "Invalid incoming sse/ws message format!">'. Type 'Ok<unknown>' is not assignable to type '(arg: Result<MessageEvent<unknown>, "Incoming sse/ws message is null!">) => Result<unknown, "Invalid incoming sse/ws message format!">'. Type 'Ok<unknown>' provides no match for the signature '(arg: Result<MessageEvent<unknown>, "Incoming sse/ws message is null!">): Result<unknown, "Invalid incoming sse/ws message format!">'.
Any ideas?
Thanks!
How can I select just a subset of keys from an object?
Consider this example in an express app. I want to validate the body
, params
, and query
and want to use ts-belt to select just those three from the request. Is there a way to do this with ts-belt? Maybe a unsafeSelectKeys
?
const validKeys = ['body', 'params', 'query']
const validReq = D.selectKeys(request, validKeys)
Thanks for the help!
I was making interchangeable Pos
and Size
, and they work like
Pos(1, 2) + Size(3, 4) // Pos(4, 6)
Size(1, 2) + Pos(3, 4) // Size(4, 6)
but currying didn't work well with pipe
(since pipe is data-first), I had to do something like this:
import { pipe } from '@mobily/ts-belt'
type A = { tag: 'A' }
type B = { tag: 'B' }
type Two = A | B
function foo(a: Two): (c: Two) => Two
function foo(a: Two, b: Two): Two
function foo(a: Two, b?: Two) {
return b ? a : (c: Two) => foo(c, a)
}
const [a, b] = [{ tag: 'A' }, { tag: 'B' }] as const
console.log(foo(a, b)) // { tag: 'A' }
console.log(foo(b)(a)) // { tag: 'B' }
console.log(pipe(a, foo(b))) // { tag: 'A' }
is this considered idiomatic? I see most of ts-belt
's (data-first) functions take the same approch, i.e reversed currying, for example S.concat:
function concat(appendValue: string): (str: string) => string
function concat(str: string, appendValue: string): string
however, I'm confused because ts-belt
is written in rescript, and in rescript docs about pipe, it says
Do not abuse pipes; they're a means to an end. Inexperienced engineers sometimes shape a library's API to take advantage of the pipe. This is backwards.
does ts-belt
take this approach because of pipe
's limitation (cannot change position of argument unless using lambda)?
getName(input)
->namePerson(personDetails, _)
Hi!
Is a D.copy
function planned?
Thanks!
tried solution at #18, but there seems to be problems:
main.tsx
to see that it works)repro at: https://stackblitz.com/edit/vitejs-vite-8d4ao8?file=src/main.tsx
readonly
is constant source of pain, because everytime where code from ts-belt
come to contact with normal JS code it throws type error. It happens us a lot because we are using it in legacy codebase a even if you do thing like simple A.map
you need always wrap result in F.toMutable
.
If I decide to refactor our code to work with it, I need to place readonly
literally everywhere. That will break TS in our tests because of our fixtures which are mutable. Same problem is that everywhere where code come to touch with 3rd party libraries I will throw some errors about readonly
again.
And last and probably strongest argument is this nice thread on Twitter with some great examples why it's even not that safe >>> https://twitter.com/MichaelArnaldi/status/1601535981949456385
I know this is will produce big change (but shouldn't be breaking) and final resolution is on you @mobily but please consider dropping it for v4.
Hi 👋
A belt port in TS is something I've already dreamed of, so thank you!
I tried adding it to our codebase, but think it might have an issue with the Option
type guards (isSome
/ isNone
)
On usage with ts-pattern
(for pattern maching):
import { A, O } from "@mobily/ts-belt";
import { match, when } from "ts-pattern";
const array = A.make(Math.floor(Math.random() * 10), "foo");
match(A.get(array, 5))
.with(when(O.isSome), value => { /* value is O.Option<string>, it should be string */ })
.with(when(O.isNone), value => { /* value is O.Option<never>, it should be never */ })
.exhaustive();
I checked the types of the given function and it confirms the issue:
/** Returns `true` if the provided `option` is `Some(value)`, otherwise, returns `false`. */
export declare function isSome<T>(option: Option<T>): option is Option<T>;
/** Returns `true` if the provided `option` is `None`, otherwise, returns `false`. */
export declare function isNone<T>(option: Option<T>): option is Option<never>;
Shouldn't it returns option is T
and option is never
instead?
Same example with Rescript (v
is int
, not option(int)
):
Love your lib! Though I'd share one of my thoughts. Didn't see anything about this in the docs. Sorry if I'm reraising a question.
As a person with bundle phobia, it would be nice if I could only import the functions that I need. To my understanding, the current way of importing is to import the whole function set for a given type i.e array. import { A } from '@mobily/ts-belt'
What about something like this:
LoDash approach
import shuffle from '@mobily/ts-belt/a/shuffle'
import shuffle from '@mobily/ts-belt/array/shuffle'
Tree-shaking
I have mixed feelings about this one because in some libraries it doesn't really only give you the one function. Not to sure about how all this works, but it might have something to do with your compiler setup. Even though ergonomic to use I fear that in some cases it might not work as expected.
import { shuffle } from '@mobily/ts-belt/a'
List is a purely functional alternative to arrays. ... List is immutable. ... Since List doesn't allow mutations it can be heavily optimized for pure operations.
List's benchmarks are quite impressive. Would like to see a comparison with ts-belt for the operations they share, like map
. Or compare ts-belt with Ramda + List, since List was designed to seamlessly integrate with Ramda.
Maybe ts-belt could even benefit from integrating List?
Hello!
First of all, thank you very much for building ts-belt. It has really attacked what I thought was the biggest problem with fp-ts, namely, bad documentation and incomprehensible errors.
Two functions that I've seen in almost every fp library are Sequence
and Traverse
. I've found whenever I work with Options and Results that I invariably end up reaching for these functions. However, I can't seem to find any equivalent in ts-belt. Is it expected that the user would create their own?
Hi @mobily, the library looks really interesting! I'm quite interested in trying it out for future projects. I just have a couple of questions about certain functions, and I hope this is the right place to ask.
assoc
and dissoc
with ts-belt
? Would it have to be D.merge(m, {k: v})
and D.rejectWithKey(m, (_, key) => key == k)
?D.xxxWithKey
's signature is (v, k)
instead of the usual (or rather what I would expect to be) (k, v)
. Is that just a matter of personal preference or rather a convention from another library/language that I'm not familiar with?ts-belt
does eager evaluation? So something like, say, pipe(m, D.toPairs, A.take(2))
would iterate through all the key-value pairs first before taking the first two elements?Also, something like Remeda's mapping doc would be very helpful to newcomers! I'm happy to chip in if you think it'd be useful.
This crucial function is missing. I guess, accidentally because other namespaces expose it.
For example: https://mobily.github.io/ts-belt/api/option#flatmap
I've double checked it's not exposed under a different name like chain
or concatMap
.
I'm struggling to understand how to satisfy TypeScript when using ts-belt. Appreciate any help
How do I cast array of numbers from splitting the string?
Is this the right approach of such ?
const sample1 = "2x3x4";
_.pipe(
sample1,
_.F.coerce<() => [number, number, number]>(_.S.split("x")),
_.O.flatMap(([l, w, h]) => 2 * l * w + 2 * w * h + 2 * h * l)
);
or
const data = `azs
20x3x11
15x27x5
6x29x7`
const getSurfaceArea = (input: number[]) =>
_.pipe(
input,
_.O.flatMap(([l, w, h]) => {
if (!l || !w || !h) return 0;
const squareFeetArea = 2 * l * w + 2 * w * h + 2 * h * l;
const smallestSide = Math.min(l * w, w * h, h * l);
return squareFeetArea + smallestSide;
})
);
const totalSquareFeet = _.pipe(
data,
_.S.split("\n"),
_.A.map((el) => _.A.map(_.S.split("x")(el), Number).filter(Boolean)),
_.A.map(getSurfaceArea),
_.A.reduce(0, _.N.add)
);
console.log(totalSquareFeet);
TS2345: Argument of type '(xs: readonly number[][]) => readonly Option[]' is not assignable to parameter of type '(arg: readonly number[][]) => readonly number[]'. Type 'readonly Option[]' is not assignable to type 'readonly number[]'. Type 'Option' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'.
how to handle typing of Option/Result
Link: https://mobily.github.io/ts-belt/api/array
node: v18.2.0
typescript: 4.7.3
tsserver: 0.11.1
tsconfig.json: i follow this https://mobily.github.io/ts-belt/docs/getting-started/config
I just tried the code from the array doc and it gives a warning like this:
the code runs fine, but the warning annoys me. So i removed the warning by handling the Option
using match
:
import { A, O, pipe } from "@mobily/ts-belt";
const arr = A.makeWithIndex(5, (i) => i); // if i changed this to [] without O.match, it will throws error
console.log(
pipe(
arr,
A.tail,
O.match( // initially i wanna use O.getWithDefault([]), but it also gives warning. O.getExn works but throws error if arr = [] :(
(tail: readonly number[]) => tail,
() => []
),
A.take(2),
A.map((val) => val * 2)
)
);
question: is it some secret pipe
behavior that handles Option
or A.take
type is not complete or the doc needs an update?
thank you
Hi! Great work with this library 👍
Are there any plans for adding functions related to lenses? Such as lensProp
from Ramda.js?
Steps
npm init
"type": "module"
to package.jsona.js
file with just import * as Belt from '@mobily/ts-belt'
/tsbelt # node a.js
(node:63) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/tsbelt/node_modules/@mobily/ts-belt/dist/esm/index.js:1
import { pipe } from "./pipe.js";
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Object.compileFunction (node:vm:352:18)
at wrapSafe (node:internal/modules/cjs/loader:1031:15)
at Module._compile (node:internal/modules/cjs/loader:1065:27)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:196:29)
at ModuleJob.run (node:internal/modules/esm/module_job:183:25)
at async Loader.import (node:internal/modules/esm/loader:178:24)
at async Object.loadESM (node:internal/process/esm_loader:68:5)
I think it would be helpful if the API documentation would give clarification on whether or not functions such as F.equals
or D.merge
are shallow or deep. We can work this out by looking at the source code, but this shouldn't be necessary and is espescially annoying for people who don't know rescript.
Hey there, this project looks really cool!
Is it possible to use this as a regular Rescript package? It looks like the NPM package only published the generated ts/js files. Is there any future version where this is supported?
Hi there! :)
I was looking for something like lodash's sortBy
with multiple accessors, e.g:
sortBy(users, [user => user.name, user => user.age]);
Why?
I know this could be implemented using the existing A.sortBy
function but after 2 accessors it becomes tedious and prone to error. Lodash implementation might also not be the fastest possible.
Would something like sortByMultiple
be on the roadmap for ts-belt?
Api suggestion:
function sortByMultiple<A, B>(xs: Array<A>, sortFns: Array<(_1: A) => B>): Array<A>
function sortByMultiple<A, B>(sortFns: Array<(_1: A) => B>): (xs: Array<A>) => Array<A>
Thank you for your time 🙏
I'm using A.uniq
on an array of Tuples. Unfortunately, that results in an exception being thrown:
@mobily/ts-belt/dist/cjs/Array/index.js:446
if (!b.hasOwnProperty(key)) {
^
TypeError: b.hasOwnProperty is not a function
First of all, I want to say that I really like ts-belt utilities and really wanted to make it work with my current project.
import { A, D, O, pipe } from '@mobily/ts-belt';
^
SyntaxError: Named export 'A' not found. The requested module '@mobily/ts-belt' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '@mobily/ts-belt';
const { A, D, O, pipe } = pkg;
Issue:
module:nodenext
, ts-belt is resolved as a esm module and redirected to dist/esm
folder. However, the files of this folder still have .js
extension and the package.json
does't contain type:module
option, which makes node assumes that it is trying to import a CommonJS module and try to resolve the import as such.^^^^^^
SyntaxError: Cannot use import statement outside a module
Since all files inside esm
are still understood as commonjs
files, the import
syntax of esm still doesn't work.
Proposed fix:
"type":"module"
to dist/esm/package.json
import
without file extension. Typescript compiler also won't add extensions by default. This may require manually updating ts files with import to include extension instead, for example import * from 'Require'
to import * from 'Require/index.js'
.D.keys
type is incorrect:
const keys = D.keys({ 1: 'ahoj', 2: 'ahoj2' });
console.log(keys);
This will print [ '1', '2' ]
but type for keys
is readonly (1 | 2)[]
which is wrong because it shouldn't be numbers but strings: readonly ('1' | '2')[]
Hi! I'm confused about pipe
accepting a value first. Unless I'm missing something, you can't create a point-free (tacit) function.
With a pipe
that takes the value last (as in most, if not all implementations), you can do this:
const someFn = pipe(fnA, fnB, fnC)
// later:
someFn(value)
I think this is very clean. With a pipe
that takes the value as it's first argument, you always have to "wrap" the composition in a function:
const someFn = (value) => pipe(value, fnA, fnB, fnC)
// later:
someFn(value)
Making a point-free style impossible. Am I missing something?
Hi,
I am using ts-belt in legacy code-base and there is this message in docs:
Adding noUncheckedIndexedAccess to your tsconfig.json is mandatory for the Option type!
Why it's neccessary, will it not work at all or it will break in some cases? Enabling noUncheckedIndexedAccess
in our codebase is not a option because it will throw like in milion places.
F.defaultTo(1, 2) // got 1
but the type definition is that
/** Returns a default value if `value` is nullable. */
export declare function defaultTo<T>(defaultValue: NonNullable<T>, value: T | null | undefined): NonNullable<T>;
version: 3.11.0
For a function
const toFirstName = (input: string) => R.fromPredicate(input, S.isNotEmpty, 'Enter your first name');
the return type is inferred as
which is technically true, but I would have preferred the inferred type to be the same as that of R.fromPredicate()
Right now, I need to specify the return type explicitly as
const toFirstName = (input: string): Result<string, string> => R.fromPredicate(input, S.isNotEmpty, 'Enter your first name');
I use VSCode.
in my environment, after using the map function of type option, use the getWithDefault function of type option. Then the ts(2345) error is displayed.
const result = pipe(
O.fromNullable(null),
O.map((value) => `${value} world!`), //← ts2345 error is displayed.
O.getWithDefault("error")
);
but
const temp = pipe(
O.fromNullable(null),
O.map((value) => `${value} world!`)
);
const result = O.getWithDefault(temp, "test"); //← ts2345 error is NOT displayed.
and
const temp = pipe(
O.fromNullable(null),
O.map((value) => `${value} world!`),
O.getWithDefault("test" as string) //← ts2345 error is NOT displayed.
);
mytsconfig
{
"compilerOptions": {
"strictNullChecks": true,
"noUncheckedIndexedAccess": false,
"target": "ESNext",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
typescript: 4.8.4
node: 16.18.0
use tsc command -> ts2345 error is NOT displayed.
const someValues = [ { id: 1, value: "hello" }, { id: 2, value: "world" } ]
pipe(
someValues,
A.groupBy(value => value.id),
D.mapWithKey((id, values) => {
console.log(typeof id) // prints out string
const numId: number = id; // this should throw, but it doesn't because type is number instead of string
return values;
}),
);
This is kind of interesting problem it could be either type issue or implementation issue. First solution could be that it will be same type as value.id
(A.groupBy(value => value.id)
) and only types that could become object keys should be allowed (probably only number/string ?). Then implementation needs to be fixed.
Or if current implementation is correct it should be types as always string
.
I'd like to use these Option types in an API, IE currently an interface might be:
interface Person {
name: string;
display_name?: string // I'd like this to be display_name: Option<string>;
...
}
Would this be supported?
As per documentation, the function given to flatMap is only invoked if the status is Ok
. Hence, if the Result
was already in an error state BEFORE R.flatMap
is invoked, the state doesn't change.
While this is true at runtime, this is not how the type specification is implemented.
For example:
import {R, pipe} from '@mobily/ts-belt';
class Error1 extends Error{}
class Error2 extends Error{}
const value = Math.random() < 0.5 ? null : 'test';
const test = pipe(
R.fromNullable(value, new Error1()),
R.flatMap((value) => {
return Math.random() < 0.5
? R.Ok(value)
: R.Error(new Error2())
}
)
)
console.log(test);
The type of test
should be Result<string, Error1 | Error2>
, but it is Result<string, Error2>
.
hello there! 👋
I have been working on the new version of ts-belt with support for Async*
modules for a quite long time, and now I feel it's a great time to publish (at least) a release candidate
version. It's also an excellent opportunity to gather feedback from you :) The bad news is, the docs for these modules are missing at the moment (I'm working on it!), but let me describe the essentials of each module:
yarn add @mobily/ts-belt@next
AsyncData
AsyncData
contains a variant type for representing the different states in which a value can be during an asynchronous data load.
There are four possible states: Init
, Loading
, Reloading
, and Complete
.
type AsyncData<T> = Init | Loading | Reloading<T> | Complete<T>
import { AD } from '@mobily/ts-belt'
Variant constructors:
AD.Init
AD.Loading
AD.Reloading(value)
AD.Complete(value)
AD.makeInit()
AD.makeLoading()
AD.makeReloading(value)
AD.makeComplete(value)
Functions:
AD.isInit
AD.isLoading
AD.isReloading
AD.isComplete
AD.isBusy
AD.isIdle
AD.isEmpty
AD.isNotEmpty
AD.toBusy
AD.toIdle
AD.getValue
AD.getWithDefault
AD.getReloading
AD.getComplete
AD.map
AD.mapWithDefault
AD.flatMap
AD.tapInit
AD.tapLoading
AD.tapReloading
AD.tapComplete
AD.tapEmpty
AD.tapNotEmpty
AD.all
AD.fold
Example: https://codesandbox.io/s/cool-star-6m87kk?file=/src/App.tsx
AsyncDataResult
AsyncDataResult
is basically an alias of AsyncData<Result<Ok, Error>>
. This variant type can be used to represent the different states in which a data value can exist while being loaded asynchronously, with the possibility of either success or failure.
type AsyncDataResult<A, B> = AsyncData<Result<A, B>>
import { ADR } from '@mobily/ts-belt'
Variant constructors:
ADR.Init
ADR.Loading
ADR.ReloadingOk(value)
ADR.ReloadingError(error)
ADR.CompleteOk(value)
ADR.CompleteError(error)
ADR.makeInit()
ADR.makeLoading()
ADR.makeReloadingOk(value)
ADR.makeReloadinError(error)
ADR.makeCompleteOk(value)
ADR.makeCompleteError(error)
Functions:
ADR.isOk
ADR.isError
ADR.isReloadingOk
ADR.isReloadingError
ADR.isCompleteOk
ADR.isCompleteError
ADR.getOk
ADR.getReloadingOk
ADR.getCompleteOk
ADR.getError
ADR.getReloadingError
ADR.getCompleteError
ADR.map
ADR.mapError
ADR.flatMap
ADR.tap
ADR.fold
ADR.foldOk
ADR.toAsyncData
Example: https://codesandbox.io/s/brave-cloud-ov30h7?file=/src/App.tsx
AsyncOption
Same as Option
but for handling asynchronous operations.
type AsyncOption<T> = Promise<Option<T>>
import { AO } from '@mobily/ts-belt'
Variant constructors:
AO.make(promise)
AO.resolve(value)
AO.reject()
Functions:
AO.filter
AO.map
AO.flatMap
AO.fold
AO.mapWithDefault
AO.match
AO.toNullable
AO.toUndefined
AO.toResult
AO.getWithDefault
AO.isNone
AO.isSome
AO.tap
AO.contains
AO.flatten
AsyncResult
Same as Result
but for handling asynchronous operations.
type AsyncResult<A, B> = Promise<Result<A, B>>
import { AR } from '@mobily/ts-belt'
Variant constructors:
AR.make(promise)
AR.resolve(value)
AR.reject(error)
Functions:
AR.flatMap
AR.fold
AR.map
AR.mapWithDefault
AR.getWithDefault
AR.filter
AR.match
AR.toNullable
AR.toOption
AR.toUndefined
AR.isOk
AR.isError
AR.tap
AR.tapError
AR.handleError
AR.mapError
AR.catchError
AR.recover
AR.flip
AR.flatten
A.sample
(gets a random element from provided array)O.all
(transforms an array of Option(s)
into a single Option
data type)R.all
(transforms an array of Result(s)
into a single Result
data type)R.filter
(returns Ok(value)
if result
is Ok(value)
and the result of predicateFn
is truthy, otherwise, returns Error
)groupBy
signaturets-belt@v4 does not support Flow
, due to a lack of proper features in flowgen
, sorry about that!
Feel free to post your thoughts, any kind of feedback would be greatly appreciated! 💪
Hey there, loving the library thus far.
I was wondering if pipe
and flow
support async functions without needing to wrap them in an IIFE. Purify supports this via their MaybeAsync
and EitherAsync
wrappers, and I believe Ramda also supports this via the composeWith
and pipeWith
functions.
I'm currently evaluating replacing Ramda with either this library or Purify, but to reduce the amount of churn, being able to support async ops in a composition is ideal.
I have a project that I am creating with SvelteKit, ts-belt seems to work just fine on my client-side code:
if (browser) {
console.log(A.map([1, 2, 3], x => x + 2));
}
However, when I try to run it in on of the server-side files (i.e. +page.server.ts) it throws an error:
import { A } from '@mobily/ts-belt';
export const load: PageServerLoad = () => {
const arr = A.map([1, 2, 3], x => x + 1);
console.log(arr);
return {};
};
TypeError: Cannot read properties of undefined (reading 'A')
at load (.../src/routes/auth/register/+page.server.ts:23:13
I'm not sure if this is a SvelteKit issue, I haven't had that problem with any other library. Any ideas?
Thanks for your work on this awesome library
I'm trying to replace lodash with this in my project, but not sure whether i'm using this library in a idiomatic way
sometimes components only accept mutable types, is it a right practice that manually converting result to un-readonly type by using as
keyword?
for example
export declare type PickerColumnItem = {
label: ReactNode;
value: string;
};
export declare type PickerColumn = (string | PickerColumnItem)[];
export declare type PickerViewProps = {
columns: PickerColumn[] | ((value: PickerValue[]) => PickerColumn[]);
}
when i pass a A.map
output to the columns prop, tsc complaint that:
The type 'readonly { value: string; label: string; }[]' is 'readonly' and cannot be assigned to the mutable type 'PickerColumn'.
and fix this by as PickerColumnItem[]
i wanted to chain multiple Results in a pipe & noticed that there is not a chain operator.
can it be done with something else in this lib or did i miss something?
i am grateful for help
Awesome job! I've been using your library for a while and was wondering if you see it possible to add a Do-bind syntax to be able to chain without so much verbosity.
I've sketched my utility for the Results, but this can be extended to Options and Async Result
export const Do = <T, E = Error>(value?: T) => R.makeOk<NonNullable<T> | {}, E>(value ?? {})
export const bind =
<K extends string ,T, U, E>(key: K, fn: (value: T) => R.Result<U, E>) =>
(prev: R.Result<T, E>) =>
R.flatMap(prev, value => R.map(fn(value), result => D.set(value, key, result)))
export const bindTo = <K extends string ,T, E>(key: K) => (prev: R.Result<T, E>) => R.map<T,E,Record<K, T>>(prev,value => D.fromPairs([[key, value]]))
With this, we can improve readability
const before = pipe(
resultFn1(x),
R.flatMap(prev =>
R.map(resultFn2(y), current => ({
['meningFullKey2']: current,
['meningFullKey1']: prev,
})),
),
R.flatMap(prev =>
R.map(resultFn3(z), current => ({
['meningFullKey3']: current,
...prev,
})),
),
R.flatMap(prev =>
R.map(resultFn3(prev.meningFullKey3, prev.meningFullKey1), current => ({
['meningFullKey4']: current,
...prev,
})),
),
// ...
)
const after = pipe(
R.do(),
R.bind('meningFullKey1', () => resultFn1(x)),
R.bind('meningFullKey2', () => resultFn2(y)),
R.bind('meningFullKey3', () => resultFn3(z)),
R.bind('meningFullKey4', ({ meningFullKey3, meningFullKey1 }) => resultFn3(meningFullKey3, meningFullKey1)),
// ...
)
Hi, I think it would be good to have a curry function in the function api if possible.
Cheers.
example event handler in React:
const onChange = (event: ChangeEvent<HTMLInputElement>, index: number) => {
setTupleState((prev) => {
return [...prev.slice(0, index), event.target.value, ...prev.slice(index + 1)];
});
};
With eslint-plugin-functional/lite
rules I get the following errors:
What is the usage case for this Partial type? Could it just return Record?
Thanks for producing this library.
I'm new to TypeScript and somewhat new to functional programming, but this library seems to have what I would like to see in a functional programming library.
The documentation for A.range
states "Returns a new array of numbers from start (inclusive) to finish (exclusive)." However, it actually appears to include the upper bound. Here is the text from a Node session (using ts-node
):
λ ./node_modules/.bin/ts-node
import { A } from '@mobily/ts-belt';
undefined
A.range(0, 3);
[ 0, 1, 2, 3 ]
A.rangeBy(0, 3, 1);
[ 0, 1, 2, 3 ]
When I look at the documentation for Belt.Array.range
, it reports that the range is inclusive (on both arguments) and the following example duplicates the behavior that I am seeing:
Belt.Array.range(0, 3) == [0, 1, 2, 3]
I'm uncertain if the error is in the documentation or in the implementation.
function uniqBy<A>(xs: ReadonlyArray<A>, uniqFn: (_1: A) => A): ReadonlyArray<A>
instead of
function uniqBy<A, B>(xs: ReadonlyArray<A>, uniqFn: (_1: A) => B): ReadonlyArray<A>
It would be a lovely addition if partition
were aware of type guard functions. Something like this
export function partition<A>(
xs: ReadonlyArray<A>,
predicate: (value: A) => boolean
): [A[], A[]];
export function partition<A, B extends A>(
xs: ReadonlyArray<A>,
predicate: (value: A) => value is B
): [B[], Exclude<A, B>[]];
I thought it might be sufficient to drop this in https://github.com/mobily/ts-belt/blob/master/src/Array/Array.ts but I'm not confident enough to open it as a PR without being able to test locally and the tooling for this project is new to me so I couldn't afford to invest the time in getting it set up yet.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.