Lets say I have a lot of entities in global store
Apples
Shops
Juices
If I would like to have function getAppleJuicePrice, I can do it in 2-3 ways
Via parameters
function getAppleJuicePrice(apples, juices) {
return apples * juices; // Some computing
}
Via getState, no parameters
function getAppleJuicePrice() {
return (dispatch, getState) => {
const {apples, juices} = getState();
return apples * juices; // Some computing
}
}
Via getState and parameters
function getAppleJuicePrice(applesParam, juicesParam){
return (dispatch, getState) => {
const apples = applesParam || getState().apples;
const juices = juicesParam || getState().juices;
return apples * juices; // Some computing
}
}
*In case 2,3 I need to dispatch the function
Could you please give me your advise about
A) Functions 1,2,3 - Is it ok to do it in this way? Or some of them are better?
B) Architecture (I mean about fact that we are having a lot entities in the global store so we can even create functions which depend on them)
I'd strongly argue for example #1.
This function is pure, descriptive, and doesn't rely on outside knowledge. Assuming you've already got access to state, it's almost always better to derive the values you're after rather than directly requesting state.
If that's not possible, then factor out the data retrieval to a second function, then pass your values into the first. That way you've got a clear separation of concerns, your methods are easy to understand, you're not relying on side-effects (the presence of state values), and both methods should be easily testable.
Hell, you could go one further and refactor function 1 to be a generic utility for comparing/combining the prices of whatever, not just Apples and Juice.
const stateObject = getState() // At top of whatever scope
// Generic, no side-effects
function processPrice (x, y) {
return x * y; // Whatever processing
}
// Specific, injected dependency on state
function getAppleJuicePrice (state) {
const { apples, juice } = state;
return processPrice(apples, juice);
}
// Explicit value assignment
const appleJuicePrice = getAppleJuicePrice(stateObject)
Just a quick comment on store architecture: Always aim to keep it as simple, flat, and data-related as possible. Avoid compromising the data structure of your store simply for convenience of value computation. Anything you can derive locally (and easily) you should do so. The store isn't the place for complex value mutations.
(*preemptive this is not a hard rule, just a guideline)
Related
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.
I'm trying to write a function composition that partially applies an argument at each step and ends up calling a curried two-argument function.
There is a set of example functions to compose. I removed the calculations that there are supposed to do as they are not relevant to the problem but let's assume that every argument is required.
const getDayLimit = () => {
return 10
}
const getIpCount = ip => dayLimit => {
return 99
}
const getIp = (deviceId, headerIp) => {
// TODO: use Result monad to handle errors
if (!deviceId && !headerIp) {
throw new Error('Ip not provided')
}
return deviceId || headerIp
}
And the composition attempt:
const validateIp = R.compose(
f => f(getDayLimit()),
getIpCount,
getIp
)
validateIp(1, 2)
In the first step, getIp received two values and based on them returns an ip that is then partially applied to getIpCount, now the composition return a function that expects the dayLimit argument that needs to be computed first.
The plain way of doing this could be: f => f(getAccountLimit()).
I'd like to remove such function creation f => f... and pass it point-free.
Here's a helper function that solves this but is not handling all cases such as passing arguments to the result function:
const applyResult = result => f => R.compose(f, result)()
then I could do:
const result = R.compose(
applyResult(getDayLimit),
getIpCount,
getIp
)
It seems too hacky for me and not substantial for my further use. I'd rather avoid writing my own helper function for this kind of problem.
Is there a functional way of computing arguments before partially applying them to a function? It seems to be a pretty common case in my mind, though perhaps I'm not thinking about the problem correctly.
Is my thinking incorrect about this problem and function composition?
What is a good approach to handling such a case with a function with two parameters in a composition?
Can this case of partially applying function arguments with each step be handled in a function composition?
Thank you!
I think I would use a continuation which, as I understand it, represents a computation that has been interrupted:
const cont = x => f => f(x);
With a continuation, you get x before f. Instead of doing f(x) you do cont(x)(f) which behind the scene just does f(x) for you.
At the time of composing the functions together you already know the value of x which is getDayLimit(), you just don't know the value of f yet which is known only when result is applied to the first two initial parameters.
So here's what I'd do:
const result = R.compose( cont(getDayLimit())
, getIpCount
, getIp);
Is there a functional way of computing arguments before partially applying them to a function?
I would simply note that you apply a function to a value (not the other way round)
I have a series of (pure) functions responsible for expanding tokens held in an object graph.
I won't go into the details as they aren't important, but they are something like:
expandData => expandDataNodes => expandDataNode => expandDataNodeItem
I have everything working fine, but I now have a new requirement which means I now need user defined configuration to be available in expandDataNodeItem. Obviously in the OO world this is a trivial problem to solve just make the config available on the DataExpander class via a getter and pull it out when needed, but I'm interested in the options for handling this situation when using a functional paradigm.
I know I can add a param to each of these functions and pass down either the option or a function for accessing the option, but this results in lots of functions which have no interest in the option getting it added to their signatures which feels like it really muddies things.
The best option I can think of at the moment is to wrap all these functions in another function and use closure to make the options available to all functions within, but is there a better way?
With these few details I would try another approach: you may refactor your code so the functionality that requires some configuration may come already injected in a partially-applied function. That is, your current function should be a high-order function.
Check the following sample on which I demonstrate that approach:
const f = ( { config1, configN } ) => x => config1 + x
const g = f => x => f ( x )
const settings = {
config1: 1
}
// _f would be the function that you would provide
// when you need to do certain operation based on
// who knows what configuration
const _f = f ( settings )
const y = g ( _f ) ( 2 )
console.log( y )
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);
}
Background:
Composition is putting two functions together to form a third function where the output of one function is the input of the other.
No matter how much I look at this I struggle with how to read it. In particular why the compose() return => (a) => captures the 121.2121212 in local scope. Also I struggle with how final fn f(g(a)) would look with all the values/fn present w/o the use of variables.
Question: Does anyone have any techniques or diagrams for quickly reading examples like this; how can I mentally debug and follow the function flow?
Reference:
const compose = (f, g) => (a) => f(g(a)) // Definition
const floorAndToString = compose((val) => val.toString(), Math.floor) // Usage
floorAndToString(121.212121) // '121'
As mentioned by T.J. Crowder, it often helps rewriting arrow functions as regular functions. So the function:
const compose = (f, g) => (a) => f(g(a))
Can be rewritten as:
function compose (f, g) {
return function (a) {
return f(g(a));
}
}
Now it is perhaps more obvious what's going on. So now let's rewrite the other parts:
const floorAndToString = compose((val) => val.toString(), Math.floor)
Can be rewritten as:
function convertToString (val) { return val.toString() };
const floorAndToString = compose(convertToString, Math.floor);
Now it may be more obvious that the compose function will return the function:
// remember that we pass `convertToString` as `f`
// and `Math.floor` as `g`:
function (a) {
return convertToString(Math.floor(a));
}
So it's obvious that the function floorAndToString simply returns the result of convertToString(Math.floor(a)). There is nothing special about compose that captures 121.2121212 because it doesn't. Instead it creates a function where 121.2121212 can be passed as an argument to convertToString(Math.floor(a)).
It might help to look at the Wikipedia article for function composition. But I think your problem is not really related to function composition but to the arrow notation in general.
Maybe it helps to look at a simpler example first:
const addOne = (x) => x + 1
const addN = (n) => (x) => x + n
const addSeven = addN(7)
The last line produces a new function that adds seven to the input (x) => x + 7. You can think of the parameter tuples between the arrows as being filled from left to right when values are provided (and the variables to the right are bound to these values). As long as you don't provide all parameters, you will obtain a new function.
You can also provide all parameters like this:
addN(5)(3) // yields 8
Note that addN can be seen as taking two parameters but in separate bracket pairs. The arrows between the brackets in the definition kind of allow you to omit parameters to the right and obtain a function with fewer parameters with the left ones being already fixed.
Let's look at an alternative definition of compose:
const weirdCompose = (f, g, a) => f(g(a))
It should be clear how it works, but the problem is that you cannot use this to compose two functions without evaluating the result of the composition with the value a right away. By separating the parameters into two groups you can partially apply the function and only provide f and g in a first step.
To understand this better, I suggest you also have a look at the concept of currying