These are chat archives for ramda/ramda

16th
Apr 2016
Hardy Jones
@joneshf
Apr 16 2016 01:30
You have the opportunity to use clojure and you're thinking of using javascript instead?
I strongly urge you to reconsider.
Jordan Rome
@jordalgo
Apr 16 2016 11:27
I'm sure this has been asked a million times. But is there a performance or other benefit to using Ramda's curry or partial instead of native bind ?
Example:
function createStream() {
var s = {};
s.map = map.bind(this);
// or
s.map = R.curryN(2, map)(s);
return s;
}
Aldwin Vlasblom
@Avaq
Apr 16 2016 12:33
@jordalgo
  • Currying is about making functions "auto-bind", rather than having to partially apply them yourself every time.
  • Ramda's curry comes with placeholder support, so you can partially apply a function with its second and third argument, but not its first. This is something you couldn't do with native bind.
  • Native bind is dog slow, I wouldn't be surprised if Ramda's partial, or even on-the-fly currying like you do in your example, would still be faster than native bind.
  • Don't think too much about "micro performance". Try to think about what would make your code more expressive and more maintainable first.
Jordan Rome
@jordalgo
Apr 16 2016 12:48
@Avaq Thanks! To be honest I'm not sure what makes it more maintainable in this case. If I require in Ramda's curry that's a dependency I now have. I'm just trying to figure out if it's worth it if I have a stream object that I add a lot of methods to in this fashion.
Aldwin Vlasblom
@Avaq
Apr 16 2016 12:55
I personally wouldn't curry methods, though I prefer not using methods to start with. The way you are using curryN in your example has no benefits over using partial, or bind besides the placeholder thing.
Jordan Rome
@jordalgo
Apr 16 2016 12:57
@Avaq Yeah, that makes sense. Just curious, what do you mean when you say you prefer not to use methods ?
In the case of this stream maker, I want to be able to chain.
Raine Virta
@raine
Apr 16 2016 13:39
methods vs. functions
Jordan Rome
@jordalgo
Apr 16 2016 13:50
@raine But functions aren't chainable if you want to do something like
createStream()
.map(add1)
.scan(sum)
.forEach();
Aldwin Vlasblom
@Avaq
Apr 16 2016 13:55

@jordalgo We use compose and pipe for stuff like that:

const processStream = pipe(
  map(add(1)),
  scan(sum),
  map(tap( doSideEffect ))
);

processStream(createStream())

I recommend you watch "Hey, underscore, you're doing it wrong", a talk by Brian Lonsdorf on the matter. :)

This example also shows function currying at its best: I'm partially applying map, add, scan, and tap, which is possible because each of them is curried.
Jordan Rome
@jordalgo
Apr 16 2016 14:01
@Avaq I've seen it. It's a great talk. And I use R.pipe/compose a lot in production. Just to give a bit more context, I'm working on (mostly just for fun) a stream/observable lib. And the example above (using Pipe) doesn't allow you to set an observer on that transformed value coming out of the stream. Or rather it might require some clunky looking code.
James Forbes
@JAForbes
Apr 16 2016 14:09
@jordalgo if the observable lib implements fantasy land, it's pretty seamless. I use pipe with flyd streams
@jordalgo here is an example using streams/observables with pipe to make the gamepad api a bit more palatable
function Gamepad(options){
    var index = options.index || 0
    var end = f.stream()
    var deadzone = 0.25

    var streams = {}

    var poll = f.endsOn(end, streams.poll = f.every(options.poll || 100))

    streams.state = f.combine(function(){
        return navigator.getGamepads()[index]
    }, [poll])

    var extractButtons =
        R.pipe(
            R.prop('buttons')
            ,R.map(R.pick(['pressed', 'value']))
        )

    streams.axes = f.dropRepeats(
        streams.state.map(
            R.pipe(
                R.prop('axes')
                ,R.map(
                    R.when(
                        R.pipe(Math.abs, R.lt(R.__, deadzone)),
                        R.always(0)
                    )
                )
            )
        )
    )
    streams.buttons = f.dropRepeats(streams.state.map(extractButtons))
    streams.timestamp = f.dropRepeats(streams.state.map(R.prop('timestamp')))


    return {
        streams: streams
        ,end: end
    }
}
I guess the key point here is, I didn't need flyd to implement some special methods for accessing sub properties, or conditionally modifying streams, because it gave me map, and from there I could compose whatever transformation in userland
Jordan Rome
@jordalgo
Apr 16 2016 14:18
@JAForbes Flyd is an awesome stream lib, probably my favorite ATM. But it still adds base methods to the stream object like map and ap.
The only thing I'm trying to reconcile, that I feel is a bit strange, is that Flyd streams have state, such as their current value. Also, if your stream obj has a few more methods, the code looks cleaner and is easier to follow. Though it does add bloat to your lib.
Jordan Rome
@jordalgo
Apr 16 2016 14:23
The trade off that Flyd made was having a folder of modules that you could import specifically to keep the core stream lib small but you sacrifice the ability to chain by leaving them as functions instead of stream methods.
James Forbes
@JAForbes
Apr 16 2016 14:28
@jordalgo It reluctantly adds base methods to be compatible with fantasy land
But I could have just as easily said R.map instead of stream.map
I don't think chaining those module functions would ever be a good idea, in most cases that would create many intermediary streams, which is a lot slower than a single composed transformation.
You could be careful about it, but I think a chaining api would encourage practices that really hurt performance

https://github.com/paldepind/flyd#streammapf

stream.map(f)
Returns a new stream identical to the original except every value will be passed through f.
Note: This function is included in order to support the fantasy land specification.

emphasis mine
Jordan Rome
@jordalgo
Apr 16 2016 14:37
@JAForbes It's definitely a concern about the creating of intermediary streams. Though I wonder how much memory we're really talking about here. Also every flyd module creates a new stream using flyd's combine method.
And as long as you properly remove the references to those intermediary streams when a stream ends/completes. It might be fine. You're only creating a new stream once when you call map, for example, and not every time a value passes through.
RxJs and Highland, I believe, both have chainable apis that create intermediary streams/objects.
James Forbes
@JAForbes
Apr 16 2016 14:55

Method chaining is a lot easier on non functional programmers. And there are enough complex concepts with observables as it is.
That is 1 reason, to use chaining. But I think the reason Rx and highland and others are using chaining, is because for a lot of JS programmers, composition is a new, or a newly rediscovered approach. And I hope, we are going to transition away from chaining towards composition.

Rx in particular, is a cross language project, and composition / currying is not as simple in Java or C#, while chaining is pretty much guaranteed to work in any language they are targetting or plan to target. So that is another reason to use chaining.

For me, the memory budget matters. And I find I get great performance using streams for intricate / complex situations, by only creating a new stream when I want to expose a particular level of the transformation to an external party. I can imagine situations where it really doesn't matter either way, certainly.

I think the real reason the modules in flyd are in a separate core, is to demonstrate and encourage the creation of your own operators on the fly. Flyd is so small and fast, but its also built for composition. I think the statefulness is there to make the creation of new operators trivial, not so much for retrieving the stream's state in the application space.

Jordan Rome
@jordalgo
Apr 16 2016 15:10

I completely agree that observables are very complex (unnecessarily so). Big part of why I think Flyd is great.

One of the things I've been tinkering with, which seems to be a problem if you only use composition for streams, is error handling and bubbling. If you're transforming values in a compositional way via Ramda or Lodash then you can't really bubble up errors to the subscribers of these transformed streams. You can start using Monads like Left and Maybe but that might be a bit too complicated for someone starting out with functional JS.

I was kind of considering an opt-in style chainable API where you can specify the modules/functions up front that you want to make available as methods on your stream/observable objects. You still have intermediary streams objects getting created but they can be much smaller than including in the whole kitchen sink of transformations.

Stefano Vozza
@svozza
Apr 16 2016 16:04
You don't have to use chaining in Highland, all those methods are exposed on the top level as autocurried functions that take a stream as their last argument so you can use compose on them.
Jordan Rome
@jordalgo
Apr 16 2016 18:46
@svozza Maybe I'm not looking in the right place but where are you seeing that?
Stefano Vozza
@svozza
Apr 16 2016 18:47

i'm a contributor to the highland lib, the chaining is completely optional.

hl([1,2,3,4,5]).map(R.add)

can be written

hl.map(R.add, hl([1,2,3,4,5]));
Jordan Rome
@jordalgo
Apr 16 2016 19:02
@svozza That's cool. Thanks! Do you worry at all about the memory footprint of highland streams if you are creating a lot of them via map, filter or similar transformations?
Stefano Vozza
@svozza
Apr 16 2016 19:56
no, it's never been a problem but we'd be running these on decent sized ec2 instances or aws lambda functions
Jordan Rome
@jordalgo
Apr 16 2016 20:03
I was more talking about in the browser rather than on the server.
Also, do you find yourself opting more for the compositional style when you can chain?
Stefano Vozza
@svozza
Apr 16 2016 21:48
i don't do any frontend development so i don't have any experience with that side of things unfortunately. i used to use the compositional style exclusively but the other devs in the team were more comfortable with chaining so for consistency we adopted that syle