calmm-js / karet.util

Utilities for working with Karet
MIT License
20 stars 5 forks source link

Make U.seq to lift functions? #9

Closed rikutiira closed 6 years ago

rikutiira commented 6 years ago

Would it make sense for U.seq to lift functions automatically like U.pipe?

Right now this fails:

const value = U.atom(1)

const obsSeq = U.seq(value,
    U.defaultTo(0),
    U.gt(U.__, 0)
) // false

While this works:

const value = U.atom(1)

const obsSeq = U.seq(value,
    U.defaultTo(0),
    U.lift(U.gt(U.__, 0))
) // observable

Alternatively if it's possible, maybe lifted Ramda functions could return lifted functions when used with placeholder values.

polytypic commented 6 years ago

I think that having lifted Ramda functions return lifted functions when used with placeholders would be the better option.

rikutiira commented 6 years ago

Since all Ramda functions (I think?) are curried and have no optional arguments, it would probably be possible to simply define their arity in karet.util and have them return lifted functions until all arguments are given?

I think this would also solve the issue of having to use placeholder variables in cases like:

Before: U.find(U.propEq('foo', 'bar', U.__), array) After: U.find(U.propEq('foo', 'bar'), array)

I know those situations have caused some confusion with a few people using karet.

I can make a PR about this if this solution sounds good to you.

polytypic commented 6 years ago

Yes, I think it is likely possible to lift Ramda functions more intelligently.

I'm going to look into this soon. I think it is time to introduce separate modules/libraries for lifted Partial.Lenses functions and Ramda functions.

polytypic commented 6 years ago

I started a repo: kefir.ramda. It is very experimental at this point. Please try it out and open issues there or ask on Gitter.

rikutiira commented 6 years ago

Thanks! I will take the library into use in our project and report if everything seems to work nicely.

polytypic commented 6 years ago

Currently, in Karet Util, a lifted function is a curried function that returns a function when applied to one or more observables (but not enough parameters to saturate full arity). In Kefir Ramda, however, a lifted function is a curried function that returns an observable (instead of a function) when applied to one or more observables.

For example, with U as Karet Util and K as Kefir Ramda,

U.map(U.add(Kefir.constant(1)), [1, 2, 3])

returns an array of observables, but

K.map(K.add(Kefir.constant(1), [1, 2, 3])

returns an observable array, which is what you usually want. The key difference is that K.add(Kefir.constant(1)) returns an observable that produces the function R.add(1).

So, Kefir Ramda functions probably work better in Ramda style point-free expressions. However, with current U.seq they don't work, because U.seq expects functions rather than observables. So, a lifted version of U.seq might be useful after all.

polytypic commented 6 years ago

There is now also Kefir Partial Lenses. Also U.view in Karet Util now lifts the lens as a template and Karet Util now has U.thru and U.thruPartial that are lifted versions of U.seq and U.seqPartial.

One thing I notice is that there are still cases where one has to specifically perform e.g. a non-lifted R.compose or R.pipe. So, just importing

import * as L from 'kefir.partial.lenses'
import * as R from 'kefir.ramda'

everywhere does unfortunately not "just work". I'll have to go through various code sample and see if there is a simple set of advice that can be given.

rikutiira commented 6 years ago

We have used both R and U in our codebase, because often we have used synchronous versions of R functions in our callbacks, for example. I'm not sure if it has been the best approach because you have to have pretty good understanding of when to use synchronous R or asynchronous U.

If I recall right, there were some special cases of lifted Ramda functions where they returned observables despite not having observable arguments. Maybe I remember wrong though, it could just be that we decided it's simpler to treat U functions as something which can be potentially asynchronous due to it also having always-observable versions of Kefir methods.

polytypic commented 6 years ago

you have to have pretty good understanding of when to use synchronous R or asynchronous U

Yes, I agree. I'm kind of hoping that it might be possible to make it so that one could use lifted R and L except in a few special cases that could be documented, but I'll have to see how it turns out.

lifted Ramda functions where they returned observables despite not having observable arguments

That shouldn't happen.

polytypic commented 6 years ago

FYI, I've been converting samples at CodeSandbox to use the new lifted libraries (Ramda, Lenses, Validation) and I'm cautiously optimistic. Most of the samples have become slightly less noisy, because having the lifted libraries at hand one can eliminate some uses of U.lift or K.

The new liftRec (lift "recursive") function in Kefir Combines has the nice property that it preserves the currying / optional parameter behaviour of the original function. For example, currently Karet Util exports a stringify function, which is U.lift(JSON.stringify), but it has the nasty gotcha that U.lift curries the function, so one has to call it with all three arguments unlike the original JSON.stringify. If it were liftRec(JSON.stringify), the behaviour of the original JSON.stringify would preserved and you could call it with just one argument.

My current plan is to add libraries or separately imported modules for the lifted Math and JSON objects (and other lifted standard functions I'm forgetting right now) currently in Karet Util and deprecate the existing lifted functions in Karet Util (including the lifted Ramda functions).

polytypic commented 6 years ago

Resolved in latest version.