jamesmcnamara/shades

[Question] max 6 arguments

JesseZomer opened this issue ยท 3 comments

Hello ๐Ÿ‘‹,

I've been trying this pretty amazing library. One thing I've run into is that it you can only have max 6 arguments.
We have a pretty nested view (calendar with week, days, appointments..) and sometimes I need 7 or 8 arguments

example: mod('view', 'weeks' , matching(currentWeek), 'days', matching(currentDay), 'appointments', matching(appWithId(..)).
Is there anyway to combine lenses like

const currentDay = mod('view', 'weeks' , matching(currentWeek), 'days', matching(currentDay));
mod(currentDay, 'appointments')

or something like that? Or is there another way to best handle this kind of problem?

Also if I want to remove an element from an array is this the way to do it:

   mod('days',  matching(currentDay)) ( (day) => ({...day, appointments: day.appointments.filter(...)})) (data)

or is there someway I can go to the appointments in the lens and use a filter fn like:

   mod('days',  matching(currentDay), 'appointments') (filter(not({ id: appointmentId })) (data) 

Thanks for the cool library ๐Ÿ‘

Hey glad you are enjoying it!

The reason for the six argument limit is that we programmatically generate overrides for each of the methods based on all the different combinations of input types (string, number, traversal and virtual lens). With four possibilities for each position, every additional parameter adds an exponential amount of new overrides. With 6, we have over 50,000 lines of typings. Above that, the TypeScript server really slows down.

As a workaround, you can just nest a second call to mod as your transformer function, i.e.:

mod('view', 'weeks' , matching(currentWeek), 'days', matching(currentDay), 'appointments')(mod(matching(appWithId(..)), 'more', 'keys')(myTransformer))

Splitting this up a little bit, we can look at the types and see how they fit together.

type ModFn = (updater: (appts: Appointments[]) => Appointments[]) => (cal: Calendar) => Calendar
const modFn: ModFn = mod('view', 'weeks' , matching(currentWeek), 'days', matching(currentDay), 'appointments')

Curried functions are a bit weird, but modFn is expecting an Appointment[] to Appointment[] function and then will give you back an updater for your parent type (which I just called Calendar). So now we want a function that goes from Appointment[] to Appointment[]:

type Updater = (appts: Appointment[]) => Appointment[]
const updater: Updater = mod(matching(appWithId(..)), 'more', 'keys')(myTransformer))

updater here is applied to an actual business logic transformer function that works on whatever is the result of taking an Appointment and extracting more and then keys. The result is function that goes from Appointment[] to Appointment[], which is just what we wanted above.

type Final = (cal: Calendar) => Calendar
const final: Final = modFn(updater)

mod applied to a series of lenses is just expecting a function that goes from the focus of the lens S to S. Separately, mod applied to a series of lenses and a transformer function from T to T (where T is the focus of the new lens) produces a function that goes from S to S. So mod applied twice is a perfect candidate for mod applied once. Pretty cool.

There is the possibility that with nesting you might confuse TS, but you can always add an explicit argument signature to the inner function and it should figure it out:

mod('view', 'weeks' , matching(currentWeek), 'days', matching(currentDay), 'appointments')((appointments: Appointment[]) => mod(matching(appWithId(..)), 'more', 'keys')(myTransformer)(appointments))

As for the filtering question, yes I think that's a very reasonable way to do it.

I noted this elsewhere too, but I think Variadic Tuple Types coming in TS4 should help, or maybe be able to eliminate all the overloading.

@ptpaterson I would be very curious to see an example that used variadic tuple types to create a type for get or mod. From my impression, it handles the case where you need to preserve an arbitrary list of input types and transform it into an output type (tail, concat in their examples), but not cases where the function actually relies on the relationship between each subsequent type (like compose or get).

For instance, the get function needs to know "If you passed me in three strings, I'm going to give you a function which takes an object that includes that nested path". I'm not sure how to express that relationship (parameter n impacts parameter n + 1) generically.

But maybe I'm just missing it.