KiaraGrouwstra / typical

playground for type-level primitives in TypeScript
MIT License
173 stars 5 forks source link

Flow port #13

Open KiaraGrouwstra opened 6 years ago

KiaraGrouwstra commented 6 years ago

Flow offers things like $Call which have been a bottleneck for me so far. I should finish up my flow branch to get things up to parity. I'm not actually good at Flow so help welcome!

edit: I should check type-at-pos to find the inferred types.

goodmind commented 5 years ago

@tycho01 I have found a way to iterate for unions

type A = 'a' | 'b' | 'c'
type D = {[a: A]: any}

declare var d: $ObjMap<D, () => string>

d.a
d.b
d.c

const s = d.unknown 
// this is still string but access errors
/*
10: const s = d.unknown
              ^ string `unknown` [1] is incompatible with enum [2].
References:
10: const s = d.unknown
              ^ [1]
2: type D = {[a: A]: any}
                 ^ [2]
*/
KiaraGrouwstra commented 5 years ago

yeah $ObjMap seems great :sweat_smile:, definitely seems one of the main features of Flow over TS (next to $Call, $TupleMap)

goodmind commented 5 years ago

@tycho01 I thought conditional types is basically $Call?

KiaraGrouwstra commented 5 years ago

@goodmind: you're not far off! it has two use-cases:

This is why I was kinda sad they gave us conditional types instead of $Call, as it means the second use-case is likely still gonna remain pretty far off. And since those are pretty vital for FP libs like Ramda, that's why I'd feel more optimistic about Flow at this point.

goodmind commented 5 years ago

@tycho01 hey! I implemented $Required in Flow and it allows to implement Pick in userland ($Rest doesn't remove optional properties because it is unsound in runtime, but we are at type-level :P) https://github.com/facebook/flow/pull/7571

type RestFixed<T1, T2> = $Rest<T1, $Required<T2>>
type Pick<T1, T2> = RestFixed<
  T1,
  $ObjMap<RestFixed<T1, T2>, <V>(V) => any>
>;

declare var picked: Pick<{| a?: number, b?: string, c: string |}, {| a: any |}>;

picked.a; // void | number
picked.b; // error
picked.c; // error
goodmind commented 5 years ago

@tycho01 with latest generic function inference in TypeScript it seems like Flow now far off from typing Ramda :(

KiaraGrouwstra commented 5 years ago

that looks pretty cool! :D

with latest generic function inference in TypeScript it seems like Flow now far off from typing Ramda :(

Do you mean stuff like R.pipe(R.identity) that got better in recent TS isn't as good yet in Flow?

Like, how do you see the major differences in expressiveness in TS and Flow now? Is it mostly about some of the TS additions like infer?

edit: P.S. if you have a few types in a repo I can link it from the readme here, or alternatively I can give you push access over here if you want.

goodmind commented 5 years ago

@tycho01 infer can still be done with $Call but new generic inference https://github.com/Microsoft/TypeScript/pull/30193, https://github.com/Microsoft/TypeScript/pull/30215 can't, but Flow now is more active in community than before

Also, I don't have typelevel stuff repository, because I'm not a fun of too much complex typelevel stuff after I've seen what styled-components does in TS image 💯

https://github.com/tycho01/typical/issues/27 also this lol

I often write simple flow code, and if something can't be typed I fork library

KiaraGrouwstra commented 5 years ago

Yeah, type-level coding sucks, admittedly. In an ideal world, I hope we could just get down the hard stuff down once, after which end-users can just automagically enjoy the benefits without having to understand freaky type magic.

I'm shocked about the styled-components example as well! Not because I haven't seen complex types before, obviously, but I find it surprising that a library at their level would need to make types this convoluted. I'd wonder if I'm missing something or if they really need it. My idea had been perhaps most libraries could just use well-typed variables/functions instead of having to write their own custom type magic. Definitely curious as to what limitations forced them into this!

goodmind commented 5 years ago

@tycho01 I was playing around OCaml and wrote this 🔥

image

Any examples I can implement with this?

KiaraGrouwstra commented 5 years ago

hahaha, I'm quite confused about what happened in your example, but a type-level reduce is pretty cool! that's definitely among the remaining challenges in TS. I listed some potential use-cases for this based on Ramda here, though part may be solved already.

goodmind commented 5 years ago

@tycho01 I just hardcoded reducing into object

| DefT (_, trust, ArrT arrtype), MapTypeT (reason_op, Reduce funt, tout) ->
      let props = match arrtype with
      | ArrayAT (_, opt) -> 
        (match opt with
          | Some ts -> 
            Core_list.fold_left ts ~f:(fun acc value ->
              match value with
                | DefT (_, _, SingletonStrT str) -> 
                  let t = EvalT (funt, TypeDestructorT (unknown_use, reason_op, CallType [value]), mk_id ()) in
                  SMap.add str (Field (None, t, Positive)) acc
                | _ -> acc
            ) ~init:SMap.empty
          | None -> SMap.empty)
      | TupleAT (_, ts) -> 
        Core_list.fold_left ts ~f:(fun acc value -> 
          match value with
            | DefT (_, _, SingletonStrT str) -> 
              let t = EvalT (funt, TypeDestructorT (unknown_use, reason_op, CallType [value]), mk_id ()) in
              SMap.add str (Field (None, t, Positive)) acc
            | _ -> acc
        ) ~init:SMap.empty
      | ROArrayAT (_) -> SMap.empty in
      let props_tmap = Context.make_property_map cx props in
      let t =
        let reason = replace_reason_const RObjectType reason_op in
        let proto_t = ObjProtoT reason in
        let call_t = None in
        let dict_t = None in
        let flags = {
          exact = true;
          sealed = Sealed;
          frozen = false;
        } in
        DefT (reason, trust, ObjT {flags; dict_t; proto_t; props_tmap; call_t;})
        (* Obj_type.mk_with_proto cx reason ~frozen:true ~sealed:true ~exact:true ~props proto *)
      in
      rec_flow_t cx trace (t, tout)

, perhaps this is more limiting to implement something like path.

Not sure how would initial value behave if we can't subtract and add types?

// build object
var s: $Reduce<["a", "b"], <Acc, K>(Acc, K) => {
   ...Acc,
   [K]: K // sorry no computed props in Flow 
}, {||}> // failed example, should be special cased then

// build union without initial value
var s: $Reduce<["a", "b"], <Acc, K>(Acc, K) => Acc | K> // "a" | "b"

And this quickly becomes more complicated to implement

TypeScript in general allows more userland type magic, because all type utilities work well together but this isn't like this in Flow, $Values breaks this $Reduce example if you use switch on resulting union

goodmind commented 5 years ago

Working example for second reduce attempt, without initial value, because no idea how to do optional parameters

image

KiaraGrouwstra commented 5 years ago

hm. I thought you showed me a path Flow implementation before, using compose stuff to achieve the reduce-like functionality...

goodmind commented 5 years ago

@tycho01, but Compose implementation doesn't work if you export function

KiaraGrouwstra commented 5 years ago

really? I'm probably missing some context but that sounds kinda lame. :/

goodmind commented 5 years ago

Yeah, all exports should be annotated properly for performance reasons, and you can't do this with compose(...spread). It would've worked if there was explicit $ComposeTuple, but $Compose is variadic

goodmind commented 5 years ago

I managed to do initial value, but didn't found how to resolve generics for third argument :/ So it is pretty useless now IMG_20190324_221919_895

Aka flow would crash if you pass generic to third argument

KiaraGrouwstra commented 5 years ago

ah, I see, thanks! if Flow has issues as well, then I guess we're probably boned for the time being. :joy:

goodmind commented 5 years ago

There is also discord server with Flow maintainers, usually they help with contributing. I managed to add BigInt parsing and $Required type only with their help https://discordapp.com/invite/8ezwRUK

KiaraGrouwstra commented 5 years ago

whoa, that's pretty nice! It's felt like the TS team has been fairly swamped with stuff, though at least recently Ramda got some attention there as it was the only typing repo convoluted enough to break on their recent changes. :joy: