gigobyte / purify Goto Github PK
View Code? Open in Web Editor NEWFunctional programming library for TypeScript - https://gigobyte.github.io/purify/
License: ISC License
Functional programming library for TypeScript - https://gigobyte.github.io/purify/
License: ISC License
I was wondering if there was a roadmap, even if vague and/or previously unwritten, for this library?
The API and explanations in the docs so far look great so far. Even though I am not currently using TypeScript, this library seems like one I would adopt (in the untyped JS world, I have adopted Sanctuary).
However, the following is unclear:
But by all means, if you are satisfied with the current state of the library, that's cool too - I can see a lot of the essentials are already in place. All I can think of are things like typesafe pipes (which I hear are a challenge in TypeScript, and safe record property access, and that's more niche and perhaps better served in a standalone JSON handling library).
(also, thank you for writing this library. It got me interested in fluent functional APIs again!)
It'd be really nice if in my beautiful declarative fluent call chain, i could unwrap a Either<L, Promise<R>>
into a EitherAsync<L, R>
(or just as well a Maybe<Promise<T>>
into a MaybeAsync<T>
)
If I have:
function saveEgg(egg: Egg): Promise<Egg> { ... }
function breakEgg(egg: Egg): BrokenEgg { ... }
const egg = new Egg();
and my requirements are to save (asynchronous) and then break this egg, and I would like to have an EitherAsync<Error, Egg>
at the end, here's how I picture this happening in an ideal world:
const savedEgg = Either.of<Error, Egg>(egg) // Either<Error, Egg>
.map(saveEgg) // Either<Error, Promise<Egg>>
.unwrapAsync() // EitherAsync<Error, Egg>
.map(breakEgg); // EitherAsync<Error, BrokenEgg>
right now I can kind of do this with .either
and a couple helper functions:
function eitherAsyncFromLeft<L>(value: L): EitherAsync<L, never>;
function eitherAsyncFromPromise<T>(promise: Promise<T>): EitherAsync<Error, T>
const savedEgg = Either.of<Error, Egg>(egg) // Either<Error, Egg>
.map(saveEgg) // Either<Error, Promise<Egg>>
.either(
eitherAsyncFromLeft,
eitherAsyncFromPromise,
) // EitherAsync<Error, Egg>
.map(breakEgg); // EitherAsync<Error, BrokenEgg>
I have a function with a return type of Maybe<EthereumCredential>
. When I return Nothing
, TypeScript complains.
However, when I comment out strict: true
in my tsconfig, then it compiles fine. Specifically, it looks like the issue is caused by setting strictFunctionTypes: true
(which strict
enables).
Since many projects use strict mode, is it possible to make it compatible/is there a workaround?
Here's the full output:
[ts]
Type 'Maybe<never>' is not assignable to type 'Maybe<EthereumCredential>'.
Types of property ''fantasy-land/alt'' are incompatible.
Type '(other: Maybe<never>) => Maybe<never>' is not assignable to type '(other: Maybe<EthereumCredential>) => Maybe<EthereumCredential>'.
Types of parameters 'other' and 'other' are incompatible.
Type 'Maybe<EthereumCredential>' is not assignable to type 'Maybe<never>'.
Types of property ''fantasy-land/ap'' are incompatible.
Type '<U>(maybeF: Maybe<(value: EthereumCredential) => U>) => Maybe<U>' is not assignable to type '<U>(maybeF: Maybe<(value: never) => U>) => Maybe<U>'.
Types of parameters 'maybeF' and 'maybeF' are incompatible.
Type 'Maybe<(value: never) => any>' is not assignable to type 'Maybe<(value: EthereumCredential) => any>'.
Types of property ''fantasy-land/alt'' are incompatible.
Type '(other: Maybe<(value: never) => any>) => Maybe<(value: never) => any>' is not assignable to type '(other: Maybe<(value: EthereumCredential) => any>) => Maybe<(value: EthereumCredential) => any>'.
Types of parameters 'other' and 'other' are incompatible.
Type 'Maybe<(value: EthereumCredential) => any>' is not assignable to type 'Maybe<(value: never) => any>'.
Types of property 'orDefault' are incompatible.
Type '(defaultValue: (value: EthereumCredential) => any) => (value: EthereumCredential) => any' is not assignable to type '(defaultValue: (value: never) => any) => (value: never) => any'.
Types of parameters 'defaultValue' and 'defaultValue' are incompatible.
Types of parameters 'value' and 'value' are incompatible.
Type 'EthereumCredential' is not assignable to type 'never'. [2322]
When you don't want the default value to be evaluated
TS: 3.3.3333
purify: 0.12.2
According to the docs, it should be possible to destructure a tuple with each side retaining its correct type. It's typing as a union of fst
and snd
instead.
Repro:
import { Tuple } from 'purify-ts/Tuple';
const [a, b] = Tuple(1, 'hello');
// Expected: [number, string]
// Actual: [string | number, string | number]
Hi ๐ love this repo. I'm writing this issue more to start a conversation and ask if this style of import structure was considered:
import { Either, Just, MaybeAsync } from 'purify';
...
vs the current pattern of:
import { Either } from 'purify-ts/Either';
import { Just } from 'purify-ts/Maybe';
import { MaybeAsync } from 'purify-ts/MaybeAsync';
...
It feels a little counter to other npm libraries that have homogeneous import structures, but if there's a good reason for the current style i'll shut up
Hi,
Thanks for your work on this library! I'm really enjoying using it.
Is there a way to get multiple errors from a decode result?
Example
// codec
const BlogPostCodec = Codec.interface({
name: string,
authorId: number
});
I'm trying to build an object like this:
{
name: "must be a string",
authorId: "must be a number"
}
I can't seem to get this right. Why is isRight()
not type guarding the data
object?
Take a look here, please:
Thanks.
Much like Maybe#toList()
, but always returns fixed array, e.g.:
const [ left = 0, right ] = Right(42).toList();
const [ err, data ] = getEither().toList();
if (err == null) {
console.info(data);
}
How does it looks like?
Hey! So, it's only been a few hours but I love this project.
One pain point for me so far has been how to handle a tuple of maybes. For example, imagine a function that returns an object with optional properties. I'm interested in two of these properties. What I'd ideally like in my mind is to be able to form a sort of MaybeTuple.fromNullable, or something along those lines. Instead I ended up with the following which works but feels quite unergonomic:
maybeThing
.chain(thing => thing.propA && thing.propB
? Just(Tuple(thing.propA, thing.propB))
: Nothing,
)
.ifJust(([first, second]) => {
doSomething(first, second);
});
In my mind I'm thinking something like this for the chain:
.chain(thing => MaybeTuple.fromNullable(thing.propA, thing.propB))
Which would return MaybeTuple<typeof thing.propA, typeof thing.propB>
, basically a melding of Maybe and Tuple. It's only Just
if both arguments pass the not nullable check.
Thoughts?
Hello!
There is a common use case when you need to compose EitherAsync
with synchronous Either
function:
function validate(doc: Document): Either<Error, ValidatedDocument> {
// ...
}
function fetchDocument(id: string): EitherAsync<Error, Document> {
// ...
}
fetchDocument("123").chain(doc => liftEither(validate(doc)))
It would be nice to have combinator which will lift Either
itself. cats library has subflatMap method for this purpose.
Possible API:
subchain<R2>(f: (value: R) => Either<L, R2>): EitherAsync<L, R2>
// ========
fetchDocument("123").subchain(doc => validate(doc))
What do you think?
Targeting down to es5
right now is indeed allowing old system to benefit from the module, in additional to that, could be great to have es2018
without downlevelIteration
flavor placed so that lots of modern users could opt-in as needed?
I've tested a little bit on local with small changing in build steps, wondering would that be OK to proceed with PR to make the cut?
For some reason, I cannot chain left to right and left to left (type errors).
const r1 = () => right(1);
const l1 = () => left('woops');
const l2 = () => left(/woo/);
const rl = () => Math.random() > 0.5 ? left('woo') : right('yay'); // could be the case.
r1().chain(l1); // error
l1().chain(l2); // error
Any thoughts?
Line 85 in 6cc9c9b
Now the function chain is useless if I want to return Either in chain, it more likely will have a different type of error
Example
this.phoneService.findPhone(createPhoneDto).chain(phone => {
return this.passwordService.checkPhonePassword(phone, createPhoneDto);
})
this.phoneService.findPhone() // => EitherAsync<PhoneNotFound, Phone>
this.passwordService.checkPhonePassword() // => EitherAsync<NoPhonePassword, boolean>
I would like that would be after the call cahin
EitherAsync<PhoneNotFound | NoPhonePassword, boolean>
type
Or tell me how can I do it differently
Hi,
The function below, which is expected to return a Maybe cannot return Nothing. I am getting the error: Type 'Card' is not assignable to type 'never'
Both returning Maybe.empty() and returning Nothing result with the same error.
What am I doing wrong here?
import { Maybe, Just, Nothing } from "../../functions/src/common/model/utility"
import { Card } from "../../functions/src/common/model/authentication";
export function getCardFromLocaleStorage(): Maybe<Card> {
var cardJSON = localStorage.getItem("guestCard") || localStorage.getItem("userCard");
if (!cardJSON) return Maybe.empty();
try {
return Just(JSON.parse(cardJSON) as Card)
}
catch (err) {
return Nothing
}
}
The maybe implementation does not satisfy the monad laws because the implementation is not parametric in the wrapped value. Example tests that should pass:
test('parametricity', () => {
expect(Just(undefined).isNothing()).toEqual(false)
expect(Just(null).isNothing()).toEqual(false)
})
test('Left Id', () => {
expect(Just(undefined).chain(x => Just(typeof x === 'undefined'))).toEqual((x => Just(typeof x === 'undefined'))(undefined))
expect(Just(null).chain(x => Just(typeof x === 'object'))).toEqual((x => Just(typeof x === 'object'))(null))
})
I want to declare interface with optional field.
So I usenullable
with bit change: add undefinedType
function nullable<T>(codec: Codec<T>): Codec<T | null | undefined> {
return oneOf([codec, nullType, undefinedType]);
}
then declare interface and object value.
const MyObject = Codec.interface({ id: string, name: nullable(string) });
type MyObject = GetInterface<typeof MyObject>;
// Property 'name' is missing in type '{ id: string; }' but required in type '{ id: string; name: string | null | undefined; }'.
// const obj1: MyObject = { id: "1" };
const obj2: MyObject = { id: "1", name: undefined };
obj1
causes compile error because field of name must have a value even if type of value is undefined so, I have to declare undefined explicitly ๐ข
Another problem,
MyObject
Codec can't decode obj1
as MyObject
.
// {
// __value: 'Problem with property "name": it does not exist in received object {"id":"1"}'
// }
console.log(MyObject.decode({ id: "1" }));
This behavior is clumsy to use.
(example usecase: api request validation with optional parameter.)
Hi there,
great project! I hardly go a day without using purify-ts. A thousand thanks for your work to date!
I'm opening this issue as a place to discuss the addition of a chainLeft
function for the Either
type, which is like chain
but for the rejection branch. I'd define the function as follows
Either a b ~> (a -> Either c b) -> Either c b
so that Right
values are kept the same and Left
values are chained on the provided function. While chainLeft
doesn't appear in the fantasy-land spec, I've found a function like this helpful in "resetting" a computation back on to the successful path, in certain situations from which an error can be recovered (but without losing the surrounding type as with orDefault
).
This signature represents one take, I guess after thinking about it there could be other signatures, such as
Either a b ~> (a -> Either c d) -> Either c b|d
in the event that the function provided to chainLeft
returns a different type than b
.
Nothing concrete for now obviously, just wanted to touch base.
Cheers
Hi,
Is there a way to extract value from the Either? (Both Left/Right)
I know should better not extract it but my server returns me an either, and now I need to return a simple json. how can I do the transformation? Through closure? That sounds very ugly
While sitting on public transport this morning I was informed about your awesome project by a German news site for techies.
Checking out the documentation I stumbled upon some minor typos.
Gonna submit a PR soon...
Does chain accept an async function? I didn't understand if my code is broken or that chain does not accept async function..
The code that I want to improve via chain is as below. As far as I understand, I should use chain instead of map to eliminate the extract() function all over the code
var either = await service.form.getFormInstance(req.body.formInstanceId)
return either
.map(async (doc: Doc<FormInstance>) => {
var either = await getTokenObjectValidForFormInstance(req.body.token, doc.data)
return either
.map((tokenObject: OrganizationTokenObject | RoomTokenObject) => {
return OK(res, bZetOK<FormInstance>(doc.data))
}).extract() // ???
})
.mapLeft((err: ZetError) => ERROR(res, err))
.extract()
The following issues popped up after upgrading from 0.12.x to 0.13.x:
Property 'extract' in type 'Nothing' is not assignable to the same property in base type 'Maybe'.
Type '() => this extends AlwaysJust ? never : undefined' is not assignable to type '() => this extends AlwaysJust ? never : never'.
Type 'this extends AlwaysJust ? never : undefined' is not assignable to type 'this extends AlwaysJust ? never : never'.
Type 'undefined' is not assignable to type 'never'.
Property 'extractNullable' in type 'Nothing' is not assignable to the same property in base type 'Maybe'.
Type '() => this extends AlwaysJust ? never : null' is not assignable to type '() => this extends AlwaysJust ? never : never'.
Type 'this extends AlwaysJust ? never : null' is not assignable to type 'this extends AlwaysJust ? never : never'.
Type 'null' is not assignable to type 'never'.
Is this package removed from npm registry? ๐ค
Hi, I'm trying out your library, and playing around with it I'm having difficulty debugging with logs. Take for example an Either
of value Right({foo: 'bar', bar: { baz: 'baz'}})
. When this is stringified it comes out as Right([object Object])
which is quite useless. It would be nice if it can rather JSON stringify that. For example currently I'm doing
Object.prototype.toString = function() {
return JSON.stringify(this, null, 2)
}
console.log(`${Right({foo: 'bar', bar: { baz: 'baz'}})}`)
which prints:
Right({
"foo": "bar",
"bar": {
"baz": "baz"
}
})
I don't want to override Object.prototype.toString so it would be awesome if this library can be improved in this way.
Currently it crashes when starting yarn develop
(which is just an alias to gatsby develop
) with the new version of Gatsby.
To get started you need to:
cd
into the site
folder in this projectyarn develop
When I use an EitherAsync where is mapLeft?
First, thank you first this great library !
When we want to check the existence of something before going on with the program, we can use MaybeAsync
and then .toEitherAsync
I found myself with the opposite case. I want to check that something is not here before going on. Consider the following :
type Person = { id: number };
function findPerson(id: number): MaybeAsync<Person>;
function save(person: Person): EitherAsync<Error, Person>;
const create = (person: Person): EitherAsync<Error, Person> => {
const maybeAsyncPerson = findPerson(person.id);
return maybeAsyncPerson
.toEitherAsyncSwaped(new Error("Cannot create Person which already exists"))
.chain(() => save(person));
};
I would be happy to make a PR with this new method toEitherAsyncSwaped
if you find it interesting.
We can also imagine a swap
on eitherAsync
, which would be more generic. Then the previous case would become :
const create = (person: Person): EitherAsync<Error, Person> => {
const maybeAsyncPerson = findPerson(person.id);
return maybeAsyncPerson
.toEitherAsync(new Error("Cannot create Person which already exists"))
.swap()
.chain(() => save(person));
}
Maybe there is a different way that I did not think of...
Prior to using this library, my primary means of simulating a Maybe ADT was to make something Nullable (T | null
), and treat null as Nothing. In doing so, the following code was possible:
const genNullableNum = () => Math.random() > .5 ? Math.random() : null;
const numberPlease = (a: number) => {};
function example() {
const nullableNum = genNullableNum();
if (nullableNum === null) return;
numberPlease(nullableNum);
}
This code uses early termination to narrow the type of nullableNum
to number
; if you remove that line, the compiler will correctly complain that you're potentially passing null
to numberPlease
.
This doesn't appear to be possible with this library, though it's possible I'm misusing it. If you attempt to run similar code using maybeNum.isNothing()
in the if statement, the type isn't narrowed. Equally, if you run if (maybeNum.isJust())
the type isn't narrowed inside the if statement either.
The type does narrow if you run Maybe.ifJust
or Maybe.isNothing
, however due to that using a callback that doesn't permit the early termination pattern I prefer.
Cheers!
It would be nice if some methods would be generic on the argument type. For example currently Maybe<T>::alt
takes and returns a Maybe<T>
. This makes it impossible to do something like just(5).alt(just('different type'))
. It would be nice if alt
accepted a Maybe<U>
and returned a Maybe<T | U>
(or even this extends AlwaysJust ? Maybe<T> : Maybe<T | U>
). I think this change can be made without breaking backwards compatibility, as T | T
is equal to T
.
- const just = <T>(value: T): Just<T> => new Just(value)
+ const just = <T>(value: T): Maybe<T> => new Just(value)
ref:
Line 452 in 4de6d71
Hi, I just try to plug the library into my project. I have 2 problems which I may wrongly approach but I could not find a solution within the library.
my code below is an example for presenting the problems:
having:
async function getDocsByQuery<T>(query: Query): Promise<Util.Either<Util.ZetError, Doc<T>[]>> {..}
async function add(collectionRef: CollectionReference, data: any): Promise<Util.Either<Util.ZetError, string>> {..}
---
export const newUser = async function(organizationId: string, username: string, password: string, active: boolean,
displayName?: string, email?: string, tags?: Util.ZetTags, roles?: RoleSet): Promise<Util.Either<Util.ZetError, string>> {
var user = bOrganizationUserData(username, password, active, displayName, email, tags, roles);
var collection = db
.collection("organization")
.doc(organizationId)
.collection("user");
var query = collection
.where("username", "==", username)
.where("deleted", "==", false)
.limit(1)
var eitherDocs = await getDocsByQuery<OrganizationUserData>(query)
if(eitherDocs.isRight()){
// the first query resulted successfuly, I check how many docs returned. if no document, I can add user
if(eitherDocs.unsafeCoerce().length==0){
var eitherAdd = await add(collection, user)
return eitherAdd
}
else{
// there is already a doc with same username, return error
return Util.Left(bZetError("Username already exists"))
}
}
else return Util.Left(eitherDocs.leftOrDefault(Util.bZetError("")))
/// I need to transform the either to comply with the return type
};
What I try to do, is
Either#filter
is missing in compare to Maybe#filter
?fantasy-land/filter
on both above as well.Line 100 in 27bfd9e
Will resolve to a promise.
EDIT:
alt
(other: EitherAsync<L, R>): EitherAsync<L, R>
orDefault
(defaultValue: R): Promise<R>
leftOrDefault
(defaultValue: L): Promise<L>
Hi,
I would like to talk about adding the Either
type a swap
function.
If this is a Left
, then return the left value in Right
or vice versa: e.g.
const sample: Either<string, number> = getEither();
const swapSample: Either<number, string>= sample.swap();
Specifically, it is the correct behavior that an error always occurs, and it is illegal when no error occurs.
const sample: Maybe<string> = getMaybe();
// In some cases, it is reversed from option. But, there is no `toLeft` instance method for the `Maybe` type.
sample.toLeft(1) // Either<string, number>
// `swap` method can reduce redundancy to some extent.
sample.toEither(1).swap() // Either<string, number>
I know that we can use Either#either()
for an alternative.
const sample: Either<string, number> = getEither();
const swapSample: Either<number, string> = sample.either(v => Right(v), v => Left(v));
const sample: Maybe<string> = getMaybe();
sample.toEither(1).either(v => Right(v), v => Left(v))
Hi! Thanks for this fantastic library, it's really helpful and the API is very easy to work with. I was wondering if there are any plans to be able to wrap promises in the api?
In the case of listToMaybe
, maybe make a NonEmpty
ADT instead?
If yes, please post your company name, logo and whatever other resources you want to share (articles, repos etc) and I'll add them to the landing page of the website ๐
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.