TsMonad icon indicating copy to clipboard operation
TsMonad copied to clipboard

Be `strictNullChecks` compatibility

Open JohannesHome opened this issue 7 years ago • 11 comments

Currently, when using this library with the flag enabled we have the following issue:

const object: { value: number | null } = { value: null };

const result: number | null = Maybe.maybe(object)
            .map(obj => obj.value)
            .valueOr(10);

As you can see result keeps the type null, since the current typings reflect this. It would be nice if the type for map could reflect the removal of null. Example:

    map: <U>(f: (t: T) => U | null) => Maybe<U>;

I saw that in https://github.com/cbowdon/TsMonad/issues/31 this point has been mentioned and fixed for Maybe.maybe but not in general.

Currently there is not a single optional or maybe typescript library out there that is fully strictNullChecks compatible.

My question, is this planned?

JohannesHome avatar Jun 19 '18 15:06 JohannesHome

Working on an alternative library with pretty similar functionality and modern TS support:

https://github.com/patrickmichalina/typescript-monads

patrickmichalina avatar Aug 08 '18 03:08 patrickmichalina

@patrickmichalina yeah, we can all build our own library, just kinda defeats the purpose :)

Hmm, looking at your code, I think it also only handles undefined not null. Also I think it might swallow empty strings, 0, and false and there seems no way to map the wrapped value, as map looks like a flatMap.

JohannesHome avatar Aug 08 '18 08:08 JohannesHome

If people kept up on their repos, it wouldn’t be necessary :)

I’ll check out your comments and adjust accordingly. Thanks.

patrickmichalina avatar Aug 08 '18 13:08 patrickmichalina

@patrickmichalina @JohannesHome Sorry for bothering you with same repeated idea. I recently built https://github.com/Ailrun/typed-f and I believe these packages satisfy all usage with strictNullChecks. However, this package is still in 0.* version so I need more helps from forks. If you are willing to, please take a look :smile:

Ailrun avatar Aug 28 '18 19:08 Ailrun

@Ailrun looks good, but it it isn't type safe. Simple example is already the signature for Maybe.from(value?: null): Nothing<any>;, it should infer the type base on the input parameter instead of using any.

This let's me do stuff like this:

    const test: string | undefined = undefined;
    const from = Maybe.from(test);
    const result: string = from.valueOr(1);

without a compile error. Also the docs could be more comprehensive.

And yeah I know I can force the type with Maybe<string>, but it's nicer if the monad infers the type 😄 (less work for me)

JohannesHome avatar Aug 29 '18 09:08 JohannesHome

@JohannesHome Oh, thank you for feedback. However, even with following type,

Maybe.from<T>(value?: null): Nothing<T>;

Following code will give you wrong type.

const test: string | undefined = undefined;
const from = Maybe.from(test); // This will give you Nothing<{}>

Though it will give you an error for following statement.

const result: string = from.valueOr(1); // Complain about that `{}` cannot be assigned to `string`.

Ailrun avatar Aug 29 '18 14:08 Ailrun

@JohannesHome Following code will give you better explanation.

function x(test: string | undefined) {
  const from = Maybe.from(test);
  return from.valueOr(1); // Give you an error!
}

Your code works since TS think your test as just undefined, and that's why following also works in TS.

const test: string | undefined = undefined;
const x: number | undefined = test;

Ailrun avatar Aug 29 '18 14:08 Ailrun

@Ailrun this is a bit out of scope but your library could infer the type based on the actual value like so Maybe.from<T>(value?: T): Nothing<T> which is the same as Maybe.from<T>(value: T | undefined): Nothing<T>, this allows the compiler to infer the type correctly and making it typesafe without having to specifying the return type (something I like to forget).

JohannesHome avatar Aug 29 '18 14:08 JohannesHome

@JohannesHome ~It will give you Nothing<undefined>, not Nothing<string>. To be clear, this is because of TS inference, not because of some typing of the library. See https://www.typescriptlang.org/play/index.html#src=interface%20Test%3CT%3E%20%7B%0D%0A%20%20v%3A%20T%3B%0D%0A%7D%0D%0Adeclare%20function%20x%3CT%3E(v%3A%20T%20%7C%20undefined)%3A%20Test%3CT%3E%3B%0D%0Aconst%20t%3A%20string%20%7C%20undefined%20%3D%20undefined%3B%0D%0Aconst%20y%20%3D%20x(t)%3B%0D%0Aconst%20test1%3A%20Test%3Cstring%3E%20%3D%20y%3B%0D%0Aconst%20test2%3A%20Test%3Cundefined%3E%20%3D%20y%3B%0D%0A~

Ailrun avatar Aug 29 '18 15:08 Ailrun

@JohannesHome These are all because TS knows what the value of test is in compile time, since you declare const test: string | undefined = undefined. As I said, TS consider this test as undefined, so you can assign this value to any type that accepts undefined. You already loose your string type.

Ailrun avatar Aug 29 '18 15:08 Ailrun

~However, if it's still unclear to you, could you open an issue on my repo? Explaining things related with my code in this repo feels somewhat weird for me :sweat_smile:~ https://github.com/Ailrun/typed-f/issues/46

Ailrun avatar Aug 29 '18 15:08 Ailrun