[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.