These are chat archives for ramda/ramda

16th
Mar 2017
Andy Hale
@anotherhale_twitter
Mar 16 2017 02:59

I am new to ramda but loving it. I am stuck on trying to filter a list of objects on a date property.
Say I have this list:
const events = [{id:1, date:"2017-02-13T00:00:00.000Z"},{id:2, date:"2017-03-23T00:00:00.000Z" }]

I am trying to filter it with these filters:

const dateFilter = R.curry((startDate,endDate, date) =>{
return moment(date).isBetween(moment(startDate),moment(endDate))
})
const appDateFilter = dateFilter(appStartDate, appEndDate)
const activeDateFilter = R.filter(R.apply(appDateFilter,R.prop('date')))
const activeEvents = activeDateFilter(events)

I am not sure about the activeDateFilter if that is how I invoke my appDateFilter with each date prop.
Lucas Schejtman
@lucasschejtman
Mar 16 2017 03:06
you could do
const activeDateFilter = R.filter(R.compose(appDateFilter, prop('date')));
Andy Hale
@anotherhale_twitter
Mar 16 2017 03:07
Awesome! I will give that a try.
Andy Hale
@anotherhale_twitter
Mar 16 2017 03:44
That worked like a charm. I am still trying to learn when to use which functions.
Lucas Schejtman
@lucasschejtman
Mar 16 2017 03:50
takes a while, use this room to ask questions
Andy Hale
@anotherhale_twitter
Mar 16 2017 04:56

How about: trying to find the union on a property between to lists?
If the events had a type property and I have a list of selected active types:
const events = [{id:1, date:"2017-02-13T00:00:00.000Z", type: "appointment"},{id:2, date:"2017-03-23T00:00:00.000Z", type: "anniversary" }]
const selectedTypes = [{type:"appointment'},{type:"notification"}]

I have tried R.intersectionWith(R.eqBy(R.prop('type'))) but that is not right...

James Forbes
@JAForbes
Mar 16 2017 05:15
@anotherhale_twitter hey could you try wrapping your code in backticks? ```
Makes it easier for us to read :)
@anotherhale_twitter how do you measure equality for your union? The entire record? Or just the fact it has the type?
Andy Hale
@anotherhale_twitter
Mar 16 2017 05:18
sorry about that. I will use back ticks for code. I would like the union to be the entire matching event records
James Forbes
@JAForbes
Mar 16 2017 06:14
@anotherhale_twitter no worries, could you post some input and some expected output?
Andy Hale
@anotherhale_twitter
Mar 16 2017 06:15
sure
Andy Hale
@anotherhale_twitter
Mar 16 2017 06:34

`
let R = require('./ramda/dist/ramda.js')

const events = [
{id:1, date:"2017-02-13T00:00:00.000Z", type: "appointment"},
{id:2, date:"2017-02-14T00:00:00.000Z", type: "anniversary" },
{id:3, date:"2017-02-15T00:00:00.000Z", type: "notification"},
{id:4, date:"2017-02-23T00:00:00.000Z", type: "task" }
{id:5, date:"2017-02-27T00:00:00.000Z", type: "task" }
]
const selectedTypes = [{type:"appointment"},{type:"notification"}]
const selectedEventsFilter = R.intersectionWith(R.eqBy(R.prop('type')))
const selectedEvents = selectedEventsFilter(events)(selectedTypes)

// I expect selectedEvents to contain the two match event records :
// [
// {id:1, date:"2017-02-13T00:00:00.000Z", type: "appointment"},
// {id:3, date:"2017-02-15T00:00:00.000Z", type: "notification"},
// ]
`

hmm let me try again
let R = require('./ramda/dist/ramda.js')

const events = [
    {id:1, date:"2017-02-13T00:00:00.000Z", type: "appointment"},
    {id:2, date:"2017-02-14T00:00:00.000Z", type: "anniversary" },
    {id:3, date:"2017-02-15T00:00:00.000Z", type: "notification"},
    {id:4, date:"2017-02-23T00:00:00.000Z", type: "task" }
    {id:5, date:"2017-02-27T00:00:00.000Z", type: "task" }    
]
const selectedTypes = [{type:"appointment"},{type:"notification"}]
const selectedEventsFilter = R.intersectionWith(R.eqBy(R.prop('type')))
const selectedEvents = selectedEventsFilter(events)(selectedTypes)
// I expect selectedEvents to contain the two match event records :
// [
//     {id:1, date:"2017-02-13T00:00:00.000Z", type: "appointment"},
//     {id:3, date:"2017-02-15T00:00:00.000Z", type: "notification"},
// ]
James Forbes
@JAForbes
Mar 16 2017 06:56
@anotherhale_twitter what happens if there are multiple appointment or notifications?
Andy Hale
@anotherhale_twitter
Mar 16 2017 06:58
I would like to gather all of the events that match the type property from the list of selectedTypes.
I suppose it is actually just a filter where I just map over the list of selectedTypes and OR them together?
James Forbes
@JAForbes
Mar 16 2017 08:02

So let's start with filtering by one type. @anotherhale_twitter

R.filter( propEq('type', 'notification'), events )

Then filtering by 2 types (manually)

R.filter( either( propEq('type', 'notification'), propEq('type', 'appointment') ), events )

We can make it less hard coded:

const matchTypes = 
  pipe(
    map(propEq('type')) // create our predicates
    ,anyPass // if any of the above predicates pass we're ok
    ,filter // filter by the above
  )

matchTypes(['notification', 'appointment'])(events)
You mentioned a union, which was making me think there were two lists. Are there?
If so, we can just concat them beforehand
Robert Mennell
@skatcat31
Mar 16 2017 17:01
@JAForbes purely stylistically, why would you use pipe over compose in this situation?
const matchTypes = 
  compose(
    filter // filter and array
    ,anyPass // if any of the redicates pass we're ok
    ,map(propEq('type')) // create our predicates
  )

matchTypes(['notification', 'appointment'])(events)
Ryan Zeigler
@rzeigler
Mar 16 2017 17:08
i find myself prefering compose for composing a short number of functions (2 - 3). If it gets longer, I find it easier to read top -> bottom the order in whicht he functions are applied so I don’t need to orient myself the end of the group
so for lots of functions or functions with long names I prefer pipe
Robert Mennell
@skatcat31
Mar 16 2017 17:14
@rzeigler I doubt either way we'd notice any real performance difference
Ryan Zeigler
@rzeigler
Mar 16 2017 17:14
you wouldn't
in fact, I think both are implemented in terms of pipe2 internally
Robert Mennell
@skatcat31
Mar 16 2017 17:15
actually compose is done in terms of pipe
Robert Mennell
@skatcat31
Mar 16 2017 17:22
so if you're doing an absurd ammount of piping you'll see some performance issues(reverse array) with compose... so just use pipe?
Ryan Zeigler
@rzeigler
Mar 16 2017 17:35
i dont know how much it matters
in normal use cases
unless you are doing some really wild point free stuff
i have no evidence for that assertion though
Robert Mennell
@skatcat31
Mar 16 2017 17:48
I really doubt you'll see an issue
Galileo Sanchez
@galileopy
Mar 16 2017 18:51
is there something like union-type case in ramda?
Denis Stoyanov
@xgrommx
Mar 16 2017 18:52
@galileopy what do u mean?
Galileo Sanchez
@galileopy
Mar 16 2017 18:53
I need to implement something like a switch, perhaps cond is the tool for the job
I have a list of possible events that I can receive from a device, some events has data attached, and some don't
In order to parse the data I need to first know which function is going to do the parsing
and that depends on the event
if it is X, parse an Int, if it is Y, parse an ascii string, if it is Z parse with this bitmap parser
I was thinking on using union-type to define the events and case to get the data parsing function
Denis Stoyanov
@xgrommx
Mar 16 2017 18:57
@galileopy for example Either is union type or sum type (ADT) https://github.com/xgrommx/practical-functional-programming/blob/master/either/index.js#L5-L8 (use union-type)
Galileo Sanchez
@galileopy
Mar 16 2017 19:01
Isn't union-type overkill for that kind of things?
Ryan Zeigler
@rzeigler
Mar 16 2017 20:02
generally, ad-hoc union types with some kind of discriminator field for cond has always been enough for me
Matthew Willhite
@miwillhite
Mar 16 2017 20:38
Dealing with lot’s of unstructured data in a large application has led me on a crusade to start giving “shape” to data wherever I see it being passed around to different contexts
I’m using daggy right now, but the union-type lib is also great
Rob Hilgefort
@rjhilgefort
Mar 16 2017 21:19
This obviously doesn’t work- as always isn’t called with whenTrueValue like it does in the top implementation. Is there a way to do something like that I’m trying to do?
const whenPropTrue = curry((key, whenTrueValue, data) =>
  whenPropEq(key, true, always(whenTrueValue), data),
);
const whenPropTrue = whenPropEq(__, true, always(__), __);
Thanks in advance for the help
Jonah
@jonahx
Mar 16 2017 22:28

Given a list of numbers, I want to return a list of pairs, where the first item in the pair is the original number in the list, and the 2nd item is the number of times that number has appeared in the list so far. Thus the first time a number appears in the list, its 2nd pair item will be 1, the 2nd time it appears, it will be 2, and so on.

Here's a solution that I'm not happy with. How can I improve it? I would like to avoid any solutions which involve reducing an object which holds item counts.

const numbers = [1, 2, 2, 4, 1, 2];
const partials = tail(scan(flip(append), [], numbers));
const counts = map(x => length(filter(equals(last(x)), x)), partials);
zip(numbers, counts)
// => [[1, 1], [2, 1], [2, 2], [4, 1], [1, 2], [2, 3]]
Galileo Sanchez
@galileopy
Mar 16 2017 22:33
@miwillhite yeah, that's why I was thinking on using union-type, but was not necessary in this case, it developed the habit to give shape and name to the data I use, after refactoring some old code and noticing how it really improves readability
Galileo Sanchez
@galileopy
Mar 16 2017 22:39
assoc doesn't work on functions
@ram-bot assoc("prop", 0, identity)
This message was deleted
@ram-bot help
Denis Stoyanov
@xgrommx
Mar 16 2017 22:45
@galileopy I got {"prop": 0}
intended behaviour apparently. Functions don't have iterable properties unless they get set.
http://ramdajs.com/repl/#?identity.test%20%3D%20%27a%27%0Aassoc%28%22prop%22%2C%200%2C%20identity%29
James Forbes
@JAForbes
Mar 16 2017 22:58

@skatcat31 re: pipe vs compose...

It's purely a modelling thing. I tend to prefer pipe in most cases but there are exceptions.

e.g. say I am trying to guarantee various entry points to a system are the same data type, to emphasise it, I might want to use compose so I can see all the compositions end with the same function.

e.g.

compose( String , .... )

I need to know its a String, and I want to place emphasis on that.

vs:

pipe(...., String)

If the pipeline is pretty long, the fact it ends up being a string takes longer to recognise because its last.

Another case, I might have an event handler, and I want to write the result to a stream, before I write I need to access extra data and transform it.

If I want to emphasise that destination is a stream I'd use compose, if I want to emphasise the transformation, I'd use pipe.

// emphasise where the value is going...
oninput: compose( valueStream, Number, path(['currentTarget', 'value']) ) 

// emphasise the transformation 
oninput: pipe( path(['currentTarget', 'value']), Number, valueStream )

For me, it all depends what is the most/least suprising, or what I have the most confidence in, what the surrounding code is doing etc.

I think multi line composes can read really well in certain contexts. Particularly for modelling heirarchies.

Think of some hyperscript of a table:

compose(
  tbody
    map(
       tr(
          map(
             td
          )
       )
    )
)

Perfectly mirrors the resulting html:

<tbody>
  <tr>
    <td></td>    <td></td>    <td></td>
  </tr>
  <tr>
    <td></td>    <td></td>    <td></td>
  </tr>
</tbody>

So no hard and fast rules, but that's the aesthetic I've landed on to date.

Matthew Wagerfield
@wagerfield
Mar 16 2017 23:02
hey everyone, how would I check that a value is an Object {}, but not an Array?
I get true for is(Object, [])
Margaret
@Margaret2
Mar 16 2017 23:05
@JAForbes thanks for that long post, that was interesting :D
James Forbes
@JAForbes
Mar 16 2017 23:07
@Margaret2 :sparkles: :)
Robert Mennell
@skatcat31
Mar 16 2017 23:08
@JAForbes yes, readability is important
James Forbes
@JAForbes
Mar 16 2017 23:09
@skatcat31 it's interesting how both pipe and compose can seem "backwards" in different contexts :)
Lucas Schejtman
@lucasschejtman
Mar 16 2017 23:09
@wagerfield welcome to JS :smile: . You could do something like R.allPass([is(Object), is(Array)])
James Forbes
@JAForbes
Mar 16 2017 23:10

Yeah or in the case of finding only an object maybe and( complement(is(Array)), is(Object) )

Wishcomplement wasn't so long :(

Robert Mennell
@skatcat31
Mar 16 2017 23:10
@JAForbes it is isn't it? I'd be hard pressed to find a hard rule that would make sense in 100% of cases. It's more like each case makes sense... so don't forget some comments...
James Forbes
@JAForbes
Mar 16 2017 23:10
@skatcat31 ahhhh I think comments are a really a bad idea generally :D
Denis Stoyanov
@xgrommx
Mar 16 2017 23:11
@jonahx
reduce((a, x) => {
  let el = findLast(compose(equals(x), head), a)
  return append(el ? [x, compose(add(1), last)(el)] : [x, 1], a)
}, [], [1, 2, 2, 4, 1, 2])
Robert Mennell
@skatcat31
Mar 16 2017 23:11
@JAForbes same, but if something is hard enough to reason at first glance... comments are essential.
James Forbes
@JAForbes
Mar 16 2017 23:12
haha still disagree

in that context especially, I want the dev to read the code, not the comments.

Its very likely the comments will have long gone out of sync with the code which just leads to a game of telephone

Robert Mennell
@skatcat31
Mar 16 2017 23:14
@wagerfield depends on if you want to check for null as well
R.allPass(
  is(Object),
  compliment( is( Array ) ),
  compliment( isNil )
)
James Forbes
@JAForbes
Mar 16 2017 23:14

comments are an escape hatch / or a crutch so we don't feel we need to write simpler code

I mean there's always exceptions, but I think those exceptions are very rare

@skatcat31 true!
Robert Mennell
@skatcat31
Mar 16 2017 23:15
@JAForbes agreed. I'm saying in those rare cases
James Forbes
@JAForbes
Mar 16 2017 23:15
or regexp! typeof /hi/ //=> object
Matthew Wagerfield
@wagerfield
Mar 16 2017 23:16
thanks guys, this is great :)
James Forbes
@JAForbes
Mar 16 2017 23:17
@wagerfield http://ramdajs.com/docs/#type is pretty great for this
much easier than handling all the cases where is(Object) will return true
Matthew Wagerfield
@wagerfield
Mar 16 2017 23:18
oh snap!
even better
thanks @JAForbes :)
James Forbes
@JAForbes
Mar 16 2017 23:18
:)
Matthew Wagerfield
@wagerfield
Mar 16 2017 23:18
new to Ramda, so still learning its API
James Forbes
@JAForbes
Mar 16 2017 23:18
I forgot type was there until just now :)
Matthew Wagerfield
@wagerfield
Mar 16 2017 23:22
so if I have an object which may or may not have a property on it and I want to check that when it does have a property that it is of type({}) => 'Object', otherwise default to an empty object, what's the most elegant way of doing that
I was using propOr(), but that doesn't validate the value type
James Forbes
@JAForbes
Mar 16 2017 23:25

Well, first thing would be try to push the code that isn't sure what type an input is to the very edge of the system. But assuming that's already happening.

Is the property dynamic, or do you always know the property name where this might happen?

Matthew Wagerfield
@wagerfield
Mar 16 2017 23:27
I always know the prop name
const config = {
  // this prop may or may not exist
  rules: {} // when it does it must be of type "Object"
}
James Forbes
@JAForbes
Mar 16 2017 23:29
You could do something like
evolve({ 
  someProperty: ifElse( 
   pipe(type,  equals('Object'))
      , identity
      , always({})
   )
  )
})
You could substitute evolve for assoc or lensProp, but that should work
@wagerfield hey can I ask, what other types could the property ever be?
Matthew Wagerfield
@wagerfield
Mar 16 2017 23:31
well someone could pass anything — an array, number or whatever, but the only valid type is an object
James Forbes
@JAForbes
Mar 16 2017 23:31
@wagerfield but why would they pass anything?
Matthew Wagerfield
@wagerfield
Mar 16 2017 23:31
because they might not know what they're doing :)
James Forbes
@JAForbes
Mar 16 2017 23:31
haha
Matthew Wagerfield
@wagerfield
Mar 16 2017 23:32
thanks for the above
James Forbes
@JAForbes
Mar 16 2017 23:35
@wagerfield no problem
James Forbes
@JAForbes
Mar 16 2017 23:43

So generally, when writing functional code we do not code defensively against unexpected errors. If someone passes input that is not as documented then we should crash and not try to absorb it.

There's this idea of expected and unexpected errors I think its worth reading in this case @wagerfield

Of course, every program is different and you have your own context, but I'm proposing, you probably dont need to check if someone is using your program in a way that is counter to its documented inputs.
Robert Mennell
@skatcat31
Mar 16 2017 23:45
HOW DO YOU THUMBS UP THINGS IN GITTER! ARGH!!!
Margaret
@Margaret2
Mar 16 2017 23:46
:+1: => :+1:
Robert Mennell
@skatcat31
Mar 16 2017 23:46
:+1:
Margaret
@Margaret2
Mar 16 2017 23:46
:smile:
Piet Vandeput
@piet-v
Mar 16 2017 23:48
Hi guys, currently doing some javascript kata exercises using Ramda and I've hit a snag. Goal is to count the occurences of pirate and pile_of_gold
[ 'heap_of_pirates',
  'pile_of_gold',
  'pirate',
  'stack_of_gold',
  'pirate',
  'pirate',
  'pile_of_gold',
  'pile_of_mud',
  'pile_of_gold',
  'stack_of_gold',
  'stack_of_gold',
  'pile_of_gold',
  'heap_of_pirates',
  'pile_of_mud',
  'pirate',
  'pile_of_gold' ]
would this be a good solution? or does anyone see something I could improve on?
const R = require('ramda');
const secretMap = R.pipe(
  R.flatten,
  R.countBy(R.identity),
  R.props(['pirate', 'pile_of_gold']),
  R.map(R.defaultTo(0)),
  ([pirates, gold]) => ('count of pirates: ' + pirates + ' and the count of gold piles: ' + gold)
);
James Forbes
@JAForbes
Mar 16 2017 23:51

If you look through ramda's codebase, you'll see the only times it checks types of inputs is if Ramda is trying to pretend JS has a different underlying api, e.g. it pretends Array has a .ap method, or {} has a .map. But that behaviour is documented, so its ok.

But if you pass weird inputs to Ramda it will let you do it, it won't throw an internal error, or even check. E.g. if I pass null to nth nth(null, null) it just crashes.