Fluture
Fluture copied to clipboard
Add branch awareness to TypeScript types (strict)
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
)
-
chain (_ => reject ('b')) (reject ('a'))
gets typeFuture<string, never>
: The types align. -
chain (_ => reject ('a')) (reject (1))
gets typeFuture<number | string, never>
: The rejection type was expanded. -
chain (_ => reject ('a')) (resolve (1))
gets typeFuture<string, number>
: The types were expanded withnever
, which leaves them intact.
Strict: Without auto-expansion (behaviour on master
for everything else)
-
chain (_ => reject ('b')) (reject ('a'))
gets typeFuture<string, never>
: The types align. -
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. -
chain (_ => reject ('a')) (resolve (1))
produces a type error: TypeScript doesn't want to assignnever
to another type. Forchain
andchainRej
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
andchainRej
using the strict approach, getting rid of the problem highlighted by example2
. - 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 assignnever
to anything: Our overloads take care of any occurrence ofnever
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!
Codecov Report
Merging #438 (6e1afab) into master (42e2892) will not change coverage. The diff coverage is
n/a
.
@@ 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.
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:
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. :)