Ramda - partially applied function depends on full application - javascript

I have a situation where the value that I want to add depends on a future value i.e.
let metadata = {videoId: 123, likes: 400};
let addSubtitles = R.assoc('subtitles', R.__, R.__);
Here addSubtitles is a partially applied function. However, the second argument of addSubtitles is supposed to be the subtitles, but the subtitles themselves depend on the third argument i.e. metadata.videoId. To be called as follows:
addSubtitles(metadata); //but with another argument perhaps?
const subtitles = async getSubtitles(videoId) => await apiCall(videoId);
Any way to solve this in a functional manner? It seems possible if I were to bound the context of the future third argument, but unsure of how to go about doing this.
Please let me know if there is any extra information needed to answer.
This is a working solution, though I was looking to use Ramda to make this work:
const addSubs = async function(el) {¬
const subtitles = await writeCaptions(el.videoId);¬
return R.assoc('subtitles', subtitles, el);¬
};

First let me back up a moment to note that you don't need to use R._ for later values in the signature. Almost every function in the Ramda libary is auto-curried for you, so you can call with all arguments or just some subset to preload data. R._ is only used for holding a space for future calls.
So what you want to do with currying is just keep adding parameters one at a time until you get a complete function ready to call. When the order is wrong you can use R.flip or R._ (depending on context) to reach the values you are ready to fill, and come back to the values you don't know yet.
So from your description, it sounds like your concern is that getSubtitles(metadata) is a network call that returns a promise. This example will proceed under that assumption. Here I think R.flip will be more expressive for you than R._.
const metadata = { videoId: 123, likes: 400 }
const addSubtitles = R.flip(R.assoc('subtitles'))
// Now you have a curried function that takes 2 arguments, first the metadata, and then the subtitles.
const withSubtitlesPromise = getSubtitles(metadata)
// Here we drill down a little further by adding the metadata, and then
// pass it as the callback to .then, which will pass in the final param
// once it resolves
.then(addSubtitles(metadata)
withSubtitlesPromise.then(console.log)
You could definitely capture all of this logic in a single function that takes in metadata and returns a promise for subtitled data. For good measure, we'll also pass in getSubtitles as a depenedency, for easier testing and weaker coupling. Now it's trivial to swap out another function for retrieving subtitle data. In this case, the use of R._ makes the code a little cleaner, so we'll switch it up.
// Define all the logic in a single easy function
const makeAddSubtitles = getSubtitles => metadata =>
getSubtitles(metadata).then(R.assoc('subtitles', R._, metadata))
// Then push in the dependency
const addSubtitles = makeAddSubtitles(getSubtitles)
// Use it
const vid123 = { videoId: 123, likes: 400 }
const vid123WithSubsPromise = addSubtitles(vid123)

Related

Ramda point-free problems with transform functions (map, etc)

I have this function, which is basically mapping request parameters and query parameters to a SQL statement:
function selectSingleResult(params, { order, limit, offset, fields, runset_id, txOffset, txFactor }) {
const transformer = R.when(R.equals('values'), R.compose(knex.raw.bind(knex), applyConversion({ txOffset, txFactor })))
const newFields = R.map(transformer , fields);
knex('myTable').select(...newFields) // etc...
Ideally, I would like to be able to define transformer outside the function so the function can just become:
const mapFields = R.map(transformer);
function selectSingleResult(params, { order, limit, offset, fields, runset_id, txOffset, txFactor }) {
knex('myTable').select(...mapFields(fields)) // etc...
The issue being that the applyConversion function needs arguments given to selectSingleResult.
This is a common issue I have with transform functions to map. They often require parameters other than the values being mapped over. In such cases, how can I write in point-free, or at least more testable style and not end up nesting functions so much?
It feels as though you're trying to go point-free in code where it doesn't make much sense. But there's a contrasting notion of destructuring a large set of fields from the second parameter that doesn't seem necessary, so this seems mostly confused. (It might actually be necessary: perhaps you're using order, limit, offset, and runset_id in the remainder of your main function.)
I think you can accomplish almost what you're asking by just introducing one more layer of calls, with something like this:
const transformer = (query) =>
R .when (R .equals ('values'), field => knex .raw (applyConversion (query) (field)))
const mapFields = R.pipe (transformer, map)
const selectSingleResult = (params, query) => {
knex ('myTable') .select (... mapFields (query) (query .fields))
// etc...
}
I name the second parameter to your main function query; it's a guess, and if that's confusing, replace all the instances of query with foo or with something meaningful to you.
Note that mapFields could also be written as const mapFields = (query) => R.map (transformer (query)), but the version above seems simpler. Also note that I simplified transformer a bit. I simply don't see any reason to try to go point-free, when you can't get all the way there. And trying to mix point-free code with OO constructs such as knex .raw just seems to confuse things.
If I read the code correctly, we also might rewrite transformer like this:
const transformer = (query) => (field) =>
field == 'values' ? knex .raw (applyConversion (query) ('values')) : field
but I can't decide if that is an improvement or not.

How does this code pass an argument to a callback that takes no arguments?

I'm sifting through some code and I don't get how this piece of code is able to pass arguments to a callback that takes no arguments.
We start with this object:
const oracleEndpoints = {
pancake: () => fetchPancake(),
lps: () => fetchLP(endpoints.lps), <-- endpoints.lps is an API url
};
And this map:
const oracleToIds = new Map()
oracleToIds.set("lps", ["png-snob-avax", "png-png-avax"])
oracleToIds.set("pancake", ["WBNB", "BREW"])
Now this is the part I don't get:
const promises = oracleToIds.keys().map(key => oracleEndpoints[key](oracleToIds.get(key)));
oracleEndpoints[key] is an anonymous callback (() => fetchPancake() or () => fetchLP(endpoints.lps)) and oracleToIds.get(key) is an array of strings.
What's going on in this expression oracleEndpoints[key](oracleToIds.get(key)) then where an unparametised callback is passed an array?
How is it able to do this?
I'd double check if the array argument is actually being used by anything meaningful. It could easily be a mistake in the code caused by confusion of the writer, given the high level of nesting mashed into a single line of the const promises code that you showed.
This would silently cause an error that would only manifest in application usage -- no exception is thrown in JavaScript if you are passing extra params to a function.

Function composition early return

I am composing a series of function but I wonder what's the best way to achieve what I want first this is how I compose:
const composeP = (...fns) => fns.reduce((f, g) => async (...args) => f(await g(...args)))
const profileSummary = profileData => composeP(createProfileSummary, getMKAProfile)(profileData)
now what I want is to do a check and if profileData which is my input is a certain string e.g. "cantbesearched" I want to return a value immediately to "profileSummary" variable instead of executing previous functions...
so is it possible to create a "filterWords" function, put it in front of the composition like this:
const profileSummary = profileData => composeP(createProfileSummary, getMKAProfile, filterWords)(profileData)
and if certain words are detected, skip previous functions on the left then return a value.
Is it possible to create a "filterWords" function to be put it in front of the composition?
No. What you want to do is branching, which is not possible with function composition.
What you can do is compose functions that work on a type which provides an error path, like Maybe or Either. (You can also consider exceptions as a builtin error path for every type, so just throw).
Oh wait, you already are doing that! You didn't write a plain function composition compose, you wrote composeP which uses monadic Kleisli composition - and promises do have such an error path:
function filterWords(word) {
return word == "cantbesearched"
? Promise.reject(new Error("filtered for your safety"))
: Promise.resolve(word);
}

Rx: Auto-refreshing list(array)

I want to implement observable of array that auto-refresh itself. I do it like this:
const list$ = Observable.create(function(observer) {
getList(list => observer.next(threads);
}));
const liveList$ = Observable.interval(2000).switchMapTo(list$);
When I do subscribe to the liveList$ stream I get values only after n ms. as expected.
The question is how can I get values after first call of getList immediately and each next call with interval?
P.S. I've tried to $list.switchMapTo($liveList).subscribe(console.log) but nothing is changed in behaviour of the chain
Use the timer operator instead. It can be passed an initial delay, as well as a period:
Observable.timer(0, 2000).switchMapTo(list$);

Invoke a Function with the Correct Parameters from an Object in JavaScript

Let's say I have an object that looks like this:
{
'apple': 'nice',
'banana': 'decent',
'cherry': 'yuck',
}
and I have these two methods:
function eatItems(cherry, apple) { }
function throwItem(banana) { }
My two questions:
Is it possible for me to invoke eatItem and send the arguments in the correct order? Maybe something like:
eatItems.call(this, {'cherry': cherry, 'apple': apple});
What if I don't know what arguments eatItems receives, can I dynamically look up the names of the arguments for a function so I can know the order that I need to throw them in?
There's a way, indeed, and it involves calling toString on a function:
var source = eatItems.toString();
// => "function eatItems(cherry, apple) { }"
The next step is to parse the string you've got to get the names of the arguments:
var args = source.substring(source.indexOf("(") + 1, source.indexOf(")")),
argNames = /\S/.test(args) ? args.split(/\s*,\s*/) : [];
A few caveats:
This solution has been kept quite simple. It doesn't handle comments in the function definition.
Not every browser can correctly convert a function to a string (the PS3 browser comes to my mind), but they're a really small minority anyway.
I haven't tested it, but there may be some performance issues on slower machines and/or older browsers with large functions.
And, overall, this solution is more like an exercise. I wouldn't recommend taking this pattern in Javascript. Don't forget that some functions handle a variable number of arguments, and you won't find them listed in their definition. Rethink your code, and find a better way.
If I understand correctly you want extract the argument names from the function, and inject data from an object based on those names. This can be accomplished by converting the function to a string, extracting the arguments, and applying the function with those arguments:
function inject(data, f) {
var args = f.toString()
.match(/function\s*?\((.+?)\)/)
.pop()
.split(',')
.map(function(a){return data[a.trim()]})
return function() {
return f.apply(this, args)
}
}
var data = {
apple: 'nice',
banana: 'decent',
cherry: 'yuck',
}
var eat = inject(data, function(cherry, apple) {
console.log(cherry, apple)
})
eat() //=> yuck, nice
The obvious problem with this approach is that it is highly dependent on the variable names, so when you minify your code, the variables will get mangled and the function will stop working. This is a known problem in AngularJS, which uses something similar for their dependency injection.
This is often an XY problem, or an anti-pattern at the very least.

Categories