These are chat archives for ramda/ramda

22nd
Jan 2019
Just RAG
@justrag
Jan 22 09:58
@ben-eb Let's say {action: 'MOVE', player: 1, fromPosition: <originalPosition>, toPosition: <targetPosition>} or something like this. Generally, a data structure for the board that would make it easy (or at least not needlessly complicated) to remove the "pawn" from previous position and put it in new position.
Frank
@frankvanvuuren
Jan 22 12:57
Hi guys, I've a question regarding currying when using compose. Can someone please explain to me why my code is failing https://repl.it/@frankvanvuuren/BestDefiantParallelcomputing
Nevermind, I guess I can't read. In the docs: Note: The result of compose is not automatically curried.
Lewis
@6ewis
Jan 22 16:42
anyone used twillio?
offtopic* sorry
Brad Compton (he/him)
@Bradcomp
Jan 22 16:44
I used it at a prior position, but not currently
@justrag Another option is to have a mapping where the keys are each piece, and then the values can contain info on their movement, their current positions, etc.
You don't need to represent every board position, you just eed to know where each piece is
Mattibun
@Mattibun
Jan 22 18:14
I'm having an issue figuring out why cond is failing. The console spits out pairs[idx][0].apply is not a function at line 41 of cond.js. Here's my function:
// Takes in an game model object
export default function startEnemyRound(model) {
  const checkedSleep = sleepCheck(model); // Processes sleep status
  const checkedRun = runCheck(model); // Sees if the enemy runs
  return R.cond([
    [R.gt(checkedSleep.enemySleep, 0), R.always(checkedSleep)], //Return the model with sleep messages if asleep
    [R.equals(checkedRun.inBattle, R.F), R.always(checkedRun)], // Return the model with run messages if they flee
    [R.T, enemyRound(model)] // Otherwise, pass the model to the normal fight code.
  ])(model);
}
Brad Compton (he/him)
@Bradcomp
Jan 22 18:18

cond takes functions for its conditions, but you have values for the first two:

R.gt(checkedSleep.enemySleep, 0) :: Bool
R.equals(checkedRun.inBattle, R.F) :: Bool
R.T :: Model -> Bool

WHat you want to do with the first two is change them to a function that takes the model and outputs the boolean, or else wrap them in always, but that isn't really a good pattern

The first one could be compose(lt(0), prop('enemySleep'), sleepCheck)
Mattibun
@Mattibun
Jan 22 18:21
So just do everything in the cond then. Let me give that a try.
Mattibun
@Mattibun
Jan 22 18:31
Still getting the error, but it has moved to line 42. Console does say the new condition is a function. New code and simplified:
export default function startEnemyRound(model) {
  return R.cond([
    [
      R.compose(
        R.lt(0),
        R.prop("enemySleep"),
        sleepCheck
      ),
      R.always(model) //Just returning the model for now
    ],
    [R.T, enemyRound(model)]
  ])(model);
}
Brad Compton (he/him)
@Bradcomp
Jan 22 18:34
It's the same error?
Mattibun
@Mattibun
Jan 22 18:35
Slight difference. pairs[idx][1].apply is not a function
So index 1, not 0. And the next line down.
Brad Compton (he/him)
@Bradcomp
Jan 22 18:36
try passing in enemyRound instead of enemyRound(model)
Mattibun
@Mattibun
Jan 22 18:37
That did it. :)
Brad Compton (he/him)
@Bradcomp
Jan 22 18:38
export default const startEnemyRound = R.cond([
    [
      R.compose(
        R.lt(0),
        R.prop("enemySleep"),
        sleepCheck
      ),
      R.identity
    ],
    [R.T, enemyRound]
  ])
Mattibun
@Mattibun
Jan 22 18:38
So it sounds like I can use the index to determine which condition is causing the problem if that error comes up?
Brad Compton (he/him)
@Bradcomp
Jan 22 18:38
Yes. cond expects functions for ... everything
other than the final value you pass in to run it with
Mattibun
@Mattibun
Jan 22 18:47
Okay, I think I see what I'll have to do with my code. I'll need to isolate the actual checks into their own functions then pass the model to those for the modifications. The check functions are doing too much.
Thank you very much for the help.
Brad Compton (he/him)
@Bradcomp
Jan 22 18:48
no prob!
Mattibun
@Mattibun
Jan 22 19:07
Got the logic split out and the tests work. :) Speaking of tests, is there a recommended unit testing library for Ramda/FP in JS?
Brad Compton (he/him)
@Bradcomp
Jan 22 19:08
You mean like a test runner? I use Jest at work, but I like Mocha a lot better
sinon is really nice for stubs and mocks
Ben Briggs
@ben-eb
Jan 22 19:09
We use jest. AVA is also nice. https://github.com/avajs/ava
Mattibun
@Mattibun
Jan 22 19:12
I'm a hobbyist. I have played with Mocha in the past, but I'll look at the others. My program is getting large enough that I think it's time to start looking into it.
In the meantime, I'll work on converting the rest of these ifs into something more functional.
Ben Briggs
@ben-eb
Jan 22 19:15
Unit testing is a good habit to get into :)
Brad Compton (he/him)
@Bradcomp
Jan 22 19:15
Yes!
and feel free to ask questions about it here. I know it's OT but I also know it's something a lot of us care about here
Mattibun
@Mattibun
Jan 22 19:17
Will do. I would imagine I'd have to mock up some models with the correct pieces to feed the tests.
Ben Briggs
@ben-eb
Jan 22 19:17
I had a bug in some large sql query the other week, and I had written a unit test for the base case. So it was immensely satisfying to add a further test and track down the problem quickly :)
Well "unit". More like integration
The nice thing about testing FP is that calling a pure function can always be replaced by inlining the value
But even if you're testing impure functions, if you return the same shape from a mock function that you would from the real one then it will continue to work
Ben Briggs
@ben-eb
Jan 22 19:29
@justrag I can't really elaborate on your specific requirements, but I think it might be nice to look into lenses for your updates.
const pieces = {
  player1: {
    p1: [0, 0]
  }
}

const updateX = update(0);
const updateY = update(1);

const updateXY = uncurryN(3, (x, y) => compose(updateX(x), updateY(y)));

const updatePiece = uncurryN(5, (player, piece, x, y) =>
  over(lensPath([player, piece]), updateXY(x, y))
);

updatePiece('player1', 'p1', 2, 2, pieces);
Not that I would really recommend a 5 arity function. This should probably be replaced by some object :)
But the advantages of lenses is that you don't need to mutate the board state, which is useful for things like undo
Mattibun
@Mattibun
Jan 22 19:33
The codebase is at https://github.com/Mattibun/Dragon-Warrior-1-Battle-Simulator/tree/master/src for the curious, but I haven't pushed the latest tweaks from this conversation.
Mattibun
@Mattibun
Jan 22 19:38
Today's task is working through EnemyAttack.js
Mattibun
@Mattibun
Jan 22 19:54
In cond, if your conditional function doesn't really need what's passed in, like with a purely random chance, do I just leave the unused parameter alone or is there a better way to do it? Such as:
function enemyStillAsleep(model) {
  return R.cond([
    [      
    [wakeUp, enemyWakesUp] // Keeping it simple    
  ])(model);
}

function wakeUp(model) { // I don't need this parameter, but cond will try to pass it...
  const wakeChance = 3;
  return R.equals(randomFromRange([1, wakeChance]), wakeChance);
}
There are other conditions, just highlighting the relevant bits.
Ben Briggs
@ben-eb
Jan 22 20:00
You could do function wakeUp() { instead and ignore the param
Mattibun
@Mattibun
Jan 22 20:02
Upon thinking, that makes sense cause the extra params will just get shoved into the arguments object. Derp.
Ben Briggs
@ben-eb
Jan 22 20:02
Yep
Ben Briggs
@ben-eb
Jan 22 20:08

Anyone seen this before?

curl https://asciitv.fr

It's star wars a new hope in ascii

Brad Compton (he/him)
@Bradcomp
Jan 22 20:17
Yes! I used to have the telnet version as an alias in my bashrc
Ben Briggs
@ben-eb
Jan 22 20:31
I wonder how long it took. The source of that is https://github.com/martinraison/ascii-tv - the text is ~2MB
Bernhard Wang
@zwc
Jan 22 21:10

I'm trying to figure out how to calculate the difference between each element in an array in Ramda. So basically, if you have 1, 2, 3, 4, 5 the end result should be [1, 1, 1, 1].

This is what I have got working in JS:

function diff(arr) {
  return arr.slice(1).map((n, i) => n - arr[i]);
}

And so far I have gotten to:

const diff2 = R.pipe(
  R.slice(1, Infinity),
  R.map((n) => {
    return n;
  })
);

But I'm stuck on the mapping, or maybe there's another/better way to do it?

Ben Briggs
@ben-eb
Jan 22 21:12
@zwc You can use addIndex(map) to get the other arguments
Bernhard Wang
@zwc
Jan 22 21:16
Ah, that helps a lot. Now for how I access the whole array in the end :)
Brad Compton (he/him)
@Bradcomp
Jan 22 21:17
Try using aperture
Bernhard Wang
@zwc
Jan 22 21:17
Oh, that's the third argument :D
Ben Briggs
@ben-eb
Jan 22 21:18
@Bradcomp Oooo, neat. I haven't seen a use case for this one yet
Bernhard Wang
@zwc
Jan 22 21:18
Oh, that's interesting, I'll give that a go.
Ben Briggs
@ben-eb
Jan 22 21:20
Don't know if it reduces readability this way though :)
compose(
  map(apply(flip(subtract))),
  aperture(2)
)([1, 2, 3])
Bernhard Wang
@zwc
Jan 22 21:23
Impressive, I tried a lot of variants of subtract, but all I got back was functions ;)
Ben Briggs
@ben-eb
Jan 22 21:23
Well with aperture you're creating an array of arrays so you need to apply those :)
I don't really know cause to me your original function is more readable
They both blow up on null anyway so the pointfree version above doesn't have any main benefit
Bernhard Wang
@zwc
Jan 22 21:27
True :)
Ben Briggs
@ben-eb
Jan 22 21:28
The only thing I might refactor it to is to convert it into an arrow function
const diff = arr =>
  arr.slice(1).map((n, i) => n - arr[i]);
vs:
const diff = compose(
  map(apply(flip(subtract))),
  aperture(2)
)
Not a lot in it though :)
Bernhard Wang
@zwc
Jan 22 21:32
Yeah, it's sweet n short :D
Ben Briggs
@ben-eb
Jan 22 21:36
Yep :) I find it all to easy to fall into the pointfree trap so I am more cautious about it these days
I had something today where I couldn't explain the arguments because I had refactored them all away and all I had was a type signature with generic types. I know this can be replaced by good docs but sometimes having the data there makes it easier to come back to
Of course, it depends on the function in question
But if you have some formula that takes say 4 arguments and you have a :: Number -> Number -> Number -> Number -> Number that can be really difficult to scan :)
Fortunately my unit tests still had the argument names ;)
Mattibun
@Mattibun
Jan 22 22:30

R.equals is really throwing me and I'm not sure why. The condition here doesn't work:

```
function enemyRound(model) {
return R.cond([
[attackMatch("ATTACK", chosenAttack), enemyAttack(model)],
[R.T, console.log("Enemy Round went wrong!")]
])(chosenAttack);
}

Mrrf...
R.equals is really throwing me and I'm not sure why. The condition here doesn't work, but it returns true.
function enemyRound(model) {
  const chosenAttack = "ATTACK";
  return R.cond([
    [attackMatch("ATTACK", chosenAttack), enemyAttack(model)],
    [R.T, console.log("Enemy Round went wrong!")]
  ])(chosenAttack);
}

function attackMatch(str, test) {
  return R.equals(str, test);
}
The goal is to clean up a switch block. Is this the wrong way to go about it?
Ben Briggs
@ben-eb
Jan 22 22:36
Two things;
  • attackMatch can be const attackMatch = R.equalsinstead
  • then chosenAttack on line 4 doesn't need to be supplied
Or indeed, that call could be inlined, am assuming the alias is for readability
Mattibun
@Mattibun
Jan 22 22:42
Well, it's skipping to the console message every time, even with those changes.
Ben Briggs
@ben-eb
Jan 22 22:43
Yeah it's because the log is evaluated straight away
() => console.log() instead :)
Mattibun
@Mattibun
Jan 22 22:46
Still complains. Again with the pairs[idx][1].apply is not a function error, but I don't see anything wrong with that line.
Ben Briggs
@ben-eb
Jan 22 22:46
Probably something to do with the enemyAttack function
It should be a function that takes a String
Mattibun
@Mattibun
Jan 22 22:47
That's definitely not the case. :) It's supposed to accept the model.
Ben Briggs
@ben-eb
Jan 22 22:48
Yeah but your cond function is switching on a string
Mattibun
@Mattibun
Jan 22 22:48
In the normal version of enemyRound, it pulls an array of attacks from the model, chooses one, then matches on that string to where to send the model next.
Ben Briggs
@ben-eb
Jan 22 22:48
So it could be a partially applied function that has the model and then waits for a string
Or again, you could ignore the string and do () => enemyAttack(model)
Mattibun
@Mattibun
Jan 22 22:50
The latter is the easier case for now, and that does work. :)
I don't need the string afterward.
Ramda is definitely a different way of thinking. I like it, but it is different.
Ben Briggs
@ben-eb
Jan 22 22:56
Yeah, declarative style takes a bit of getting used to