These are chat archives for ramda/ramda

16th
Jun 2016
Baqer Mamouri
@bmamouri
Jun 16 2016 04:48

Hey guys, how can I get the count of the previous function call. I am trying to use splitEvery function to split the elements in half:

const images = ['1.jpg’, '2.jpg’, '3.jpg’, '4.jpg’, '5.jpg',]

R.pipe(
  R.slice(1, Infinity),
  R.splitEvery(R.length),
)(images)

In R.splitEvery function I would like to know the length of the list that is piped from the last function.

James Forbes
@JAForbes
Jun 16 2016 05:19
@bmamouri I think you might need a converge R.converge(R.splitEvery, [R.length, R.identity])
Plusb Preco
@preco21
Jun 16 2016 05:21
Hello everyone! Can I ask on here about functional programming in JS, but not related ramdajs?
James Forbes
@JAForbes
Jun 16 2016 05:24
Yeah I think that's fine. @preco21
Plusb Preco
@preco21
Jun 16 2016 05:27
@JAForbes Thanks :)
James Forbes
@JAForbes
Jun 16 2016 05:28
:)
Plusb Preco
@preco21
Jun 16 2016 05:32

Recently, I have involved that how can I make the shuffle algorighm with functional programming. So, first I wrote:

// refs: https://bost.ocks.org/mike/shuffle/
function shuffle(arr) {
  let ret = [...arr];
  let current = ret.length
  let newIndex = 0;

  while (current) {
    newIndex = Math.trunc(Math.random() * current--);
    [ret[current], ret[newIndex]] = [ret[newIndex], ret[current]];
  }

  return ret;
}

to:

function shuffle2(arr) {
  const temp = [...arr];

  return arr.reduce((res, elem, index, orig) => {
    let [nextElem] = temp.splice(Math.trunc(Math.random() * (temp.length - index)), 1);

    res.push(nextElem);

    return res;
  }, []);
}

But there is variable temp is exist, so I thought that is not pure functional programming. Is it okay or anyone has better solution?

James Forbes
@JAForbes
Jun 16 2016 05:33
well, you're first function is functional because it is pure.
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:33
yeah, the first function is more readable and prolly more performant
Plusb Preco
@preco21
Jun 16 2016 05:35
Hmm.. I thought only higher-order functions are pure. Could you explain what is real functional programming? :)
James Forbes
@JAForbes
Jun 16 2016 05:35
well I suppose shuffling isn't functional per se, because we using Math.random which is a side effect
Plusb Preco
@preco21
Jun 16 2016 05:35
Someone said to me, in functional programming, we should not use loops.
James Forbes
@JAForbes
Jun 16 2016 05:35
@preco21 yeah sure :)
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:36
lol
you can use loops :+1:
Plusb Preco
@preco21
Jun 16 2016 05:36
ah
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:36
it depends really
are you are js programmer ?
Plusb Preco
@preco21
Jun 16 2016 05:37
yup, I'm js programmer, also noder
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:38
if you are js programming - I would strongly suggest reading this book to get started
it uses an older library - underscore
James Forbes
@JAForbes
Jun 16 2016 05:38

people will argue about exact definitions, but functional programming is all about ... functions

functions that do not alter any existing state, functions that create new data based on transforming inputs.

you're shuffle function is a perfect example. It receives an array, and creates a new array that has been reordered.

A non-functional approach would have modified the original array.

Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:38
but ramda is almost a child of underscore
@JAForbes I agree :+1:
haskell style functional programming is pushing it too far - sometimes a for loop is just simpler.
Plusb Preco
@preco21
Jun 16 2016 05:40
Aha, I got it.
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:40
allow mutation locally
James Forbes
@JAForbes
Jun 16 2016 05:40
functional programming is a term with many layers. The first and most important aspect is that we use functions that are pure. That means we don't mutate state, and we always return the same thing given the same inputs.
Plusb Preco
@preco21
Jun 16 2016 05:40
But I don't like underscore tho :) but lodash
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:40
you can use lodash
the book explains most fp concepts
in js terms
Plusb Preco
@preco21
Jun 16 2016 05:41
hmm.. what is ramdajs? which is am I here?
Is it same purpose of lodash or underscore?
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:43
its 90% same - with the exception that ramda has no impure functions
Plusb Preco
@preco21
Jun 16 2016 05:43
such as reverse() or sort()?
Joy Krishna Mondal
@JoyKrishnaMondal
Jun 16 2016 05:44
yep
Plusb Preco
@preco21
Jun 16 2016 05:44
ah, if so http://ramdajs.com/0.21.0/docs/#sort this method is 100% pure?
i got it
Seems similar with ImmutableJS (https://facebook.github.io/immutable-js/)
James Forbes
@JAForbes
Jun 16 2016 05:45

Another layer is composition.
Composition is creating new functionality by plugging other functions together.

Imperative code and thereforewhile loops do not compose well. While say reduce and map do. So it is true that in fp we do not tend to use loops. But the reason isn't because loops aren't pure, its because there are other abstractions related to iteration that compose more elegantly.

Plusb Preco
@preco21
Jun 16 2016 05:46
@JAForbes Is composition means currying?
James Forbes
@JAForbes
Jun 16 2016 05:46
Currying makes composition easier.
Plusb Preco
@preco21
Jun 16 2016 05:46
Ah
James Forbes
@JAForbes
Jun 16 2016 05:47
Are you familiar with currying?
Plusb Preco
@preco21
Jun 16 2016 05:48
Not really sure, but I have used it.
James Forbes
@JAForbes
Jun 16 2016 05:49
Imagine that any function you use, if you do not supply all the arguments, the function will wait for the remaining arguments before executing.
And the way it waits, is by returning a new function that accepts the remaining parameters
In some languages this is the default behaviour
Plusb Preco
@preco21
Jun 16 2016 05:50
Ah I see
James Forbes
@JAForbes
Jun 16 2016 05:50
In JS we can use a function that turns any function into a curried function
Plusb Preco
@preco21
Jun 16 2016 05:50
I remember that I used it on build scripts
James Forbes
@JAForbes
Jun 16 2016 05:50
In ramda, every function is curried by default
So its a lot like using one of those languages where currying is native
So here is why currying is so valuable
Plusb Preco
@preco21
Jun 16 2016 05:52
Hmm.. that is cool
James Forbes
@JAForbes
Jun 16 2016 05:52
If we can leave out the data parameter of a function we can pipe a bunch of functions together that will each have a turn at transforming that data
And that is why currying makes composition simpler
Plusb Preco
@preco21
Jun 16 2016 05:54
But I'm still not sure about when I should use higher order functions instead of loops.
James Forbes
@JAForbes
Jun 16 2016 05:54
great point
Plusb Preco
@preco21
Jun 16 2016 05:55
some other examples:
function diag1(arr, dimension) {
  let res = [];

  for (let i = 0; i < dimension * 2 - 1; i++) {
    const z = i < dimension ? 0 : i - dimension + 1;
    let ret = [];

    for (let j = z; j <= i - z; j++) {
      ret.push(arr[j][i - j]);
    }

    res.push(ret);
  }

  return res;
}

function diag2(arr, dimension) {
  return Array.from({length: dimension * 2 - 1})
    .map((elem, index) => {
      const start = index < dimension ? 0 : index - dimension + 1;

      return Array.from({length: index - start * 2 + 1})
        .map((elem2, index2) => {
          const offset = start + index2;
          return arr[offset][index - offset];
        });
    });
}
this method gets array's diagonals.
James Forbes
@JAForbes
Jun 16 2016 06:00
These are great examples. This exactly the sort of code where an imperative approach is probably simpler. But you are still writing pure functions which is great.
I think a good guideline for when to stick to imperative iteration, is when accessing the index is absolutely necessary for the algorithm.
But if I want to transform a value uniformly (irregardless of the structure), then you can get a lot of value by avoiding loops.
When I say uniformly, I mean, you apply the same function to each value. And the function determines its output based on that value.
There are techniques for doing this sort of thing with functional programming, (like converting the index to some data via a tuple as an example). But generally accessing the index in functional programming is an anti pattern.
Plusb Preco
@preco21
Jun 16 2016 06:05

Aha. I got it.

Hmm.. I heard about totally pure functional language such as Haskell. But if so, how can I write code in this case which is imperative approach.

James Forbes
@JAForbes
Jun 16 2016 06:05
Well in haskell, you'd probably define that code using recursion.
E.g. each row in the matrix will remove one extra item from the list each time. The first time the head of your list is the diagonal. The second time you run the same function, but you take the head twice. And then the third time, 3 times. Etc.
But I'm not sure specifically about haskell, I've yet to dive in. I'm sure someone else here could come up with a succinct functional approach to that function. I personally think though on your journey through functional programming stick to the guideline I mentioned above for a while. Focus on composition and purity. And if its easier to use a loop to do something, that's fine.
Plusb Preco
@preco21
Jun 16 2016 06:11
@JAForbes @JoyKrishnaMondal Thank you all guys for the helps :smile: Now I feel clarified!
James Forbes
@JAForbes
Jun 16 2016 06:11
:)

Let's say you have your 2d array. And you want to filter for values > 5 without flattening the list.

var f = R.map(R.filter( R.gt(R.__, 5)))

f(matrix)

Not too hard to do with a loop. But thanks to currying and composition. Its succinct with fp

Notice how each function is waiting for data

R.gt is waiting for a number to compare

R.filter is waiting for a list to filter

And R.map is waiting for a structure to traverse

We can just plug them together because they are curried.

James Forbes
@JAForbes
Jun 16 2016 06:16
If they were not curried, we'd have to do something like this:
var f = function(list){
   return R.map(function(row){
      return R.filter(function(value){
         return R.gt(value, 5)
      }, row)
   }, list)
}
A lot of extra ceremony just to pass the data around. And that is why currying is so powerful
James Forbes
@JAForbes
Jun 16 2016 06:22

Because we've abstracted away the specific iteration semantics. We can now replace our matrix with an object of lists. Or even a list of objects.

f({
  a: [5,4,6]
  ,b: [7,8,9]
}) //=> { a: [6], b: [7,8,9] }
f([
  { a: 5, b: 6, c: 7}
  ,{ a: 3, b: 8 }
]) //=> [ { b: 6, c:7 }, { b: 8 }]

Now we could also replace our arrays and objects with a tree, or a Future, or anything that supports map and reduce.

Plusb Preco
@preco21
Jun 16 2016 06:23
Ah awesome
James Forbes
@JAForbes
Jun 16 2016 06:23
And that is why we tend to avoid loops. By writing imperative code we lock ourselves into specific data structures.
A lot of the time using Ramda feels like using a terse query language
And some data structures do not facilitate manual iteration because they are async. So there's that too.
If you've ever used a while loop with a stream in nodejs you'll know how awkward imperative programming is when you are writing async code
James Forbes
@JAForbes
Jun 16 2016 06:30

I should say, you can do a lot of this with lodash/fp. Lodash won't dispatch to custom types like Ramda will.

(I've opened an issue asking for them to support that lodash/lodash#2406)

But other than that, its a pretty similar experience.

Plusb Preco
@preco21
Jun 16 2016 06:37

Hmm, I see. @JAForbes Thanks!

Btw, I have few question:

  1. When I need index-based loops, should I use loops instead of Array.from({length: n}) which is generate array.
  2. When I handle matrix (2d array), this seems should handle with index, how can I handle it pure way?
  3. When I use higher-order functions, is outer variable in logic like:
const outer = 100;

[1, 2, 3]
  .map((elem) => elem * elem)
  .reduce((res, elem) => res + elem + outer, 0);

not an anti-pattern?

Maybe I'm thinking too hardly about fp ;)
James Forbes
@JAForbes
Jun 16 2016 06:42
  1. Your Array.from trick is cool. You've also got R.range and R.times which can come in handy. You can use loops if you'd like, it really depends on what you are trying to achieve. I personally never write a loop anymore. But that took time, there is no need to force it, it just naturally arises as you compose more and more.

  2. If you need the index, stick to loops for now. When tail call optimization is supported in the environment you support, then perhaps see if you can write a recursive definition. But again, they key is purity. Loops are not evil.

  3. It's fine to reach into higher scopes. Just don't mutate them. (const is good!) :)

I know your example is contrived, but you often can avoid outer variables e.g.
[1, 2, 3]
  .map((elem) => elem * elem)
  .map(R.add(100))
  .reduce((res, elem) => res + elem, 0);
Plusb Preco
@preco21
Jun 16 2016 06:47
@JAForbes Aha, I got it. Thanks a lot!! :+1:
James Forbes
@JAForbes
Jun 16 2016 07:06
:)
Plusb Preco
@preco21
Jun 16 2016 07:30

@JAForbes Ah, sorry for ask again. :innocent:
Since you mentioned:

But generally accessing the index in functional programming is an anti pattern.

But why Array.from({length: n}) is fine? Which is access index such like:

Array.from({length: 10}, (elem, index) => index); // << ah index!
function diag2(arr, dimension) {
  return Array.from({length: dimension * 2 - 1})
    .map((elem, index) => {
      const start = index < dimension ? 0 : index - dimension + 1; // << here (but can be used `elem` instead of `index` tho)

      return Array.from({length: index - start * 2 + 1})
        .map((elem2, index2) => {
          const offset = start + index2; // << here
          return arr[offset][index - offset];
        });
    });
}
James Forbes
@JAForbes
Jun 16 2016 08:07

There are few reasons to try to avoid indexes. 1. They make your algorithm dependent on a particular data structure. 2. They can make composition less fluid.

You can often get away without using an index. R.map (by default) will not pass the index to your visitor function. In some situations the index is a number ( arrays ), and then sometimes a string ( objects ). And in some cases indexes don't really make sense, like concurrent processing.

Now, if you know your input is an array, and it will always be an array. Using an index isn't so bad. It isn't a hard and fast rule. It's just a guide to help you know when using an imperative loop code might be clearer.

There's another reason relying on indexes isn't so helpful. In functional programming we have a preference for functions that take one argument. We call them unary. When a function is unary, its easy to compose. But if we rely on the index, suddenly our map function is binary (2 arguments) and it doesn't compose as easily.

These requirements really don't matter in your situation. A lot of this stuff is just techniques that can help make your code simpler. But really, the only hard and fast rule is to keep your functions pure. Everything else is flexible.

Plusb Preco
@preco21
Jun 16 2016 10:05

Hmm.. still confusing but now I have own guide:

  1. Use loops when you need to manipulate variables at outer of higher scopes. (such as sorting, swapping)
  2. When you need numeric based loop, use: Array.from({length: n}, (elem, index) => index) which is similar as range().
  3. Do not access index or originalArray, instead use loops.
  4. Accessing outer Array or Object property as like arr[i][i2] in higher scopes are fine.
  5. If you need performance, then use loops.
  6. Manipulate res value of reduce() is fine.

Thank you so much @JAForbes for guide me fall into fp :) :heart:

LeonineKing1199
@LeonineKing1199
Jun 16 2016 15:27
@JAForbes :plus1:
LeonineKing1199
@LeonineKing1199
Jun 16 2016 20:53
Is there a difference between R.is(Array) vs R.isArrayLike?
Looking at the docs, it seems like they're pretty much the same...
Scott Sauyet
@CrossEye
Jun 16 2016 21:00
@LeonineKing1199 I'm guessing the unit tests will demonstrate the difference.
Declan de Wet
@declandewet
Jun 16 2016 21:01
@LeonineKing1199 there is a fundamental difference.
function foo () {
  return [R.isArrayLike(arguments), R.is(Array, arguments)]
  // [true, false]
}

foo()
LeonineKing1199
@LeonineKing1199
Jun 16 2016 21:02
Ah, so arguments would qualify as an array-like object. I was partially confused by the example because they had a similar object but because it looked sparse, I'm assuming that isArrayLike is intelligent enough to detect that whereas arguments's keys follows a nice integral sequence.
Thanks for the timely responses, guys
Scott Sauyet
@CrossEye
Jun 16 2016 21:03
I'm late to the discussion here, but I think there's an important point being missed. Currying is not particularly important on its own. The reason it's stressed around here is that it makes it much easier to perform functional composition. And composition is extremely important.
Also, there's a strong argument that shuffling is not (as usually done) very functional. You lose referential transparency.
Declan de Wet
@declandewet
Jun 16 2016 21:06
@crosseye by this, do you mean argument shuffling? like what R.flip does?
LeonineKing1199
@LeonineKing1199
Jun 16 2016 21:21
Actually, I'm curious what the comment was in reference too.
Brad Compton (he/him)
@Bradcomp
Jun 16 2016 21:27
I think he's referring to shuffling the elements of an array . In reference to https://gitter.im/ramda/ramda?at=576239e0da1c26b045369bcd
Drew
@dtipson
Jun 16 2016 21:28
is there a preference here for using linked lists when possible instead of native Arrays?
LeonineKing1199
@LeonineKing1199
Jun 16 2016 21:29
* knee-jerk reaction * Whoa, if you're doing a linked list in JS, something must be horribly, horribly wrong. O_o
Drew
@dtipson
Jun 16 2016 21:35
are you? I often see functional libraries that implement them as a type, both immutable and non-empty
I know little about them other than the standard performance tradeoffs between them and arrays
LeonineKing1199
@LeonineKing1199
Jun 16 2016 22:28
Well, in C++-land, all classes are types anyway so implementing a list as a type isn't all that functional. In JS, it's weird sounding me to implement a linked list. If you need rapid insertions and deletions, use a plain object because internally, they're implemented basically as a hash table.