Fluture icon indicating copy to clipboard operation
Fluture copied to clipboard

Add branch awareness to TypeScript types (strict)

Open Avaq opened this issue 4 years ago • 3 comments

This is an alternative to #435 that doesn't include auto-expansion of generic types.


A quick recap of auto-expansion:

Unstrict: With auto-expansion (behaviour on master for chain and chainRej)

  1. chain (_ => reject ('b')) (reject ('a')) gets type Future<string, never>: The types align.
  2. chain (_ => reject ('a')) (reject (1)) gets type Future<number | string, never>: The rejection type was expanded.
  3. chain (_ => reject ('a')) (resolve (1)) gets type Future<string, number>: The types were expanded with never, which leaves them intact.

Strict: Without auto-expansion (behaviour on master for everything else)

  1. chain (_ => reject ('b')) (reject ('a')) gets type Future<string, never>: The types align.
  2. chain (_ => reject ('a')) (reject (1)) produces a type error: The types don't align. This is desirable behaviour, because it is more true to the behaviour in Haskell, where the rejection branch cannot change type because the monad instance is created for the unary constructor that is left after providing the first type variable.
  3. chain (_ => reject ('a')) (resolve (1)) produces a type error: TypeScript doesn't want to assign never to another type. For chain and chainRej this was fixed in #374 by making those functions unstrict.

In #435, I had given up on the strict approach (following the trend created in #374), because I thought there's no way to get the strict behaviour while retaining the ability to chain (or otherwise combine) futures where the branch types don't technically conflict because the variable on one of the sides was untouched (like in examples 3 above).

The goal of this pull request is to:

  • Reimplement chain and chainRej using the strict approach, getting rid of the problem highlighted by example 2.
  • Add branch awareness to all combinators. I realized this is enough to get rid of problem 3 from the strict approach, because we're no longer asking TypeScript to assign never to anything: Our overloads take care of any occurrence of never with special treatment.

This approach brings new problems, however. In particular, TypeScript loses type information when these functions are called indirectly, such as by pipe. I think this happens because TypeScript cannot infer types from overloaded functions (https://github.com/microsoft/TypeScript/issues/32418#issuecomment-516892451).

So where this approach works perfectly for statements like and (resolve (42)) (reject ('abc')), it produces some unknown type variables when refactoring that to reject ('abc') .pipe (and (resolve (42))), because and (resolve (42)) creates an overloaded lambda, from which pipe cannot infer any type information.

If anyone can help with this new problem, it'll be much appreciated!

Avaq avatar Aug 06 '20 12:08 Avaq

Codecov Report

Merging #438 (6e1afab) into master (42e2892) will not change coverage. The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff            @@
##            master      #438   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           46        46           
  Lines         1987      1987           
=========================================
  Hits          1987      1987           

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 42e2892...6e1afab. Read the comment docs.

codecov[bot] avatar Aug 06 '20 12:08 codecov[bot]

A solution that was found for #457 has the potential of unblocking this PR. In particular, it is possible to overload functions in such a way that TypeScript chooses the correct overload based on the "direction" that function arguments are supplied in:

https://github.com/fluture-js/Fluture/blob/8eef14aa3366cf2447be6b62f199de8b3c535967/index.d.ts#L144-L148

I believe that this allows me to fix the issue mentioned at the bottom of my PR comment. :tada:

Avaq avatar Jan 07 '21 22:01 Avaq

I rebased this on #457 and pushed a commit that shows typings for alt that are adjusted to usage with pipe and encode branching. This approach seems promising. :)

Avaq avatar Jan 08 '21 13:01 Avaq