Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript and Flow type definitions #55

Open
polytypic opened this issue Feb 10, 2017 · 20 comments
Open

TypeScript and Flow type definitions #55

polytypic opened this issue Feb 10, 2017 · 20 comments

Comments

@polytypic
Copy link
Member

polytypic commented Feb 10, 2017

Would be nice to have TypeScript and Flow type definitions for partial lenses.

@polytypic polytypic changed the title TypeScript type definitions TypeScript and Flow type definitions Feb 17, 2017
@gcanti
Copy link

gcanti commented Feb 17, 2017

@polytypic FYI ts-static-land (an early attempt) is superseded by https://github.com/gcanti/fp-ts which goal would be to be compatible with both fantasy-land and static-land (hopefully!)

@KiaraGrouwstra
Copy link

I made a bit of progress on TS typings in my fork. Not that I imagine typing FP libs to be easy (I ended up as the maintainer of the Ramda typings, so know some of the challenges involved, some yet unresolved), and admittedly I'm still not as fluent in this library yet either. Then again, working on typings will hopefully help there. :)

@KiaraGrouwstra
Copy link

I'm getting the impression the main challenge here is just... TypeScript's type language can't handle the reduce type logic needed to type R.path, which means that typing this library for proper type inference becomes... problematic, since that kind of logic is the essential cornerstone here.

So monocle.ts has managed to keep things typed, but essentially at the cost of sacrificing expressivity for verbosity.

This makes me wonder: is there any way we could have our pie and eat it too, in any language (probably meaning Haskell/Purescript, if any at all)? I glanced over their lens libraries listed in the readme here for a bit, but I'm not so familiar with them, and haven't quite managed to tell if any enables expressive constructs as in this library while also being typed without getting a bunch more verbose.

Probably a bit off-topic here, but I'd be pretty curious!

@gcanti
Copy link

gcanti commented Mar 31, 2017

@tycho01 the first example in the README of monocle-ts can be reduced from

import { Lens, Optional } from 'monocle-ts'

const company = Lens.fromProp<Employee, 'company'>('company')
const address = Lens.fromProp<Company, 'address'>('address')
const street = Lens.fromProp<Address, 'street'>('street')
const name = Lens.fromProp<Street, 'name'>('name')

const lens = company
  .compose(address)
  .compose(street)
  .compose(name)

to something like this

import { Lens, Optional } from 'monocle-ts'

const lens2 = Lens.fromPath<Employee, 'company', 'address', 'street', 'name'>(['company', 'address', 'street', 'name'])

where

// more overloadings here...
function fromPath<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3]>(path: [K1, K2, K3, K4]): Lens<T, T[K1][K2][K3][K4]>
function fromPath<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(path: [K1, K2, K3]): Lens<T, T[K1][K2][K3]>
function fromPath<T, K1 extends keyof T, K2 extends keyof T[K1]>(path: [K1, K2]): Lens<T, T[K1][K2]>
function fromPath<T, K1 extends keyof T>(path: [K1]): Lens<T, T[K1]>
function fromPath(path: Array<any>) {
  const lens = Lens.fromProp<any, any>(path[0])
  return path.slice(1).reduce((acc, prop) => acc.compose(Lens.fromProp<any, any>(prop)), lens)
}

Perhaps the same technique can be used to add typings to R.path and / or this library?

@KiaraGrouwstra
Copy link

If sticking with strings as well (navigating structures of objects, no arrays), that works. When trying to extend this to numbers to navigate structures including tuples though, this breaks down.

// string, ok
declare function path<T, K1 extends keyof T>(path: [K1], v: T): T[K1]
let a = path(['a'], { a: 1 }) // number
// number, fails
declare function path<T, K1 extends number>(path: [K1], v: T): T[K1] // Type 'K1' cannot be used to index type 'T'.
let b = path([0], [1])

The R.path type has indeed used overloading to achieve a type signature that fell short of handling tuples, but for a path length n, this meant 2^n extra overloads, and predictably, at a certain number of overloads this just trashed performance to the point applications would no longer compile due to the addition of this type definition. And that's why I'm frustrated with TS for not dealing with type inference in a better (reduce-enabled) way, and am willing to turn to other languages if needed.

That said, for R.path forcing users to manually type results could be a solution (expected in/out types), but this sorta means the type definitions are failing their inference job.

From a quick glance of the current library, the situation would get more complex than just string vs. number though: arrays here could contain any optic (to be created using any of the dozens of functions in this lib or just string / number), and essentially you'd end up having to manually type most of it anyway... hence I'm now wondering if there's some Haskell version of this that could get inference right, or if Haskell and JS really just have their own respective strengths.

@gcanti
Copy link

gcanti commented Mar 31, 2017

fell short of handling tuples

FWIW you can see a tuple [A, B] as an object with '0' and '1' props though, so no need for K1 extends number

type A = {
  a: [number, [string, boolean]]
}

const lens3 = fromPath<A, 'a', '1', '0'>(['a', '1', '0'])

console.log(lens3.get({ a: [1, ['s', true]] })) // => 's'

AFAIK all solutions in typed languages are based on

@KiaraGrouwstra
Copy link

I did try that idea with inference, though that seemed to have turned out less well somehow:

declare function path<T, K1 extends keyof T>(path: [K1], v: T): T[K1]
let b = path(['0'], [1])
// intended result: 1
// actual result: error
// Argument of type '["0"]' is not assignable to parameter of type '["length" | "toString" | "toLocaleString" | "push" | "pop" | "concat" | "join" | "reverse" | "shi...'.
// Type '"0"' is not assignable to type '"length" | "toString" | "toLocaleString" | "push" | "pop" | "concat" | "join" | "reverse" | "shif...'.

That last example on the Monocle site looks pretty good. Just realized much of the folds from this library are also in ekmett/lens, among other things... I feel like this now.

@gcanti
Copy link

gcanti commented Mar 31, 2017

I guess [1] is inferred as Array<number> instead of [number], this works

declare function path<T, K1 extends keyof T>(path: [K1], v: T): T[K1]
let b = path(['0'], [1] as [number])

@KiaraGrouwstra
Copy link

Cool, thanks!

Trying path length 2:

declare function path<T, K1 extends keyof T, K2 extends keyof T[K1]>(path: [K1, K2], v: T): T[K1][K2]
let b2 = path(['a', '0'], { a: [1] })
// Argument of type '["a", string]' is not assignable to parameter of type '["a", never]'.
// Type 'string' is not assignable to type 'never'.
let b3 = path(['a', '0'], { a: [1] } as { a: [number] })
// Argument of type '["a", string]' is not assignable to parameter of type '["a", never]'.
// Type 'string' is not assignable to type 'never'.

Okay TypeScript, I don't think we were meant to work out...

@KiaraGrouwstra
Copy link

I made a proposal that'd enable the reduce-like logic needed for typing functions from this repo (as well as similar Ramda ones); currently not looking positive though...

@KiaraGrouwstra
Copy link

I've recently been exploring type recursion as a way to better address cases like R.path, which would also solve the basis for the current repo, see here.
Typing this repo with working inference is still my main challenge for TS, a step up still from Ramda. That said, I think we only need two TS upgrades before we could do just about anything...

@siassaj
Copy link

siassaj commented Jun 4, 2019

@tycho01 Really interesting stuff, is there any new news about typings?

@KiaraGrouwstra
Copy link

@siassaj TS can operate on anything other than generic function types now, given Concat and pattern matching using conditional types. so while R.path may have progressed a bit, for this library I'm still not sure if there's much hope yet...

@siassaj
Copy link

siassaj commented Jun 5, 2019

thank you so much for the instant response.

It's a shame, I've run into some typing limitations in my own work too... the really higher order stuff gets so complex so fast...

I'm going to inspect your fork and see if I can't brute force some typings as needed to get most of the safety I need.

@KiaraGrouwstra
Copy link

@siassaj if you come up with something nice let us know! 🗡️

@rjhilgefort
Copy link

I have taken a look at monocle-ts as well as shades and nothing comes close to this library. Has anyone found a good, typed alternative to partial.lenses?

@polytypic
Copy link
Member Author

polytypic commented May 1, 2020

Have you looked at optics-ts? It is a new optics library being developed specifically for TypeScript by @akheron.

As another possibility, I recently came up with a new (to me) approach to optics while working in a project developed in C# and made a public repo in F# to demonstrate the approach: NetOptics. The significance of the approach is that it doesn't require an encoding of higher-kinded types, ad hoc polymorphism, or even the use of interfaces to manipulate (higher-kinded) abstractions and yields a relatively concise and efficient implementation. Of course, the approach has its limitations (no (higher-kinded) applicative traversal), but if one is using a language (like TypeScript) that doesn't support higher-kinded types, then the more limited and simpler typings might be a good trade-off (C# 7.3, in particular, doesn't even provide parameterized type aliases, so dealing with complex parameterized types in C# would just be incredibly cumbersome). I have no plans to make a TypeScript library using the approach, but if someone is interested in giving it a try, I can probably help to get started.

@rjhilgefort
Copy link

Thank you for the reply @polytypic! I'm going to check out if optics-ts is a good enough substitute and then might approach you for the latter idea if needed. Cheers!

@akheron
Copy link

akheron commented May 2, 2020

optics-ts is still pretty new, but the type construction it uses seems to allow typing many optics that other TypeScript libraries cannot type properly. Don't hesitate to create issues or pull requests for missing functionality!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants