Reputation: 284
I come across this use-case at work all the time and I feel like there must be a way to compose fullName in a point-free way without defining cat as a parameter:
const cats = [
{ name: 'Bob', lastName: 'Ross' },
{ name: 'Frank', lastName: 'Langella' },
];
// this bugs me
const fullName = cat => add(
prop('name', cat),
prop('lastName', cat)
);
const isEqual = curry((a, b) => a === b);
const isBobRoss = compose(isEqual('BobRoss'), fullName);
edit: some of the helpers above in case it helps understand the challenge
/**
* compose :: ((a -> b), (b -> c), ..., (y -> z)) -> a -> z
*/
const compose = (...fns) => (...args) =>
fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];
/**
* curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c
*/
function curry(fn) {
const arity = fn.length;
return function $curry(...args) {
if (args.length < arity) {
return $curry.bind(null, ...args);
}
return fn.call(null, ...args);
};
/**
* add :: a -> b -> a + b
*/
const add = curry((x, y) => x + y)
/**
* prop :: String -> Object -> a
*/
const prop = curry((p, obj) => obj[p])
}
Upvotes: 2
Views: 122
Reputation: 1429
(Initially I decline to commit to a specific functional programming library for JS/TS and work in general concepts)
Notice that your curred prop
function takes a key and returns a reader monad. This will be useful.
Assuming type Obj = {name:string,lastName:string}
, then your curried prop fn is (key:'name'|'lastName') => Reader<Obj,string>
You can use a sequence
type function to combine two reader monads into a single one, as such:
const getNameParts = sequence(prop('name'), prop('lastName')) // Reader<Obj, [string,string]>
Then you can map the [string,string] to be a single string like in your add function
const add = as => as.reduce((acc, item) => acc + item)
So if you can lift add
into your reader monad's computational context (here using a proposed `map: ((a:A)=>B)=>(fa:F<A>)=>F<B>), then compose these operations:
const buildNameFromObject = compose(getNameParts, map(add)) // Reader<Obj, string>
There we have it.
const personsName = buildNameFromObject(someObject) // string
fp-ts
is a library that provides everything I just mentioned, and using that library (with a few function name changes to align with fp-ts's vocabulary),
import { reader, map } from 'fp-ts/lib/Reader'
import { sequenceT } from 'fp-ts/lib/Apply'
import { pipe } from 'fp-ts/lib/pipeable'
const getNameParts = sequenceT(reader)(prop('name'), prop('lastName'))
const buildNameFromObject = pipe(getNameParts, map(add)) // Reader<Obj, string>, which is a fancy way of writing (o:Obj)=>string
buildNameFromObject({name:'Foo', lastName: 'Bar'}) // 'FooBar'
Your "fullName" function (buildNameFromObject
) is now point free.
Upvotes: 2
Reputation: 664620
This is no exactly function composition, but sure you can write a helper function for it. Ramda does know it as converge
, here's a simplified (unary) version of it:
const converge = (fn, wraps) => arg => fn(...wraps.map(wrap => wrap(arg)));
cost fullName = converge(add, [prop('name'), prop('lastName')]);
Upvotes: 2