These are chat archives for ramda/ramda

22nd
Oct 2015
boxofrox
@boxofrox
Oct 22 2015 00:07
Is that 'panda eats gun and leaves'?
or 'panda dines violently on leaves'...
Martin Algesten
@algesten
Oct 22 2015 00:23
panda eats shoots and leaves
there was an amusing book named that. about the problems of not using punctuation correctly.
boxofrox
@boxofrox
Oct 22 2015 00:28
ah
Scott Sauyet
@CrossEye
Oct 22 2015 00:29
And the joke was that the sign was actually badly punctuated. Instead of the correct "Eats shoots and leaves", it read "Eats, shoots, and leaves."
boxofrox
@boxofrox
Oct 22 2015 00:35
lol, i had to look that up.
Hardy Jones
@joneshf
Oct 22 2015 01:01
What do you all do when you blow the stack and don't know where to look?
Scott Christopher
@scott-christopher
Oct 22 2015 01:02
reach for a debugger
Scott Sauyet
@CrossEye
Oct 22 2015 01:14
Yes, and failing that, something like @raine's treis.
boxofrox
@boxofrox
Oct 22 2015 01:14
breadcrumbs?
var trace = R.curry(function(tag, x) { console.log(tag, x); return x; });
Hardy Jones
@joneshf
Oct 22 2015 01:29
ugh.
Jethro Larson
@jethrolarson
Oct 22 2015 03:11
Could run the profiler and look for smoke
boxofrox
@boxofrox
Oct 22 2015 03:16
If anyone figures out how to use the profiler for debugging, please share. :thumbsup:
...with blown stack traces, that is.
boxofrox
@boxofrox
Oct 22 2015 03:23

So reduce takes one function, and iterates over values in an array. Is there an inverse that takes one value, and iterates over functions in the array?

Something like:

var value = { first: 'Bob', last: 'Arnold', age: 50, likes: 'pandas', title: 'Mr.' };

var addLikeMsg = (acc, val) => R.assoc('likeMsg', "${val.first} likes ${val.likes}.", acc);
var addAgeMsg = (acc, val) => R.assoc('ageMsg', "${val.title} ${val.last} is ${val.age} year(s) old.", acc);

var result = reduceInsanity([addLikeMsg, addAgeMsg], {}, value);
David Chambers
@davidchambers
Oct 22 2015 03:24
Is that simply function composition?
boxofrox
@boxofrox
Oct 22 2015 03:24
/facepalm
wait...
don't think so...
each fn has 2 arity, which as I understand it doesn't compose.
value is the data, so it occupies the last parameter, and is passed to each function along with the result of the previous function. that accumulator is effectively composition.
David Chambers
@davidchambers
Oct 22 2015 03:29
Good point. You need S.meld. :smile:
Sorry for the slow reply. I'm in New Zealand at the moment with a terrible internet connection.
boxofrox
@boxofrox
Oct 22 2015 03:31
No worries

I need something like meld, but more like this:

          +-------+
--- a --->|       |
          |   f   |                +-------+
--- b -+->|       |--- f(a, b) --->|       |
       |  +-------+                |   g   |                +-------+
       +-------------------------->|       |--- g(a, b) --->|       |
       |                           +-------+                |   h   |
       +--------------------------------------------------->|       |--- h(g(a, b), b) --->
                                                            +-------+

where a is the initial value, and b is value from the example code above.

David Chambers
@davidchambers
Oct 22 2015 03:38
You want to use the same value as the second argument in each case?
You could partially apply each of the functions, then compose.
boxofrox
@boxofrox
Oct 22 2015 03:40
Yea. I'm taking an XmlHttpRequest response and converting it into a new data structure. I thought I could break all the transforms into little bits and the response would be my value, and the accumulator would become my new data structure.
so one function would be keepStatus, another would convert the response text to json and add it in.
Hardy Jones
@joneshf
Oct 22 2015 03:42
flip your perspective.
you want to reduce over the functions
your initial value is value.
reduce(f, value, [addLikeMsg, addAgeMsg])
then just figure out f.
boxofrox
@boxofrox
Oct 22 2015 03:45
I see how f would get the accumulated value, so it can pass that on to each function. My mind goes blank trying to figure out how f holds onto value as the second argument to each function in the array.
Hardy Jones
@joneshf
Oct 22 2015 03:46
okay, what are the arguments to f?
boxofrox
@boxofrox
Oct 22 2015 03:48
in your example? reduce gives f the arguments (acc, fn). but f needs to call fn(acc, value) each iteration.
Hardy Jones
@joneshf
Oct 22 2015 03:48
and what is acc?
boxofrox
@boxofrox
Oct 22 2015 03:49
in my graph above, acc is f(a,b) on first iteration, g(a,b) on second, then h(g(a,b),b) on last iteration.
Hardy Jones
@joneshf
Oct 22 2015 03:49
what is it initially?
boxofrox
@boxofrox
Oct 22 2015 03:50
oh, I was thinking emtpy object. {}. in my use-case I put an object in, and get an object out.
Hardy Jones
@joneshf
Oct 22 2015 03:52
hmm, how about another perspective.
What is addLikeMsg(value, value)?
boxofrox
@boxofrox
Oct 22 2015 03:54
Sorry, I don't follow. I feel like I should write the example from earlier again. I wanna say it's like a monad.
or monoid... still trying to grasp those concepts.
boxofrox
@boxofrox
Oct 22 2015 03:59

Anyway, was curious if it was a known problem someone had solved. My naive attempt comes to:

var invertedReduce = R.curry((fnary, initial, data) => {
  var acc = initial;
  for (var a = 0; a < fnary.length; ++a) {
    acc = fnary[a](acc, data);
  }
  return acc;
});

It's not technically a reducing function (no array of data), more like a anagram.

Hardy Jones
@joneshf
Oct 22 2015 04:00
I apologize, I'm probably not making sense. Been up for a long time.
boxofrox
@boxofrox
Oct 22 2015 04:01
no worries. all these inquiries got me thinking along lines I never considered.
Hardy Jones
@joneshf
Oct 22 2015 04:01
I was suggesting something ike this:
reduce((acc, f) => f(acc, acc), value, [addLikeMsg, addAgeMsg])
boxofrox
@boxofrox
Oct 22 2015 04:04
Ah. The two arguments of f are different in my case. Each function needs the original data to pull from and place in the accumulator.
var value = { first: 'Bob', last: 'Arnold', age: 50, likes: 'pandas', title: 'Mr.' };

var addLikeMsg = (acc, val) => R.assoc('likeMsg', "${val.first} likes ${val.likes}.", acc);
var addAgeMsg  = (acc, val) => R.assoc('ageMsg', "${val.title} ${val.last} is ${val.age} year(s) old.", acc);

var result = invertedReduce([addLikeMsg, addAgeMsg], {}, value);
//=>
// {
//   likeMsg: 'Bob likes pandas.',
//   ageMsg:  'Mr. Arnold is 50 years old.',
// }
I didn't want to worry about any functions overwriting initial values, or having to clear them out at the end.
Hardy Jones
@joneshf
Oct 22 2015 04:06
oh, you explicitly only want those two fields?
boxofrox
@boxofrox
Oct 22 2015 04:06
right
take object A in, use it's properties to generate values for object B going out. instead of one big function. break each property that B will have into its own function.
Hardy Jones
@joneshf
Oct 22 2015 04:08
then pretty much the same, but it'd be easier if you switched the orderof the args and curried it.
reduce((acc, f) => f(acc), {}, map(f => f(value), map(curry, map(flip, [addLikeMsg, addAgeMsg]))))
boxofrox
@boxofrox
Oct 22 2015 04:08
that's impressive
Hardy Jones
@joneshf
Oct 22 2015 04:09
or you could do some map fusion:
reduce((acc, f) => f(acc), {}, map(compose(f => f(value), curry, flip), [addLikeMsg, addAgeMsg]))
so you end up iterating the array twice rather than four times.
Scott Sauyet
@CrossEye
Oct 22 2015 04:12

Slightly different:

var result = R.head(R.reduce(([acc, val], fn) => [fn(acc, val), val], [{}, value], [addLikeMsg, addAgeMsg]));

Perhaps more explicit.

boxofrox
@boxofrox
Oct 22 2015 04:13
I hope to someday have a grasp of FP like you guys :clap:
Scott Sauyet
@CrossEye
Oct 22 2015 04:14
The ones from @joneshf are perhaps more interesting. Mine is as literal as I could make it from the problem.
Hardy Jones
@joneshf
Oct 22 2015 04:15
boxofrox
@boxofrox
Oct 22 2015 04:16
sweet, thanks
Hardy Jones
@joneshf
Oct 22 2015 04:17
@CrossEye what about an object as the accumulator?
Scott Sauyet
@CrossEye
Oct 22 2015 04:18
all sorts of possibilities. That was the first one that came to mind.
The trick was carrying through the value parameter. I did think about flipping. But by the time I'd written my first pass, you had two solutions, one of which involved flipping, so I didn't bother trying anything further.
Hardy Jones
@joneshf
Oct 22 2015 04:20
yeah
Scott Sauyet
@CrossEye
Oct 22 2015 04:20
both of which involved flipping, I guess.
Scott Sauyet
@CrossEye
Oct 22 2015 04:34

Reading that Fowler article.... "Damn it, I do have to try that!"

var reduce2 = R.curry((fns, value) => R.reduce((acc, fn) => fn(acc, value), {}, fns));
var result = reduce2([addLikeMsg, addAgeMsg], value);

This is pretty clean. It's not quite clear which is the right parameter order. Either seems defensible.

Hardy Jones
@joneshf
Oct 22 2015 04:40
yeah, that's clearer than both our previous.
I don't think it much matters the order at that point.
boxofrox
@boxofrox
Oct 22 2015 04:43
I am in awe. That's what I was going for.
Scott Sauyet
@CrossEye
Oct 22 2015 04:46
The order probably does matter. But it depends on how it's going to be used. Do you have a list of transforms that you will want to apply to various different values? ((fns, value)) Or do you have a value that you want to convert in various ways with different collections of transformations? ((value, fns)). But since I didn't know @boxofrox's real use case, I just had to guess.
boxofrox
@boxofrox
Oct 22 2015 04:47
Man, when I was drawing a blank earlier, all I had to think was 'use a closure', and I'd basically have it from your first suggestion, @joneshf .
@CrossEye, the order is right. the functions are known in advanced. The value is fed in from the result of an ajax request.
sometimes i'm dumber than a box of rocks.
Hardy Jones
@joneshf
Oct 22 2015 04:52
@CrossEye oh, that order. I was still thinking the order of the arguments to each function.
Scott Sauyet
@CrossEye
Oct 22 2015 05:06

Going to bed now, but one more thought along these lines, of a somewhat altered API, one I think might be cleaner, although the implementation is not as nice:

    var reduce3 = R.curry((pairs, value) => 
        R.reduce((acc, pair) => 
            ((context, val) => R.assoc(pair[0], pair[1](val), context))(acc, value), 
        {}, pairs)
    );

    var converter = reduce3([
      ['likeMsg', val => `${val.first} likes ${val.likes}.`],
      ['ageMsg', val => `${val.title} ${val.last} is ${val.age} year(s) old.`]
    ]);

    var result = converter(value);

Of course this could be done with an object rather than a list of pairs so long as the order of operations is not critical. That might be slightly cleaner, and should be no more difficult.

Denis Stoyanov
@xgrommx
Oct 22 2015 05:54
I have another approach:
var value = { first: 'Bob', last: 'Arnold', age: 50, likes: 'pandas', title: 'Mr.' };

var addLikeMsg = val => R.assoc('likeMsg', `${val.first} likes ${val.likes}.`, val);
var addAgeMsg  = val => R.assoc('ageMsg', `${val.title} ${val.last} is ${val.age} year(s) old.`, val);

R.compose(R.pick(['likeMsg', 'ageMsg']), R.converge(R.merge, [addLikeMsg, addAgeMsg]))(value);
Denis Stoyanov
@xgrommx
Oct 22 2015 06:06
Or common case:
var value = { first: 'Bob', last: 'Arnold', age: 50, likes: 'pandas', title: 'Mr.' };

var addLikeMsg = val => R.assoc('likeMsg', `${val.first} likes ${val.likes}.`, val);
var addAgeMsg  = val => R.assoc('ageMsg', `${val.title} ${val.last} is ${val.age} year(s) old.`, val);
var fullName = val => R.assoc('fullName', `${val.first} ${val.last}`, val);

R.compose(
  R.pick(['likeMsg', 'ageMsg', 'fullName']), 
  R.converge(R.unapply(R.reduce(R.merge, {})), [addLikeMsg, addAgeMsg, fullName])
)(value);
Denis Stoyanov
@xgrommx
Oct 22 2015 06:22
Without manual filtered by keys. http://bit.ly/1QVUXtd
Denis Stoyanov
@xgrommx
Oct 22 2015 06:29
But I don;t understand why you use assoc?
var value = { first: 'Bob', last: 'Arnold', age: 50, likes: 'pandas', title: 'Mr.' };

var addLikeMsg = val => ({'likeMsg': `${val.first} likes ${val.likes}.`});
var addAgeMsg  = val => ({'ageMsg': `${val.title} ${val.last} is ${val.age} year(s)`});
var fullName = val => ({'fullName': `${val.first} ${val.last}`});

R.converge(R.unapply(R.reduce(R.merge, {})), [addLikeMsg, addAgeMsg, fullName])(value)
Raine Virta
@raine
Oct 22 2015 06:53
i think it would be prettier if message formatters were separate functions
maybe
Denis Stoyanov
@xgrommx
Oct 22 2015 06:58
@raine what do u mean?
Raine Virta
@raine
Oct 22 2015 07:07
fullName = (first, last) =>${first} ${last}`` and so forth
Denis Stoyanov
@xgrommx
Oct 22 2015 07:10
var fullName = ({first, last})=> ({'fullName':${first} ${last}}); ?
Raine Virta
@raine
Oct 22 2015 07:10
no
Vojtech Jasny
@voy
Oct 22 2015 07:10
hello everyone, starting with ramda and have a basic question
Raine Virta
@raine
Oct 22 2015 07:11
the way I see it is, why should formatting a full name care about the result being in an object
Vojtech Jasny
@voy
Oct 22 2015 07:11
is there an option to get a path of object props like _.get(obj, 'something. somethingElse')? seems like there's not, why would that be?
Raine Virta
@raine
Oct 22 2015 07:11
it could be fullName = R.pipe(R.props(['first', 'last']), R.join(' ')) too
@voy R.path
Vojtech Jasny
@voy
Oct 22 2015 07:12
@raine oh thx, my mistake
Denis Stoyanov
@xgrommx
Oct 22 2015 07:12
@raine yes, but also this should be needed to transform in to object.
Raine Virta
@raine
Oct 22 2015 07:13
that can happen in another step
Vojtech Jasny
@voy
Oct 22 2015 07:14
and another question. say i have an object like this: { rootKey: { foo: true } } and I want to get to the value of rootKey. and for the sake of argument I don't want to do R.compose(R.head, R.keys)(myObj). can I somehow implement this using R.keys, R.head and R.prop?
Raine Virta
@raine
Oct 22 2015 07:14
@voy R.prop('rootKey', { rootKey: { foo: true } })
Vojtech Jasny
@voy
Oct 22 2015 07:15
@raine let's assume there is exactly one rootKey, but I don't know the exact name of that key
:-)
in our case that would be attribute or metric or say a fact
and I get objects with one of those root keys
Raine Virta
@raine
Oct 22 2015 07:15
in that case I believe you need to read the keys
or use some kind of JSON path abstraction library
Vojtech Jasny
@voy
Oct 22 2015 07:16
@raine just trying to train myself and implement this in a point free style
Denis Stoyanov
@xgrommx
Oct 22 2015 07:17
var v = { rootKey: { foo: true } };
R.prop(R.keys(v)[0], v)
:smile:
Vojtech Jasny
@voy
Oct 22 2015 07:17
@xgrommx that's all good, but I want a function that does this using function composition :-)
Denis Stoyanov
@xgrommx
Oct 22 2015 07:18
@voy what do u mean?
Vojtech Jasny
@voy
Oct 22 2015 07:18
sure, I could easily just write item => item[Object.keys(item)[0]], but I'm trying to learn something :-)
@xgrommx so sort of like I have written above, some construct using R.compose
Denis Stoyanov
@xgrommx
Oct 22 2015 07:20
var v = { rootKey: { foo: true } };
R.compose(R.prop(R.__, v), R.head, R.keys)(v)
Raine Virta
@raine
Oct 22 2015 07:25
const rootProp = (obj) =>
  R.prop(R.head(R.keys(obj)), obj)
i don't think there's anything in ramda that would help making this point-free
Vojtech Jasny
@voy
Oct 22 2015 07:25
@xgrommx nice try, but you curried R.prop to v forever, call it with a different object and it won't work :-(
@raine ok, that's fine too, not everything needs to be 100% point free, right? :-)
Raine Virta
@raine
Oct 22 2015 07:26
absolutely not
Denis Stoyanov
@xgrommx
Oct 22 2015 07:27
@voy :
var v = { rootKey: { foo: true } };
R.compose(R.flip(R.prop)(v), R.head, R.keys)(v)
Vojtech Jasny
@voy
Oct 22 2015 07:27
i was thinking create a map [item, rootKeyName] and then do R.apply(R.prop, myArgumentArray)
but that somehow stinks
Raine Virta
@raine
Oct 22 2015 07:27
I don't think that's a good use of compose because you're fixing the object in the pipeline
might as well just use lambda
Denis Stoyanov
@xgrommx
Oct 22 2015 07:28
agree
Vojtech Jasny
@voy
Oct 22 2015 07:29
yeah, well thank you guys :-)
btw is there a good way to just use a couple ramda functions like you can do with lodash? probably just like require('ramda/src/head.js'), right?
Denis Stoyanov
@xgrommx
Oct 22 2015 07:29
@voy R.head(R.values(v))
Raine Virta
@raine
Oct 22 2015 07:30
@voy I think that only makes sense with browserify (and webpack?)
in node there's probably no difference
Vojtech Jasny
@voy
Oct 22 2015 07:32
@raine that's exactly where i'm going. i already have lodash in place and don't want to migrate everything, but i'd like to start using ramda in places and not have to bundle the whole library, if possible.
Raine Virta
@raine
Oct 22 2015 07:32
do you use babel?
Vojtech Jasny
@voy
Oct 22 2015 07:32
bundling means parsing and i have 2 megs of js as it stands
yes, i do
Raine Virta
@raine
Oct 22 2015 07:32
there's a babel plugin that transforms const { head } = require('ramda'); to const head = require('ramda/src/head');
Raine Virta
@raine
Oct 22 2015 07:32
yep. that one
Vojtech Jasny
@voy
Oct 22 2015 07:33
alright, that's pretty slick!
Raine Virta
@raine
Oct 22 2015 07:33
Vojtech Jasny
@voy
Oct 22 2015 07:34
i have to look into how babel plugins work
@raine looks like you need to support ES6 modules :-)
but otherwise it's a neat idea, would use sth like this with lodash
Raine Virta
@raine
Oct 22 2015 07:35
they should work.
Vojtech Jasny
@voy
Oct 22 2015 07:35
oh, ok then :-)
Raine Virta
@raine
Oct 22 2015 07:35
I don't see any reason to use ES6 module syntax
Vojtech Jasny
@voy
Oct 22 2015 07:35
why not?
Raine Virta
@raine
Oct 22 2015 07:36
so far, it's just sugar on top of require, and the way the module system is going to work is not finalized or something
Vojtech Jasny
@voy
Oct 22 2015 07:44
i'm not 100% clear on that, but I don't think it will change much and you get it for free with webpack
for me there is not reason to use require with babel (sorry, not webpack)
i find it prettier, but i'm not really getting much more out of it expect it's more future-proof
Raine Virta
@raine
Oct 22 2015 07:58
i understood they're not future proof, because the loader spec is not finished
<ecmabot> While ES6 provides syntax for import/export, it currently does nothing, anywhere, because the loader spec is not finished ( https://github.com/whatwg/loader ). ES6 Modules are not yet a thing; they do not yet exist. !babel simply transpiles import/export to require, which is not guaranteed to work once the loader is finished.
Vojtech Jasny
@voy
Oct 22 2015 08:02
still it probably won't be too hard to migrate :-)
Vojtech Jasny
@voy
Oct 22 2015 08:13
what about things like .startsWith or .contains? any way to migrate?
Raine Virta
@raine
Oct 22 2015 08:15
R.invoker(1, 'startsWith')
Vojtech Jasny
@voy
Oct 22 2015 08:21
ok, so the answer is to just use invoker and String.prototype?
Vojtech Jasny
@voy
Oct 22 2015 08:39
well, not sure invoker is a great idea, the results are not awesome in terms of readability
R.compose(R.invoker(R.__, 'includes', filter), R.invoker(R.__, 'toLowerCase'), R.prop('title')) vs. R.compose(R.includes(filter), R.toLowerCase, R.prop('title'))
invoker & R. => not beatiful
of course, it does not make sense to mirror the whole string api in ramda :-)
Raine Virta
@raine
Oct 22 2015 09:53
@voy you have the luxury of putting values into variables
const includes = R.invoker(1, 'includes');
R.toLower exists in ramda
Raine Virta
@raine
Oct 22 2015 10:00
but I do think ramda needs better support for strings, even if it's in the shape of an auxiliary library
Vojtech Jasny
@voy
Oct 22 2015 10:59
@raine you're right in the variable thing, definitely a good idea to just inline everything
Vojtech Jasny
@voy
Oct 22 2015 11:06
is that something i could potentially help with?
@raine i'm guessing that what we'd need is a wrapper around String.prototype methods, which accepts the actual string as the last argument, correct?
Raine Virta
@raine
Oct 22 2015 11:07
remember that not every environment has those functions in String.prototype
Vojtech Jasny
@voy
Oct 22 2015 11:07
not sure what you mean
Raine Virta
@raine
Oct 22 2015 11:08
String.prototype.startsWith is ES6
Vojtech Jasny
@voy
Oct 22 2015 11:10
sure, you'd probably need to make sure those methods are actually available and then do the wrapping on those
i thought there was like a case where String.prototype is not available at all or sthg
Raine Virta
@raine
Oct 22 2015 11:11
I don't think string.prototype needs to be involved at all if these functions were to be introduced in ramda
Vojtech Jasny
@voy
Oct 22 2015 11:12
sounds like you essentially want to reimplement things like substr etc?
Raine Virta
@raine
Oct 22 2015 11:14
it's also a bit unclear in what form these string functions should appear in ramda
some list functions support strings
Ricardo Pallas
@RPallas92
Oct 22 2015 11:15
Do you recommend use Sanctuary with Ramda?
Scott Sauyet
@CrossEye
Oct 22 2015 12:10
@RPallas92: Absolutely. That's what Sanctuary was designed for. ..
... to fill in the gaps where certain recalcitrant Ramda developers were unwilling to tread.
(Most notably that CrossEye guy.)
Ramda adds all sorts of functional constructs to JS, but doesn't try to do anything noteworthy about type safety. Sanctuary extends this with a few more FP constructs and greater type safety.
Ricardo Pallas
@RPallas92
Oct 22 2015 12:16
quite pure
But it has't futures
hasn't
Scott Sauyet
@CrossEye
Oct 22 2015 12:17
No real difference in purity (at least in the technical sense) but more in how it deals with potential errors.
ramda-fantasy has Futures.
Ricardo Pallas
@RPallas92
Oct 22 2015 12:19
so ramda-fantasy is equivalent to sanctuary?
Scott Sauyet
@CrossEye
Oct 22 2015 12:19
it also works well with any compliant Fantasy-land Functor or Monoid or Monoid, etc.
no
Ramda-fantasy is a collection of toes meeting (or trying to meet) various parts of the Fantasy-land spec.
Sanctuary is operating more in the same space as Ramda, as a general-purpose utility lib.
Ricardo Pallas
@RPallas92
Oct 22 2015 12:22
I understand, thank you very much, but Either and Maybe are the same in both?
Scott Sauyet
@CrossEye
Oct 22 2015 12:29
Different implementations. I don't remember if their APIs are identical, but I'm sure they're similar. They'd have to share a lot of API to meet Fantasy-land specs.
Andreas Köberle
@eskimoblood
Oct 22 2015 16:20
is there a ramda function for this: a -> b -> [a, b]
Raine Virta
@raine
Oct 22 2015 16:44
Andreas Köberle
@eskimoblood
Oct 22 2015 17:20
@raine thanks, couldn't find it in my dash docs, cause its only available since 0.18
Jethro Larson
@jethrolarson
Oct 22 2015 18:25
If you saw
//:: (a -> b -> c) -> (b, a) -> c
What would you assume it meant?
specifically the second argument
Raine Virta
@raine
Oct 22 2015 18:26
tuple?
Jethro Larson
@jethrolarson
Oct 22 2015 18:28
Trying to find a way to represent fixed arity along with curried
Jethro Larson
@jethrolarson
Oct 22 2015 20:37

I've been noticing that this comes up a lot when going point-free:

var foo = (a, b)  =>...
var bar = (a, b) => foo(a, baz(b))

One choice is

bar = useWith(foo, I, baz)

with full curry it can be

bar = flip(compose(flip(foo), baz))

I wonder if there's an actual structure for this though.

var composeRight = (f, g) => x => y => f(x, g(y))
bar = composeRight(foo, baz)

Does that sound like a thing?

Scott Sauyet
@CrossEye
Oct 22 2015 21:00
@jethrolarson: it sounds like a very interesting thing. I haven't seen it before, but I'm sure I've come across the pattern, too.
Jethro Larson
@jethrolarson
Oct 22 2015 21:01
when trying to point-free a reduce a lot of this kind of thing comes up
Scott Sauyet
@CrossEye
Oct 22 2015 21:01
Makes sense.
Jethro Larson
@jethrolarson
Oct 22 2015 21:04
//:: String -> String -> String
var replaceStringToken = composeRight(reduce(replace),split(''))
Scott Sauyet
@CrossEye
Oct 22 2015 21:14
Would love to see a PR on this. We could bikeshed on the name if need be.
Denis Stoyanov
@xgrommx
Oct 22 2015 21:16
composeRight is pipe?
Scott Sauyet
@CrossEye
Oct 22 2015 22:23
@xgrommx: No. Simplified to unary/binary,
pipe = (f, g) => x => g(f(x))
pipe = (f, g) => (x, y) => g(f(x, y))
composeRight = (f, g) => x => y => f(x, g(y))
David Chambers
@davidchambers
Oct 22 2015 22:38
By the way, @RPallas92, I would like to add Future, Task, and possibly other types to Sanctuary. It's even possible that Sanctuary and ramda-fantasy could one day merge. See discussion in ramda/ramda-fantasy#56.
Jethro Larson
@jethrolarson
Oct 22 2015 22:46
I'm not convinced that ramda-fantasy is following ramda's style of using functions instead of methods
Scott Christopher
@scott-christopher
Oct 22 2015 22:47
Methods have been used to support the fantasy land spec
Jethro Larson
@jethrolarson
Oct 22 2015 22:47
I get that. I'm not sure on the value of it though
Scott Christopher
@scott-christopher
Oct 22 2015 22:47
I have some proposals to bring an emphasis back to functions and pattern matching though
Jethro Larson
@jethrolarson
Oct 22 2015 22:48
using common semantics is the real value in my mind. At least from a cultural perspective
Scott Christopher
@scott-christopher
Oct 22 2015 22:49
Well without a type system, there's not much else that I'm aware of that can be done to capture the polymorphic behaviour of the various type classes.
Jethro Larson
@jethrolarson
Oct 22 2015 22:49
And it's awesome that there's concrete rules described in a central place
Have you seen my experiment on using type matching in place of prototype inheritance?
Scott Christopher
@scott-christopher
Oct 22 2015 22:51
But that's arguably no different. You're dispatching on something attached to the prototype of an object.
Jethro Larson
@jethrolarson
Oct 22 2015 22:52
?
I have an object used as the namespace for a "type". The data of the type isn't directly tied to it though
there's an abstract interface for the functions
Scott Christopher
@scott-christopher
Oct 22 2015 22:57
If I understand correctly, the difference being that in your example the dispatch takes the type name off the prototype and then does a lookup for the matching function, rather than taking the method directly off the prototype.
Jethro Larson
@jethrolarson
Oct 22 2015 22:58
Yeah basically. Separates data from functions
types will typically have an identifier on them so they can be related to the relevant versions of the functions
It's open ended though
This has a couple benefits as I see it:
  1. You can store some types as JSON data and then use the functions without having to parse it through constructors.
  2. You can implement generic interfaces that operate on several types by sniffing for what properties they have
Jethro Larson
@jethrolarson
Oct 22 2015 23:04
contention could be a problem as the first implementation that matches the data will be used. e.g. if I make List try to operate on anything with a .length then that could be too promiscuous in some cases.
Scott Sauyet
@CrossEye
Oct 22 2015 23:07
Although I've been put it aside in favor of several other projects, I've done some work on a similar system, also prototype based, but designed a bit differently. There's a registry of types, and the functions look up in the registry the correct implementation for the instances supplied. This involves walking the prototype chain to find a match. It requires the registration of your type, but then usage is just map(fn, myFunctor), etc.
Jethro Larson
@jethrolarson
Oct 22 2015 23:08
My technique frees you from having to establish specific heirarchies using prototype chain
If object looks like a duck and dispatcher knows what looks like a duck and there's a function defined to cook a duck, you can cook a duck
Scott Sauyet
@CrossEye
Oct 22 2015 23:11
No hierarchy is necessary; just the registry. But a hierarchy is allowed. So a descendent constructor function of a one that is registered as a Functor also creates Functors.
Jethro Larson
@jethrolarson
Oct 22 2015 23:11
or any kind of fowl that looks like a duck :)
how would you map things in the registry to the data?
Scott Sauyet
@CrossEye
Oct 22 2015 23:13
isPrototypeOf
perhaps not very efficient, but it's clean.
Jethro Larson
@jethrolarson
Oct 22 2015 23:13
Obv that leaves array as a special case
Scott Sauyet
@CrossEye
Oct 22 2015 23:14
I can register Array/Object/String, etc. for whatever types I like.
I really need to get back to this.
Jethro Larson
@jethrolarson
Oct 22 2015 23:26

Without the dispatching you could accomplish most of it with static methods:

Maybe.map(add1, maybe2)

Though you don't get the benefit of generic, partially-applied functions

var mapIncrement = Maybe.map(add1)

mapIncrement([1, 2])

:boom:

Which I think is the real reason to use ramda over other options
BTW arrow functions in js now mean it's impossible to find anything about talking about functional arrows in js
Scott Sauyet
@CrossEye
Oct 22 2015 23:30
my idea is that Lib.map(square, anyFunctor) would call the function that was registered for map of the type of anyFunctor.
So after registration, I would be working with pure functions.
Jethro Larson
@jethrolarson
Oct 22 2015 23:31
Would Lib be stateful?
Scott Sauyet
@CrossEye
Oct 22 2015 23:31
Only in that you can register types.
Scott Christopher
@scott-christopher
Oct 22 2015 23:33
I started playing with something similar for dispatching in ramda, except it registers a predicate against a function name, rather than a type.
Then it just becomes a composition of the predicates, falling back to a "type not supported" error.
Jethro Larson
@jethrolarson
Oct 22 2015 23:34
var mapSq = Lib.map(square)
mapSq(Just(2))
// Error: object has not been registered
Lib.register(Maybe)
mapSq(Just(2))
// Just(4)
Scott Sauyet
@CrossEye
Oct 22 2015 23:44
Right. It would be stateful in that way, unless I did something like Bilby's environments. I'm not worried about that yet.
Scott Sauyet
@CrossEye
Oct 22 2015 23:50
and the registration would be more like:
Lib.register(Maybe, {
    Functor: {
        map: function(fn, maybe) { /* ... */}
    },
    Monoid: {
        empty: function() {/* ... */},
        concat: function(first, second) {/* ... */}
    }
});
Jethro Larson
@jethrolarson
Oct 22 2015 23:51
In my implementation I just make the equivalent of register return a new instance of the interface
Scott Sauyet
@CrossEye
Oct 22 2015 23:51
this would then also imply that Maybe is a Setoid, using concat, by the extension.
Yes, that's what I meant by the Bilby reference. I could do the same thing, and probably will. It seems a minor problem for now.
Jethro Larson
@jethrolarson
Oct 22 2015 23:52
Surely minor compared to not launching :)
Scott Christopher
@scott-christopher
Oct 22 2015 23:52
@CrossEye: Do you use the Functor and Monoid keys for anything?