These are chat archives for ramda/ramda

22nd
Mar 2016
James Forbes
@JAForbes
Mar 22 2016 01:14

hey everyone, just had a placeholder thought.

//what currently works
R.curryN(3, R.concat)('<body>', R.__, '</body>')

It could be nice if R.__ signified to the original R.curry to override the initial arity.

Which would make this possible:

R.concat('<body>', R.__, '</body>')

So any time you would find yourself recurrying a function because of a different arity, you could just put the placeholder in and it wouldn't matter what the original arity of the function was.

In this case R.concat's arity is 2, but the placeholder would force it to wait for an additional argument because there is a placeholder that hasn't been fulfilled.

Any thoughts?

I'm aware there's discussion of removing the placeholder, I was in favour of it, but this intrigues me
Lewis
@6ewis
Mar 22 2016 01:38
is curryN similar to compose somehow?
Scott Christopher
@scott-christopher
Mar 22 2016 01:40
@6ewis: curryN converts a regular function into one that can be partially applied, while compose is used to combine multiple functions by taking the output of one and feeding it to the next, and so on.
The N in curryN lets you say how many arguments it should expect.
Lewis
@6ewis
Mar 22 2016 01:42
so compose could be seen as a subset of curryN?
Scott Christopher
@scott-christopher
Mar 22 2016 01:44
@ram-bot
const addThreeNums = curryN(3, (a, b, c) => a + b + c);
const foo = addThreeNums(1);
const bar = foo(2);
bar(3);
ram-bot
@ram-bot
Mar 22 2016 01:44
6
Scott Christopher
@scott-christopher
Mar 22 2016 01:46
@ram-bot
const add3 = compose(add(2), add(1));
add3(3);
ram-bot
@ram-bot
Mar 22 2016 01:46
6
Scott Christopher
@scott-christopher
Mar 22 2016 01:47
So compose(f, g) is effectively the same as x => f(g(x))
while curryN(3, (a, b, c) => a + b + c) is conceptually the same as a => b => c => a + b + c
Note you can typically just use curry instead of curryN, which will use the length property of the given function to determine how many arguments are expected.
Brad Compton (he/him)
@Bradcomp
Mar 22 2016 01:58

@6ewis CurryN is used when you want to be able to pass in a subset of the arguments to a function, but only to a specific number of arguments. It is useful if the function you are partially applying takes a variable number of arguments.
For example, let's say you have a MongoDB library that has an update function:
update :: (collection, query, updates, [options]) -> Promise(result)

You want to be able to curry the function so you can apply the collection (and maybe the query), but you don't want to require the user to pass in the options.

const curriedUpdate = R.curryN(3, update);
const updateActiveUsers = curriedUpdate('users', {active: true})
updateActiveUsers({$set: {happy: true}});  //This works!
updateActiveUsers({$set: {happy: true}}, {multi: true}); //This works too!
Lewis
@6ewis
Mar 22 2016 01:59
const compose3 = curryN(3, (a,b,c) =>  a(b(c)) );
const add1 = (x) => x + 1;
compose3(add1,add1, 1)
@scott-christopher yup I was wondering if behind the scene it uses curryN internally @Bradcomp thanks that was a nice use case
Brad Compton (he/him)
@Bradcomp
Mar 22 2016 02:02
@6ewis You wouldn't need curryN in this case.
@ram-bot
const compose3 = (a, b, c) => a(b(c))
compose3(R.add(1), R.add(1), 1)
ram-bot
@ram-bot
Mar 22 2016 02:03
3
Brad Compton (he/him)
@Bradcomp
Mar 22 2016 02:04
R.curryN
Risto Stevcev
@Risto-Stevcev
Mar 22 2016 04:36
What is everyone's impression of using Object.freeze on exported objects/funcs?
Risto Stevcev
@Risto-Stevcev
Mar 22 2016 04:45
Well, I guess only on objects... const works fine for the funcs
James Forbes
@JAForbes
Mar 22 2016 06:17
So any thoughts on the above?
kid-icarus
@kid-icarus
Mar 22 2016 07:42
if i have an array of objects, and i want to update an object in that array, what is the best approach to avoid mutation?
make a map of the array where the one element (by index) is replaced?
ah, i think this may be the answer http://ramdajs.com/0.19.1/docs/#adjust
kid-icarus
@kid-icarus
Mar 22 2016 08:09
does the ramda community have a pref on aliasing ramda to R? or _?
docs all use R, i guess it makes it less ambiguous
Keith Alexander
@kwijibo
Mar 22 2016 08:20
:point_up: March 21, 2016 8:21 PM @jgoux I don't think transducers are necessarily better than what you have; what don't you like about what you have?
Julien Goux
@jgoux
Mar 22 2016 08:45
@kwijibo I don't know, I just wanted external opinions :D
Anyway, thanks for the tranducers link, really interesting !
Keith Alexander
@kwijibo
Mar 22 2016 09:23
@jgoux The definitive explanation, I think, is a video by Rich Hickey
it's a good watch as well, and explains the motivation for transducers
In your case, there are two things stopping your reducers compose, right?
  1. they are binary functions, not unary
2.. the input accumulator is of a different type from the output accumulator
Keith Alexander
@kwijibo
Mar 22 2016 09:28
Your updateCompose function solves both those problems
Keith Alexander
@kwijibo
Mar 22 2016 09:34
but if you're interested in other possibilities, you could abstract it out into two functions that you could decorate your reducers with, and then compose the results with ordinary compose
Julien Goux
@jgoux
Mar 22 2016 09:43
@kwijibo Interesting, how would you split the functions ?
I mean, how would you decorate the reducer ?
Keith Alexander
@kwijibo
Mar 22 2016 09:43
  1. t = nextReduce => firstReduce => (result,item) => nextReduce( firstReduce(result, item), item)

then, say you have

var add = (a,b) => a + b

and

var multiply = (a,b) => a * b

you can decorate, then compose them like:

const addThenMultiply = R.compose(t(add), t(multiply) )(x=>x)
Keith Alexander
@kwijibo
Mar 22 2016 09:51
note that the composition sort of reads like it's in reverse because t(multiply) isn't a reducer, it's a function that accepts a firstReducer, and then returns a function that will multiply the results of that
and then I had to pass in this identity function x=>x at the end to get back the composed reducer
so that only works for reducers with the type a -> b -> a
but yours are like a -> b -> [a, c]
Jakub Korzeniowski
@kujon
Mar 22 2016 10:04
@JAForbes I don't think it's possible to determine how the function with increased arity should work, e.g. what would map(add(1), R.__, [1, 2, 3]) mean? Also - concat is part of the semigroup/monoid spec, meaning it's supposed to define a binary operation and an empty/identity element so you can fold/reduce multiple values of it reduce(concat, '', ['<body>', 'stuff', '</body>'])
Keith Alexander
@kwijibo
Mar 22 2016 10:05
@jgoux so maybe something like:
const wrapReduce = (extract, combine) => 
  reduce => 
  (result, item) => 
  combine(result, reduce(extract(result), item))
const extractModel = ([Model, Effects]) => Model
const combineEffects = ([M1,E1], [M2,E2]) => [M2, [...E1, ...E2]]
Jakub Korzeniowski
@kujon
Mar 22 2016 10:09
@JAForbes also look at how concat is defined https://github.com/ramda/ramda/blob/master/src/concat.js - it just dispatches to concat method, meaning you can implement any new type according to semigroup/monoid spec and have it work nicely on multiple values with reduce.
Keith Alexander
@kwijibo
Mar 22 2016 10:14
@jgoux so you'd then have something like
const wrapUpdates = wrapReduce(extractModel, combineEffects)
const combinedUpdateReducer = R.compose(
 t(wrapUpdates(update1)
,  t(wrapUpdates(update2)
,  t(wrapUpdates(update3)
)(x=>x)
(and then you'd want to get rid of all that boiler plate, to have a composeUpdates function :) )
Julien Goux
@jgoux
Mar 22 2016 10:18
@kwijibo Nice! I'll give it a try ^^
thank you !
Stefano Vozza
@svozza
Mar 22 2016 10:18
@Bradcomp R.props has been added, just an oversight on my part
Keith Alexander
@kwijibo
Mar 22 2016 10:20
@jgoux it was fun to think through, but your original code is probably clearer and more concise - this is probably a YAGNI level of abstraction
James Forbes
@JAForbes
Mar 22 2016 10:52

@kujon I see your point, but the fact a function could be used in a nonsensical way, doesn't mean it isn't a valid or useful function.

map(add(1), R.__, [1, 2, 3])

would just place the list ( [1,2,3]) as the 3rd argument to map (which would be ignored), that usage would make no sense, but it would continue to wait for another argument, and that next argument would populate the placeholder. Which I find to be pretty intuitive/unsurprising.

Also, yes concat dispatches. But in the context where this came up I would only ever want to use it on string/arrays which are both variadic.
It's cool that fantasy land lets us write code for a potentially infinite number of data structures, but that doesn't mean all our code has to make that consideration.

But this proposal of R. enforcing the future application to be partial is a generally useful thing, that isn't specific to concat.

I don't think it's possible to determine how the function with increased arity should work

seems pretty simple to me, but maybe not compatible with the way ramda's curry works, perhaps?

Walle Cyril
@GrosSacASac
Mar 22 2016 11:07
I tell my studends to write out const Ramda = R at the beginning. It is less confusing for them.
Walle Cyril
@GrosSacASac
Mar 22 2016 11:13
First JavaScript contact, in the first week
Jakub Korzeniowski
@kujon
Mar 22 2016 11:21

@JAForbes I must be missing something. If you've got:

:: a -> b -> c

there are 3 ways to increase its arity:

:: a -> b -> d -> c
:: a -> d -> b -> c
:: d -> a -> b -> c

I don't see any generic way of doing that apart from totally ignoring d - which is imho just kind of a weird way of creating a thunk.

I think your use case mentions something more like:

:: a -> a -> a

which I still think doesn't give you the ability to consistently determine how the function should behave, e.g. what would this do:

subtract(4, R.__, 2) // => ?

Am I missing something?

James Forbes
@JAForbes
Mar 22 2016 21:53

Thanks for the critiques @kujon

subtract(4, R.__, 2) is the same as R.curryN(3, subtract)(4, R.__, 2) it's a weird usage of it.
As I said, any tool can be misused.


What I'm proposing is:

A curried function is never allowed to invoke if a placeholder is still present and any further partial application will fill those placeholders left to right.

You can apply it with an arg in a position that is greater than its arity and it will stay partially applied until the placeholder position is populated. The above rules takes precedence over the arity being fulfilled.

It won't be useful for binary functions at all. It will probably only come in handy for variadic functions.

e.g. say you have a data transformation, you know the first and last steps but not the middle step. Currently you could use curryN to prevent the final application:

R.curryN(3, R.pipe)(a,R.__, c)

Because you don't no what b will be yet.

But I'm proposing a curried function has enough context to not require the curryN

so you get

var waitingForB = R.pipe(a, R.__, c)
waitingForB(b) //invokes a(b(c()))

In my original use case I was using concat, I wanted to define a function that surrounds an element with 2 other elements (ignoring fantasy land, this is just pragmatism).

e.g.

var quote = R.curryN(3, R.concat)(`'`, R.__, `'`)

quote(4) //=> '4'

And it occurred to me this could be represented more intuitively as:

var quote = R.concat(`'`, R.__, `'`)

I could see this being used with compose, pipe, invoker, concat, converge, useWith, partial, partialRight and variadic functions in userland that are curried like virtual dom templates, or ajax request.

There are many occasions in JS where functions are variadic, and though I'd rather it were not true, its best to accept it and I think this is a nice convenience.

It is kind of weird to execute a function that still has place holders as args right? I'm suggesting, instead of removing them, we give them higher precedence.

James Forbes
@JAForbes
Mar 22 2016 22:21
note: In the above example I changed compose to pipe but then that made my "invokes a(b(c())) incorrect
Lewis
@6ewis
Mar 22 2016 22:34
is pipe composition from left to right?
that's it?
Scott Sauyet
@CrossEye
Mar 22 2016 22:36
exactly
Lewis
@6ewis
Mar 22 2016 22:36
@crossEye why not make composition from left to right from the get go then
James Forbes
@JAForbes
Mar 22 2016 22:41
@6ewis compose(f,g,h) is meant to model f(g(h()))
in some situations the right to left is more intuitive too. e.g:
var tbody = compose(
  m('tbody'),
      map(m('tr')),
        map(
            map(
              m('td')
            )
        )
)

var thead = compose(
    m('thead'),
        m('tr'),
            map(
                m('th'),
            )
)


var table = useWith(
    m('table'), [
        thead, 
        tbody
    ])
Scott Sauyet
@CrossEye
Mar 22 2016 22:44
@6ewis compose is probably the more fundamental notion. It's used everywhere, borrowed from mathematics. (g ∘ f )(x) = g(f(x)) is from some math class you may remember. We can't introduce such symbols, so we use compose(g, f) for the same thing. But there are many -- me included -- who often find the other direction easier to understand, so we offer pipe as a simple gloss, and pipe(f, g) is the same thing.
James Forbes
@JAForbes
Mar 22 2016 22:46
@6ewis
that's a virtual dom example for the record, the m function is just mithril's vdom function, but it could just as easily be snabbdom or hyperscript. In the above case, using pipe wouldn't map as intuitively to html's structure as compose