Is there a better way to compose this in a point-free way? - functional-programming

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])
}

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')]);

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

Related

Flow type a generic function that groups any type that has at least an ID

I'm trying to type with Flow a function that maps As to Bs, where the only restrictions are:
B contains an A
A has at least an id property which is a string
Apart from that, A can be any object, and, in this situation, B is well known.
I want to type the function with a generic/polymorphic type that so the type checker knows that you will get an array of objects containing the A and B that matches.
My attempt below does not give me any type error, but I don't think it is correct either.
Will love to understand how to properly type this so you can get the most guarantees.
type B = {A: {id: string}}
const BContainsA = (id: string) => (b: B) =>
b.A.id === id
type MapResult<T> = {
AsWithBs: Array<{ A: T, B: B }>,
AsWithoutBs: string[],
}
const mapAsToBs = <T>(
As: { ...T, id: string }[],
Bs: B[]
): MapResult<T> => {
return As.reduce(
(result, a) => {
const b = Bs.find(BContainsA(a.id))
if (!b) {
result.AsWithoutBs.push(a.id)
return result
}
result.AsWithBs.push({ A: a, B: b })
return result
},
{ AsWithBs: [], AsWithoutBs: [] }
)
}
mapAsToBs([{pos:2,id: '1'},{pos:1,id: '11'}],[{A:{id: '1'}}])
Seems that all I had to do was to add a constraint to the generic type like this:
const mapAsToBs = <T:{id: string}>(
As: T[],
Bs: B[]
): MapResult<T> => {
}
It is indeed documented, but the syntax is so unintuitive and the explanation is so short, that I would have never guessed it just by reading it.
You can check how it works as expected here

How to replace combinators with Future?

I have a function which returns Future. It accepts another function which accepts one argument and returns Future. Second function can be implemented as combinators chain passed into first function. It looks like this:
use bb8::{Pool, RunError};
use bb8_postgres::PostgresConnectionManager;
use tokio_postgres::{error::Error, Client, NoTls};
#[derive(Clone)]
pub struct DataManager(Pool<PostgresConnectionManager<NoTls>>);
impl DataManager {
pub fn new(pool: Pool<PostgresConnectionManager<NoTls>>) -> Self {
Self(pool)
}
pub fn create_user(
&self,
reg_req: UserRequest,
) -> impl Future<Item = User, Error = RunError<Error>> {
let sql = "long and awesome sql";
let query = move |mut conn: Client| { // function which accepts one argument and returns Future
conn.prepare(sql).then(move |r| match r {
Ok(select) => {
let f = conn
.query(&select, &[&reg_req.email, &reg_req.password])
.collect()
.map(|mut rows| {
let row = rows.remove(0);
row.into()
})
.then(move |r| match r {
Ok(v) => Ok((v, conn)),
Err(e) => Err((e, conn)),
});
Either::A(f)
}
Err(e) => Either::B(future::err((e, conn))),
})
};
self.0.run(query) // function which returns Future and accepts another function
}
}
But I want to write code of create_user as a struct implementing Future.
struct UserCreator(Pool<PostgresConnectionManager<NoTls>>, UserRequest);
impl UserCreator {
fn new(pool: Pool<PostgresConnectionManager<NoTls>>, reg_req: UserRequest) -> Self {
Self(pool, reg_req)
}
}
How to implement Future for this struct that works as first function? Please help me with an example.
Now I tried to make it like this, but nothing is computed and execution always blocks.
impl Future for UserCreator {
type Item = User;
type Error = RunError<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// Code which which works like `DataManager.create_user`
let sql = "long and awesome sql";
let reg_req = &self.1;
let query = move |mut conn: Client| {
conn.prepare(sql).then(move |r| match r {
Ok(select) => {
let f = conn
.query(&select, &[&reg_req.email, &reg_req.password])
.collect()
.map(|mut rows| {
let row = rows.remove(0);
row.into()
})
.then(move |r| match r {
Ok(v) => Ok((v, conn)),
Err(e) => Err((e, conn)),
});
Either::A(f)
}
Err(e) => Either::B(future::err((e, conn))),
})
};
self.0.run(query).poll()
}
}

Is there a method to override prop name in object with Ramda?

I have this object{thing1: {}, thing2: {}} is there a method to override just a prop name like {thing1: {}, thing3not2: {}}
Not sure if there's a quicker/easier way, but you could combine assoc to add the new key and dissoc to remove the old key:
const { curry, assoc, dissoc } = R;
const renameProp = curry(
(oldName, newName, obj) =>
dissoc(oldName, assoc(newName, obj[oldName], obj))
);
const myTransformation = renameProp("thing2", "thing3not2");
const myResult = myTransformation( {thing1: {}, thing2: {} } );
console.log(JSON.stringify(myResult, null, 4));
<script src="https://cdn.jsdelivr.net/npm/ramda#0.25.0/dist/ramda.min.js"></script>
The Ramda "cookbook" contains a renameKeys function
https://github.com/ramda/ramda/wiki/Cookbook#rename-keys-of-an-object
Below copied from there:
/**
* Creates a new object with the own properties of the provided object, but the
* keys renamed according to the keysMap object as `{oldKey: newKey}`.
* When some key is not found in the keysMap, then it's passed as-is.
*
* Keep in mind that in the case of keys conflict is behaviour undefined and
* the result may vary between various JS engines!
*
* #sig {a: b} -> {a: *} -> {b: *}
*/
const renameKeys = R.curry((keysMap, obj) =>
R.reduce((acc, key) => R.assoc(keysMap[key] || key, obj[key], acc), {}, R.keys(obj))
);
And calling it
renameKeys({thing2: 'thing3not2'}, {thing1: {}, thing2: {}})
=> {"thing1": {}, "thing3not2": {}}

FlowType errors using Object.entries

So, I have the following code, but flow errors keep popping up. I've tried to cast the Object.entries, but just won't work - others things to. Any insight?
type Fields = {
name: string,
func: (*) => boolean
};
type S = {
key1: Fields,
bill: Fields
}
var a: S = {
key1: {name: 'mary', func: (str) => str === 'mary'},
bill: {name: 'bill', func: (str) => str === 'bill'}
}
var c = Object
.entries(a)
.map(([key, obj]) => obj.func(key) ? obj : false)
.filter(f => f)
.reduce((acc, c) => {
return 'something here'
}, {});
I've left some things off, but the slow is the same. Flow is reading that entries as a return Tuple Type. I've tried all sorts of things, but instead of mudding things up, I left it untouched.
I can't seem to annotate the destructured items here ([key, obj]), get tuple errors...
Any assistance on getting that code assigned to var c, to work with annotations etc..?
The errors I get:
Cannot call method on mixed type (from obj.func)
Cannot assign value in Tuple etc..
The error is accurate. Object.entries has the type
entries(object: any): Array<[string, mixed]>;
It has no way to know what the type of the second item in the tuple will be. That means your code
.map(([key, obj]) => obj.func(key) ? obj : false)
would need to do
.map(([key, obj]) => {
if (typeof obj.func !== 'function') throw new Error();
return obj.func(key) ? obj : false;
})
so that flow knows that it is guaranteed to be a function.
Alternatively, you could change your data structure to use a type where the second item in the tuple has a guaranteed type, like Map, e.g.
type Fields = {
name: string,
func: (string) => boolean
};
type S = Map<string, Fields>;
var a: S = new Map([
['key1', {name: 'mary', func: (str) => str === 'mary'}],
['bill', {name: 'bill', func: (str) => str === 'bill'}],
]);
var c = Array.from(a, ([key, obj]) => obj.func(key) ? obj : false)
.filter(f => f)
.reduce((acc, c) => {
return 'something here'
}, {});
In my case, I had:
let objectsByName : { [string] : MyObjectType } = {}; //simple map
...
objectsByName[object.name] = object; //call repeatedly to populate map.
...
let results : any[] = []; //next we will populate this
Trying to operate on it like this failed for Flow (though this is executable JavaScript):
for (let [name : string, object : MyObjectType] of Object.entries(objectsByName))
{
let result = doSomethingWith(object); //<- error on arg
results.push(result);
}
This succeeded for Flow:
for (let name : string in objectsByName)
{
let object = objectsByName[name];
let result = doSomethingWith(object); //<- error on arg
results.push(result);
}
It is annoying having to change code structure to suit a supposedly non-intrusive system like Flow comment types, which I chose in the hopes of making my code completely oblivious to Flow's presence. In this case I have to make an exception and structure my code as Flow wants it.
Replacing Object.entries with Object.keys + lookup fixes flow errors for me assuming the input object is properly typed.
i.e. replace Object.entries(a) with Object.keys(a).map(key => [key, a[key]])
This works with flow:
type Fields = {
name: string,
func: (*) => boolean
};
type S = {
key1: Fields,
bill: Fields
}
var a: S = {
key1: {name: 'mary', func: (str) => str === 'mary'},
bill: {name: 'bill', func: (str) => str === 'bill'}
}
var c = Object
.keys(a)
.map(key => a[key].func(key) ? obj : false)
.filter(f => f)
.reduce((acc, c) => {
return 'something here'
}, {});

flow fails on union type even with if/else

In the flow documentation, it states about typeof "This type test is particularly useful in conjunction with union types." The following, however does not pass flow's scythe:
var EventEmitter = require('events').EventEmitter;
var fnify = function(key: string | (x: number, y: any) => string) {
var fnkey = typeof(key) === 'function' ? key : (t) => key;
new EventEmitter().emit(fnkey(0), 0);
}
Flow complains that it does not know the return value of fnkey, which is odd, as it is guaranteed to be a string from the signature of the function. What does go through is:
var EventEmitter = require('events').EventEmitter;
var fnify = function(key: string | (x: number, y: any) => string) {
var fnkey = typeof(key) === 'function'
? key
: (t) => typeof(key) === 'string' ? key : null;
var kludge = fnkey(0);
if (kludge) {
new EventEmitter().emit(kludge, 0);
}
}
But the latter seems unnecessarily verbose. Is this a feature? Bug? Is there something wrong in the first snippet that makes flow irate?
The problem is that key can change in the function body, either use a const binding
var EventEmitter = require('events').EventEmitter;
var fnify = function(key: string | (x: number, y: any) => string) {
const k = key;
var fnkey = typeof(k) === 'function' ? k : (t) => k;
new EventEmitter().emit(fnkey(0), 0);
}
or set experimental.const_params=true in the [option] section of your .flowconfig

Resources