These are chat archives for ramda/ramda

18th
Sep 2017
Zehua Liu
@zehua
Sep 18 2017 05:14
Hi guys, does anyone see same problem when accessing the online REPL? the browser stops at loading https://wzrd.in/standalone/sanctuary@latest and https://wzrd.in/standalone/ramda-fantasy@latest. These two urls eventually timed out. Looks like something wrong with wzrd.in?
Vasili Sviridov
@vsviridov
Sep 18 2017 05:15
Takes a while to open for me as well
Zehua Liu
@zehua
Sep 18 2017 05:16
Even when the repl managed to load itself, it stopped working after a while.
Philipp Wille
@Yord
Sep 18 2017 06:25
@kurtmilam Usually not a fan of the variable_ (added underscore) notation, but I must say in case of flip it communicates the semantics incredibly well! :)
Akshay Iyer
@AkshayIyer12
Sep 18 2017 06:33
Is R.map different from the normal map function which we use over an array?
Alexandre Theodoro da Silva
@alexandrethsilva
Sep 18 2017 07:25
@zerobias yeah, they made a comment over there on the issue that pointed that out too. thanks for the heads up!
Kurt Milam
@kurtmilam
Sep 18 2017 07:27
@AkshayIyer12 Yes, there are several differences. One difference is that Array.prototype.map passes three arguments to the callback function (currentValue, index, array) while R.map only passes one, currentValue.
@Yord Agreed - that's a convention I only started using recently. I think it works in functional code, and it's a lot easier than trying to come up with the right name for the flipped function (dividBy, dividInto, etc.).
Shubham
@shubham399
Sep 18 2017 07:35
can anyone explain me bichain() ?
Kurt Milam
@kurtmilam
Sep 18 2017 07:37
@shubham399 where do you see bichain?
Shubham
@shubham399
Sep 18 2017 07:39
Sorry Not of Ramda
:(
Kurt Milam
@kurtmilam
Sep 18 2017 07:40
@shubham399 this?
I know it's not Ramda. If you could tell me what it is, I could have a look. I have an idea (it's most likely that bichain :: chain as bimap :: map.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 08:49
Hi Guys! I'm a new to fp and ramda and I'd like to start using it for my existing projects but I have some issues how to approach to specific problem. I have some promise waterfall when every single call is making some API calls and passing some data further down the pipe. I tried to use R.pipeP() and it works as expected but I'd like to gather data for every single step to reuse it further. I manage to do that by creating unpure function that uses tap to copy some data from that pipe. Does anyone know better way how to do it?
here is an example how my code looks like R.pipeP(setUp.createRandomUser, setUp.login, collect('user'), setUp.createRandomAccount, collect('account'))
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:01
at the end I'd like to easily compose those API calls and return some objects that will be build during whole process. For example create user > login > create account for that user > return account that contains that user as an owner. Right now all those setUp methods accept single arg for example (user => user => user => account => account)
Kurt Milam
@kurtmilam
Sep 18 2017 09:10
@AdrianSkierniewski I'd probably just build up an object in the setUp methods. So, setUp.createRandomUser would return { user: { ... } }. setUp.createRandomAccount would work on x.user and return { user: { ... }, account:{ ... } }.
At the end, you'll have one big object containing all of the things that were created in the pipeline.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:12
yes I through about it but then I'd need to pass { user: {} } to setUp.createRandomAccount even when I'm calling it separate
Kurt Milam
@kurtmilam
Sep 18 2017 09:15
You could create a version of createRandomAccount account that derives from the current one but expects a wrapped value if you still want to be able to call the current method with an unwrapped value. I'll see if I can come up with another idea.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:15
so just by looking at arg list for this method, right now I have one arg user and it's well defined vs some_object that need to contains some property
I like the idea with creating separate version :+1:
Kurt Milam
@kurtmilam
Sep 18 2017 09:16
Another way to do it would be to turn createRandomAccount into a binary function. The first param you send to it would indicate how to treat the second.
So, if you want to work with an unwrapped value, you'd call createRandomAccount( identity, { ... }). If you wanted to work with a wrapped value, you'd call createRandomAccount( prop( 'user' ), { user: { ... } } ).
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:18
nice I haven't thought about it
one of my previous ideas was to pass a list down that pipe and work on it and then reduce at the end
Kurt Milam
@kurtmilam
Sep 18 2017 09:20
A list would also work, but I think I would prefer an object with meaningful property names to a list with indexes.
I don't know what you want to do with it at the end, but I suppose you could have a list of functions you want to perform using the object and just reduce over that list.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:22
yes, I didn't like the idea of depending on order or objects in that array
Kurt Milam
@kurtmilam
Sep 18 2017 09:23
Or you could use map on the object, since that also works.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:23
I'm creating a setup for load tests. I'd need to setup users, account, apps etc. and then write it to json file so next program can use it for loading tests
Kurt Milam
@kurtmilam
Sep 18 2017 09:26
If you get creative, you could end up with a generator that you send a list of names of things to create in a pipe, and it takes care of calling the correct functions and passing in the correct key names.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:27
thank you for pointing me those solutions. I'll work on them to see if they'll work for me
Kurt Milam
@kurtmilam
Sep 18 2017 09:29
createRandomEntities( [ 'user', 'account' ] ) transforms the list to an array of functions to send to pipeP:
[ x => setUp[ 'createRandom_' + 'user' ]( prop( 'user' ), x )
, x => setUp[ 'createRandom_' + 'account' ]( prop( 'account' ), x )
]
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:32
hmm interesting
Kurt Milam
@kurtmilam
Sep 18 2017 09:33
That list of entity names could be used for other things (like the reduce or map operation at the end of the pipeP call).
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:33
I could automate creation of x too based on those args in array
Kurt Milam
@kurtmilam
Sep 18 2017 09:33
And it gets you away from having magic constants that you need to remember.
Yes. Maybe overkill for a test loader, but might be a nice approach.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:35
ok but how about handling errors ?
for example someone will call those functions in wrong order
creating account without creating user first
Kurt Milam
@kurtmilam
Sep 18 2017 09:37
I think you have the same issue when you build pipeP manually, so I think it's something you'd need to control for either way.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:38
right
Kurt Milam
@kurtmilam
Sep 18 2017 09:38
Also, if createAccount needs to be called with instructions on how to extract a valid user from the second argument, createAccount should be able to tell if that didn't happen.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:39
yes, probably by breaking promise chain and returning error
Kurt Milam
@kurtmilam
Sep 18 2017 09:39
So, if you call createAccount with a property that doesn't point to a valid user in the second argument, createAccount will need to fail. Right.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:40
so I could use promise chain to handle those errors too
Kurt Milam
@kurtmilam
Sep 18 2017 09:41
If you want to add run-time type-checking to the functions and methods, you could look into sanctuary-def. There you could define the types of the arguments that createAccount expects, and you'll get a nice error if the arguments don't conform to the types.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:42
thank you
Kurt Milam
@kurtmilam
Sep 18 2017 09:43
You're welcome! Sounds like a fun thing to work on and learn / practice your functional programming skills :)
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:43
yeah, that's why I chose fp to test it how it work for this project
is there a easy way how to initialize empty object {} with properties passed in array using ramda functions?
I could use reduce
Kurt Milam
@kurtmilam
Sep 18 2017 09:49
Could you give a short example of input and expected output?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:51
const initialzeProperty = (obj, propName) => {
  obj[propName] = null
}

const runAPICallsInSequence = (operations) => {
  let container = R.reduce(initialzeProperty, operations, {})
}
I don't know if this will work
Kurt Milam
@kurtmilam
Sep 18 2017 09:51
three backticks, line break, code, line break, three backticks to get formatting.
:+1:
I wouldn't initialize the properties like that.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:53
so I'll get container for all results and now I just need to run all those operations in sequence
it doesn't look like a fp approach for me
Kurt Milam
@kurtmilam
Sep 18 2017 09:53
The functional way would be to create the container based on the shape of the output after running the operations.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:54
ok but I'd need to initialize it to pass args between function calls
Kurt Milam
@kurtmilam
Sep 18 2017 09:56
no, that shouldn't be necessary. Are you talking about calls in the pipeP to things like createAccount and createUser?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 09:56
yes
Kurt Milam
@kurtmilam
Sep 18 2017 09:56
The idea would be to build up the container as you go and pass it on to the next function in the pipeline.
No initialized container would be required.
I may try to work up a small example.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:01
ok so each function in pipeP would need to modify object that was passed as arg, right?
just to add result to it
I should probably pick easier project to start playing with FP :)
Kurt Milam
@kurtmilam
Sep 18 2017 10:04
I wouldn't modify the object, usually. Rather, I'd create a new object with the new property, then pass it on.
In FP, you generally try not to modify things once they've been created.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:05
yes that's why I wanted to initialize that object and pass it before I start running those things in pipeP
Kurt Milam
@kurtmilam
Sep 18 2017 10:05
On the one hand, your exercise isn't the easiest. On the other, it sounds like a perfect candidate for a fully FP solution.
It's not necessary to initialize the object with any keys. I would pass in an empty object to the caller and have your functions in the pipe take care of creating new copies of that object that have the necessary keys added when they're needed.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:08
ok so I can just run some dummy function at the beginning of pipeP to return empty object, right?
const initializeEmptyObject = () => {
  return {}
}
Kurt Milam
@kurtmilam
Sep 18 2017 10:09
You could, or you could pass an empty object in to the function that calls pipeP.
Isaac
@IsaacRaja_twitter
Sep 18 2017 10:10
Hi
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:10
Hi
Isaac
@IsaacRaja_twitter
Sep 18 2017 10:10
let countries = R.uniq(R.concat(getCountries(proxItineraries), getCountries(absItineraries)));
can the above be simplified further
I want to apply same function with different arguments
and then concat their results
Kurt Milam
@kurtmilam
Sep 18 2017 10:12
I don't know what your call to pipeP looks like, i.e. what arguments you're currently passing it, but to do what you want to do, I would generally pass an empty object explicitly as an argument to pipeP.
apply( pipeP )( ops )( {} )
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:13
oh I didn't know that
ok but now I'd need to build pipeP arg list on the fly
based on those strings in array
so should I just map on it and explicit run those functions?
Kurt Milam
@kurtmilam
Sep 18 2017 10:15
that's what I was trying to show above. Just map the list of strings and return a list of functions that pipeP can be applied to.
You can skip that part, if you want, and just hardcode the functions to be called in pipeP.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:17
I'll try to do it
Kurt Milam
@kurtmilam
Sep 18 2017 10:17
Then you could save it as a thing to explore later on, if you have the time.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:18
I'd need some additional function to pass it to map, right? I mean something that will find specific function in setUp and add it to the array
Kurt Milam
@kurtmilam
Sep 18 2017 10:19
Right, I tried to show that with this:
createRandomEntities( [ 'user', 'account' ] ) transforms the list to an array of functions to send to pipeP:
[ x => setUp[ 'createRandom_' + 'user' ]( prop( 'user' ), x )
, x => setUp[ 'createRandom_' + 'account' ]( prop( 'account' ), x )
]
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:19
k
Kurt Milam
@kurtmilam
Sep 18 2017 10:20
It has also occurred to me that that's not quite enough info for fns like createRandomUser to work with wrapped and unwrapped values. In addition to telling the fn how to locate the data it needs in the second argument, you also need a way to indicate to the function whether it should return a wrapped or raw value.
A way to do that would be to have createRandomUser take two arguments, a propName and the input object.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:22
if propName is empty the just use root object
Kurt Milam
@kurtmilam
Sep 18 2017 10:22
You could pass an empty string as the propName if you wanted to tell the fn to work with an unwrapped value, otherwise pass in the correct propName that tells the fn where to look on the second argument for the value it needs.
right
and if it isn't you know where to look for the data you need, and you know that you need to return a wrapped value.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:24
it would be nice if I could wrap that setUp method with another method that take care of it
so those methods in setUp won't know if they work with pipeP or standard call
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:31
x => wrapper(prop('user'), setUp['createRandom_' + 'user'])(x),
so the wrapper will extract argument and assign the value based on the function name
Kurt Milam
@kurtmilam
Sep 18 2017 10:38
Here's a quick sketchup with some of the ideas: https://goo.gl/Nt6KtT
The REPL is loading up very slow today, so you'll need to have a little patience for it to show up.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:40
thank you
how did you pass array of functions to pipeP ?
Kurt Milam
@kurtmilam
Sep 18 2017 10:40
apply( pipeP )( arrayOfFns )
I'm about to modify the example to show that, plus an additional improvement.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:42
it looks like it worked
const runAPICallsInSequence = (operations) => {
  return R.apply(R.pipeP, R.map(getSetUpFunction, operations))({})
}
Kurt Milam
@kurtmilam
Sep 18 2017 10:45
Updated, but may be a little on the cryptic side :)
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:49
I definitely need some time with docs to understand that code :)
Kurt Milam
@kurtmilam
Sep 18 2017 10:50
And here's one that's pointfree.
Yeah, I get that. It's pretty funky looking. I ditched the key argument and went with a boolean to tell the fns whether to work with wrapped or unwrapped values.
juxt is pretty useful: juxt( [ add( 1 ), add( 5 ) ] )( 1 ) //-> [ 2, 6 ]
So it returns an array of the result of applying an array of functions to an argument.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:54
nice
Kurt Milam
@kurtmilam
Sep 18 2017 10:55
o is similar to compose, but it only takes two fns to apply.
Since the recent advent of o, I usually use o if I want to compose 2 functions or pipe if I want to compose more than 2.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:56
but o will run those functions form right to left right?
Kurt Milam
@kurtmilam
Sep 18 2017 10:57
o evaluates right to left, just like compose.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 10:57
isn't this confusing mixing right to left with left to right?
Kurt Milam
@kurtmilam
Sep 18 2017 10:58
I don't find it confusing, but that could be because I have more practice.
Also, I use a different version of pipe that's called like this:
pipe( [ fn1, fn2, fn3 ] )
    ( input )
// which I think is easy to distinguish from this:
o( fn2 )
 ( fn1 )
 ( input )
My pipe is basically R.apply( R.pipe ), and this is more true to how pipe usually works in functional languages than how ramda's works.
Also, o works more like compose works in most functional languages than R.compose does.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:02
k
@kurtmilam Thank you very much four your help. I have something that I can stick with for now to explore more FP :)
Kurt Milam
@kurtmilam
Sep 18 2017 11:12
Sure thing - feel free to drop back in and ask any questions you may have in the future.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:30
hmm it would be nice to pass additional arguments to those functions in pipeP but it's problematic when I use generator approach, because I'm passing only strings
so probably I'll get back with explicit function names and I'll add curry
then I could pass something like runSetup([setUp.user, setUp.account, setUp.apps([32, 24, 23])]) and setUp.apps will be curried (appIds, obj)
Kurt Milam
@kurtmilam
Sep 18 2017 11:33
Yes, that would be a good approach if the setup methods require more than one argument.
Click the 'Tidy' link in the output pane to see nicer output.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:34
ok
Kurt Milam
@kurtmilam
Sep 18 2017 11:49
This demonstrates a quick way to call doSetup multiple times and start each process off with a random integer between 1 & 100.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:52
I don't understand that underscore here const rndInt = _ => Math.floor( Math.random() * 100 ) + 1
Kurt Milam
@kurtmilam
Sep 18 2017 11:52
_ => true is equivalent to R.always( true ).
That underscore as an argument name is a convention in many FP languages that means "I don't care about this argument."
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:53
oh, ok
but you still need to have that arg there, right?
Kurt Milam
@kurtmilam
Sep 18 2017 11:54
Yes, because rndInt is a function.
You could also write it () => true.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:54
() => Math.floor( Math.random() * 100 ) + 1
Kurt Milam
@kurtmilam
Sep 18 2017 11:54
yes
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:54
ok
Kurt Milam
@kurtmilam
Sep 18 2017 11:55
The underscore is just a little shorter and, as I mentioned, is a convention in many FP languages.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 11:56
I'm trying to figure out if there is a case when I'd need to pass more that one prop to single step
in that case I should probably pass whole object it self with all props
Kurt Milam
@kurtmilam
Sep 18 2017 12:00
I would always pass the full object with all props when working with the wrapped pipeline.
That's what my example is doing.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 12:01
right I was thinking about standard usage
so for example if createApps will require user & account
Kurt Milam
@kurtmilam
Sep 18 2017 12:02
understood.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 12:02
I'd need to pass two argurments
so probably I could just leave all those functions with single argument
object with properties
Kurt Milam
@kurtmilam
Sep 18 2017 12:03
Right, in that case, I'd either do as you suggest or really just have two separate functions, one that works on wrapped values and one that works on raw values.
That would certainly simplify things.
Or you create a base function that works on raw values and accepts user and account arguments, then create a special version of the function that works on wrapped values ({ user, account }).
raw = user => account => doStuff // with user and account
wrapped = ( { user, account } ) => raw( user )( account )
wrapped just destructures the wrapped argument and calls raw with the individual arguments it needs, so you're not duplicating logic in that case.
Adam Szaraniec
@mimol91
Sep 18 2017 12:08
@kurtmilam Hey, What are benefits for using o over compose? I think compose is more powerfull, and if you do not like that it does not behave as a 'normal' fp languages, you could just dont pass more than two arguments?
Do you think its important to learn correct way? I am asking becuase I am more used to to pipe which is just reverse compose
Kurt Milam
@kurtmilam
Sep 18 2017 12:11
R.pipe and R.compose are both wrong compared to most functional languages.
They're both variadic and must be called twice to be properly executed, first with a variadic list of functions, then with an input.
The more standard way for pipe to work is like this:
pipe( [ fns ] )( input )
And for compose, it's like this:
compose( fn2 )( fn1 )( input )
Adam Szaraniec
@mimol91
Sep 18 2017 12:13
What do you mean by called twice, isnt only difference that o will ignore 2nd argument?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 12:13
so that destructures will work when I pass it to pipeP and I'll get access to both properties?
Adam Szaraniec
@mimol91
Sep 18 2017 12:14
I didnt know that , I was always using compose like compose(fn1,fn2,fn3) cos its feelt more naturall for me
Kurt Milam
@kurtmilam
Sep 18 2017 12:14
Neither of these 'correct' versions of the functions are variadic. Each has a clear arity - pipe is binary and compose is 3nary.
@mimol91 the 'correct' versions of the two functions are more easily composable.
And if you think you might want to learn another functional language in the future, it's probably not a bad idea to get used to doing things the way they're done in most functional languages.
@mimol91 R.pipe and R.compose both have to be called twice, first with a variadic list of function arguments and second with the input. You can't call, for instance, R.pipe( fn1, fn2, fn3, input )
The first call has to be fns only. The second call is the input.
Adam Szaraniec
@mimol91
Sep 18 2017 12:19
I've tried to use o but the order feels so unnatural for me. I mean if you split each function to one line, you need to read if from bottom ;/ But I started to learn haskell. and I think Ramda learnd me how to think outside of box aswell (even if its does not contains fp types)
Kurt Milam
@kurtmilam
Sep 18 2017 12:19
'Normal' pipe can be called like this: pipe( [ fn1, fn2, fn3 ], input ) (using Ramda-style currying).
Adam Szaraniec
@mimol91
Sep 18 2017 12:19
but you can call R.pipe(fn1)(fn2)(fn3)(input) which is the closest to 'normal' fp ?
Kurt Milam
@kurtmilam
Sep 18 2017 12:20
You don't have to split o like I do. You can also call it like o( fn2, fn1 ) or o( fn2, fn1, input )
You can't call, for instance R.pipe(inc)(inc)(inc)(1). That doesn't work.
Adam Szaraniec
@mimol91
Sep 18 2017 12:21
o( fn2, fn1, input ) is it common to call it in this way? I rater create some higher order function like o( fn2, fn1) and then I can reuse it
Kurt Milam
@kurtmilam
Sep 18 2017 12:22
Yeah, you can call it like that. I like to break it up into o( fn2 )( fn1 ), but that's just my coding style.
Adam Szaraniec
@mimol91
Sep 18 2017 12:22
wow, I was 100% that you could call it like that :D, which ye, does not make sese if you think about it
Kurt Milam
@kurtmilam
Sep 18 2017 12:22
o( fn2 )( fn1 ) is equivalent to o( fn2, fn1 ) with Ramda-style currying.
Adam Szaraniec
@mimol91
Sep 18 2017 12:23
DO you recommend other languages to start to learn fp rather than haskell?
Kurt Milam
@kurtmilam
Sep 18 2017 12:23
@mimol91 no, that's what I mean. pipe has to be called twice, once with a varying number of fn arguments, and a second time with the input.
Adam Szaraniec
@mimol91
Sep 18 2017 12:24
it make sense, to use more this o I will try to get more common with that !
Kurt Milam
@kurtmilam
Sep 18 2017 12:24
I'm not an expert, but I'd probably start with Haskell.
Dmitry
@zerobias
Sep 18 2017 12:24
@mimol91 purescript is suprisingly good
Kurt Milam
@kurtmilam
Sep 18 2017 12:25
@zerobias that's second on my list :)
alpox
@alpox
Sep 18 2017 12:25
@mimol91 Taking a look at Clojure can also be nice :)
Adam Szaraniec
@mimol91
Sep 18 2017 12:26
ahh, I give up on closure if I figured out that its not so easy to add two numbers :P
Kurt Milam
@kurtmilam
Sep 18 2017 12:26
@mimol91 I like o( fn2 )( fn1 ) because it makes it explicitly clear that o is a curried function, and I think it's closer to Haskell's syntax.
alpox
@alpox
Sep 18 2017 12:26
@mimol91 (+ 1 2) is not hard, is it?
Adam Szaraniec
@mimol91
Sep 18 2017 12:26
I mean it was not to which I 've been used to
alpox
@alpox
Sep 18 2017 12:27
@mimol91 well yea its true that its different to some other things and has lots of braces, but the language itself is very nice and FPish
Adam Szaraniec
@mimol91
Sep 18 2017 12:27
I was trying to do some basic stuff with clojure, but I think it will be little bit easier to catch basic 'way of thinking' while starting with Haskell ?
alpox
@alpox
Sep 18 2017 12:27
I think its still easier than haskell
Adam Szaraniec
@mimol91
Sep 18 2017 12:27
easier? ohh,
Is it good to start on braveclojure ? or you recommend other places to start?
alpox
@alpox
Sep 18 2017 12:30
@mimol91 There is a great book: https://www.braveclojure.com/clojure-for-the-brave-and-true/ (You can read it online). Maybe just use Cursive with intellij instead of emacs though
Kurt Milam
@kurtmilam
Sep 18 2017 12:30
If you wrote an o function in Haskell (which you wouldn't, because Haskell has a compose operator), you'd call it like o fn2 fn1. I think o( fn2 )( fn1 ) is closer to that Haskell syntax than o( fn2, fn1 ), and the second syntax hides the fact that o is curried.
alpox
@alpox
Sep 18 2017 12:31
@mimol91 Heheh :D yea its a good book, i bought it recently (3 weeks ago or so) and i'm deep into the great world of clojure now :D Its awesome so far
Adam Szaraniec
@mimol91
Sep 18 2017 12:31
Good I remember that I've stated with it , then it starts to look complex, so I tought lets start Haskell (atleast I will try to learn this function 'definition' ), however now I m considering to go back to clojure
alpox
@alpox
Sep 18 2017 12:32
@mimol91 Haskell is not less complex. Its rather more complex and complicated for newcomers (Especially when you come to monads)
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 12:32
@kurtmilam I still can't figure out how you manage to change setUp behavior just by passing true/false at bottom line. I see that ifte( equals( true ) ) but I don't know how you manage to pass that there
Kurt Milam
@kurtmilam
Sep 18 2017 12:34

It's a little obscure. I manage it in doSetUp. An illustration:

// start:
const doSetUp =
  o( o( apply( pipe ) ) )
   ( juxt )
// call it with list of fns
const step2 = doSetUp( [ fn1 ] )
// step2 is equivalent to
// o( apply( pipe ) )
//  ( juxt( [ fn1 ] ) )

Now we call step2 with a Boolean. juxt applies all of the fns to the boolean, then passes the resulting array of fns to apply( pipe ).

Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 12:34
I'm starting to understand that looking at first example with more familiar syntax
Kurt Milam
@kurtmilam
Sep 18 2017 12:40
Each of setUp's methods takes a Boolean and returns a function, so the result of applying the list of fns to the Boolean (via juxt) is a new list of fns, each primed to work on either raw or wrapped values.
In doSetup, the first call, passing in the list of fns, peels off one layer of o, so to speak :)
Kurt Milam
@kurtmilam
Sep 18 2017 13:58
@AdrianSkierniewski this may also be helpful. The doSetUp I had is a point free equivalent of this:
const doSetUp =
  ops =>
    o( apply( pipe ) )
     ( juxt( ops ) )
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 13:59
thx, I made some progress
I just need some time to get familiar with Ramda functions
Kurt Milam
@kurtmilam
Sep 18 2017 14:02
No worries - that example is going straight into overdrive. What I hoped to show is sort of a goal to work toward, what a more or less fully functional solution might look like.
Have fun!
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:03
I'd probably need some time to figure out all those issues to fully understand FP potential but it's a good start
for now I'm just using pipe because I'm getting confused about order of operation
when I finally understand the big picture that I'll start using other methods like o or compose
Kurt Milam
@kurtmilam
Sep 18 2017 14:05
:+1:
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:06
becasue when I don't see those args because of curry I'm getting so confused
Kurt Milam
@kurtmilam
Sep 18 2017 14:07
You mean like o( fn1 )( fn2 )?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:07
yes
Kurt Milam
@kurtmilam
Sep 18 2017 14:07
As opposed to o( fn1, fn2 )?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:07
yes
plus then o vs pipe order of operation
and I don't see exact object that is passed under the hood
Kurt Milam
@kurtmilam
Sep 18 2017 14:09
I understand. It's a little confusing. On the other hand, the curried version is good because it explicitly reminds you that the functions are all curried. It's just that Ramda adds some special sauce so that you can call them either way. Eventually, it's best to keep in mind that they're all curried, and I tend to think it's best to call them that way just to keep that fact front and center, but I understand it's strange just starting out.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:10
combining everything together and you can get a lot of confusion if you don't have experience with this kind of code
so one step at the time :)
Kurt Milam
@kurtmilam
Sep 18 2017 14:11
Take a look at this when you have time. I have interspersed log calls between each function call in ops so you can see how each object is being built up.
Barney Carroll
@barneycarroll
Sep 18 2017 14:12
I wonder if anybody can help me with a functional composition I'm having difficulty expressing
Kurt Milam
@kurtmilam
Sep 18 2017 14:12
The passing of the object is obscured a little by my use of chain, as well.
@barneycarroll hi, I'd be happy to try.
Barney Carroll
@barneycarroll
Sep 18 2017 14:12
Cheers @kurtmilam
Kurt Milam
@kurtmilam
Sep 18 2017 14:13
:wave: from the mithril Gitter.
Barney Carroll
@barneycarroll
Sep 18 2017 14:13
I can't spend my whole life there ;P
Kurt Milam
@kurtmilam
Sep 18 2017 14:13
:+1:
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:13
I'd probably create setUp library with standard versions of those functions (with explicit args) validation etc. and then I'd create some functional wrapper so I won't need to pass that additional Boolean value
and it should be easier to understand for peoples that don't understand fp style
Kurt Milam
@kurtmilam
Sep 18 2017 14:14
Sure, that'd be completely OK.
This is also true.
Barney Carroll
@barneycarroll
Sep 18 2017 14:15
I have a throttle(config)(fn) function, a memoize(fn) function, esotericGetInput() and esotericSideEffects(input)
Kurt Milam
@kurtmilam
Sep 18 2017 14:15
ok
Does Ramda come into play here or no?
Barney Carroll
@barneycarroll
Sep 18 2017 14:16
compose and memoize are there
Kurt Milam
@kurtmilam
Sep 18 2017 14:17
ok, a note on memoize - I believe it is scheduled for deprecation, with memoizeWith being preferred.
Barney Carroll
@barneycarroll
Sep 18 2017 14:17
Right
Kurt Milam
@kurtmilam
Sep 18 2017 14:17
Cool, just wanted to make sure you were aware.
Barney Carroll
@barneycarroll
Sep 18 2017 14:18
Yeah that is what I'm using actually, with JSON.stringify, just wanted to reduce problem surface
Kurt Milam
@kurtmilam
Sep 18 2017 14:18
outstanding - you're ahead of the game.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:19
so my fp version of could look like this account: R.chain(R.assoc('account'))(R.pipe(R.prop('user'), createAccount)),
Barney Carroll
@barneycarroll
Sep 18 2017 14:20
So the highest order function is throttle. The composition I want to throttle is a memoized function which takes esotericGetInput() as input and feeds it to the memoized version of esotericSideEffects(input)
Kurt Milam
@kurtmilam
Sep 18 2017 14:20
@AdrianSkierniewski yes, that looks good.
ok
Dmitry
@zerobias
Sep 18 2017 14:21
Memoize in ramda is relatively slow, is better to use some other implementation
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:22
and if I'd like to pass more that 1 argument that way, by using destructure?
Kurt Milam
@kurtmilam
Sep 18 2017 14:22
@zerobias memoize is slow, but he's using memoizeWith.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:22
I'd need to implement some wrapper method and put it instead R.prop('user'), right?
Kurt Milam
@kurtmilam
Sep 18 2017 14:22
right
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:23
finally I'm starting getting it :)
Kurt Milam
@kurtmilam
Sep 18 2017 14:23
:clap: that's great - it's a lot of pretty complicated stuff if you're just starting out with fp.
@barneycarroll should I be waiting on additional explanation, or does that about cover it?
Barney Carroll
@barneycarroll
Sep 18 2017 14:24
That's about the size of it, I'm trying to figure out a clearer way of expressing it
Kurt Milam
@kurtmilam
Sep 18 2017 14:25
Do you have a simple working version that I could look at?
I understand this may be much more complex than you're explaining here, but I think I'd have an easier time if I were looking at a simplified version of what you have.
Like who's calling what, when, and with which arguments, in a way that works, at which point I think I'll have an easier time breaking it down into constituent parts.
Barney Carroll
@barneycarroll
Sep 18 2017 14:31
Yeah sorry
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:32
Then I can extract that logic to separate method to easily reuse it during new functions definition
const makeWrappedVersion = (returnKey, extractor, fun) => R.chain(R.assoc(returnKey))(R.pipe(extractor, fun))
account: makeWrappedVersion('account', R.prop('user'), createAccount),
so I still should be able to extract single or multiple values but it will look a lot easier to read what is going on here
Kurt Milam
@kurtmilam
Sep 18 2017 14:33
That looks pretty good.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:34
One more thank you! :)
Kurt Milam
@kurtmilam
Sep 18 2017 14:35
You're welcome. I hope you enjoy experimenting with fp. I think it's pretty awesome with a little practice.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:35
I bet it is
Kurt Milam
@kurtmilam
Sep 18 2017 14:39
Also, chain( fn1 )( fn2 )( x ) is equivalent to fn1( fn2( x ) )( x ), to maybe remove some of the mystery there.
aka, chain( fn1, fn2, x ) is equivalent to fn1( fn2( x ), x ).
That's the technique I'm using that obscures how the object is being passed from step to step.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:45
ok
Barney Carroll
@barneycarroll
Sep 18 2017 14:45
My composition problem. I can get
compose(
  throttle,
  memoize,
  input => doStuff()
)
The problem is that I want the input to be memoized against to always be the result of idempotent getInput()
Kurt Milam
@kurtmilam
Sep 18 2017 14:46
For the whole composition? Also, is it doStuff( input )?
Barney Carroll
@barneycarroll
Sep 18 2017 14:46
Yes
But for the purposes of my problem I think it may as well be anything, doStuff and getInput are the kind of 'leaves' of this composition
Kurt Milam
@kurtmilam
Sep 18 2017 14:49
OK, from the example, it seems like this should work, so I may be missing something:
memoize(
  compose(
    throttle,
    doStuff
  )
)
You answered that the whole composition should be memoized. It's possible that there was a miscommunication there (the question I asked may have been ambiguous).
Barney Carroll
@barneycarroll
Sep 18 2017 14:50
The bit I'm struggling with is where I slot in getInput
So getInput is idempotent and returns different things at different times
It needs to run at the same time as the throttled function
Kurt Milam
@kurtmilam
Sep 18 2017 14:52
ok, is this with the granular redraw stuff you've mentioned recently for mithril?
Barney Carroll
@barneycarroll
Sep 18 2017 14:52
Struggling to express this… Basically the memoization needs to happen at throttled resolve time, based on whatever getInput returns then
No, actually I'm writing a form autosave function :)
Kurt Milam
@kurtmilam
Sep 18 2017 14:53
ok, just thought I'd ask for the sake of context :)
I have to step away but will be right back.
Barney Carroll
@barneycarroll
Sep 18 2017 14:53
getInput serializes the form
Kurt Milam
@kurtmilam
Sep 18 2017 14:54
and you only want to save if something has changed.
Barney Carroll
@barneycarroll
Sep 18 2017 14:54
Exactly
Kurt Milam
@kurtmilam
Sep 18 2017 14:55
take a single input form
say you type in 'hello' then wait a bit.
gitInput runs, serializes the form, saves it since it hasn't seen that value yet.
Barney Carroll
@barneycarroll
Sep 18 2017 14:55
Yup. The throttle is to ease traffic. The memo is to dedupe.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:55
hmm I have problem related to promise, it looks like that code doesn't return promise chain
Kurt Milam
@kurtmilam
Sep 18 2017 14:56
Now you change that to hola and the form is saved again.
if you change it back to hello, will it not save since it's been memoized?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 14:56
    return f.apply(ctx, arguments).then(function(x) {
                                  ^

TypeError: Cannot read property 'then' of undefined
Barney Carroll
@barneycarroll
Sep 18 2017 14:56
@kurtmilam well I've swapped out my memoize for a function which only stores the last input.
But yes, good catch :)
Kurt Milam
@kurtmilam
Sep 18 2017 14:57
ok, cool, just want to make sure I understand the problem :)
ok, I have to step away but will be back in a couple of minutes.
Barney Carroll
@barneycarroll
Sep 18 2017 14:59
Cheers for your perseverance. The edge case caught by throttle + memo should be that if you input hello, (trigger -> resolve -> memo -> persist) then hola(trigger -> ...), then hello (... -> resolve -> memo -> GUARDED)
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 15:02
it's because I'm returning wrapped promise { user: Promise { <pending> } } so that wrapped doesn't have .then
Kurt Milam
@kurtmilam
Sep 18 2017 15:05
@AdrianSkierniewski I'll have to think about that a little.
@barneycarroll this is where observables and streams can be nice.
You could use an observable that's a combination of an interval (replacing your throttle) and the memoized form serialization
and persist is only called when the interval trips and the serialization has changed. You could use something like Kefir's skipDuplicates on the form serialization.
So, assuming you're not going to use that sort of thing, you're looking for another way to get the same functionality.
Barney Carroll
@barneycarroll
Sep 18 2017 15:08
You're right, the inherent difficulty here is the temporal aspect
I did make my throttle function return promises at one point but it didn't make things much clearer
Kurt Milam
@kurtmilam
Sep 18 2017 15:10
what's your throttle interval?
Barney Carroll
@barneycarroll
Sep 18 2017 15:11
I'm using 1 second ATM just for the sake of debugging
In practice we're going for 5 minutes because we're scared of server load
Kurt Milam
@kurtmilam
Sep 18 2017 15:12
ok, so it seems the other problem would be what happens if it takes me 1.5 seconds to type 'hello'. Maybe 'hel' is persisted but 'lo' are throttled and never persisted?
or is that already handled?
Barney Carroll
@barneycarroll
Sep 18 2017 15:13
I've got that part sorted, it's more of a throttle + debounce, or debounce with intervals, or whatever
Kurt Milam
@kurtmilam
Sep 18 2017 15:14
ok, cool, then I think I have a reasonably complete picture of the situation. I'll put my thinking cap on for a few minutes.
lastly, getInput is probably triggered by an onChange event, or something of that nature, I guess?
Barney Carroll
@barneycarroll
Sep 18 2017 15:15
No, the whole overall thing is triggered by the events
Kurt Milam
@kurtmilam
Sep 18 2017 15:16
cool
Barney Carroll
@barneycarroll
Sep 18 2017 15:16
getInput should only occur once we've gotten past throttle and dedupe
Kurt Milam
@kurtmilam
Sep 18 2017 15:16
:+1:
Oh wait, I thought dedupe was based on the return from getInput?
Barney Carroll
@barneycarroll
Sep 18 2017 15:17
Erm yes sorry
Kurt Milam
@kurtmilam
Sep 18 2017 15:24
Is your throttle actually a debounce?
Barney Carroll
@barneycarroll
Sep 18 2017 15:26
According to what I consider the canonical source on the matter, it's a 'trailing throttle'
Kurt Milam
@kurtmilam
Sep 18 2017 15:27
throttle( 1000, false, memoize( doStuff ) )( getInput() ) what breaks with this?
Barney Carroll
@barneycarroll
Sep 18 2017 15:30
getInput is executed at invocation time, not resolution time
Kurt Milam
@kurtmilam
Sep 18 2017 15:31
ok, so that would work if getInput's invocation were delayed until resolution.
const getInputAndDoStuff =
  fn => 
    memoize( doStuff )( fn() )
throttle( 1000, false, getInputAndDoStuff )( getInput )
Now getInput's invocation is delayed, but this may still not work.
Seems like it should, but I haven't tested it.
Kurt Milam
@kurtmilam
Sep 18 2017 15:39
This is also where observables are nice, since you don't have to care about timing.
Barney Carroll
@barneycarroll
Sep 18 2017 15:40
Do you see any advantages in Observables over Streams?
Kurt Milam
@kurtmilam
Sep 18 2017 15:41
Well, streams are a type of observable. mithril's streams are more like what I would call atoms, since they always have a current value.
Not sure whether this is a mithril context or not.
Barney Carroll
@barneycarroll
Sep 18 2017 15:41
I think your general insight about having the right essential types for the groundwork is essential
I'm afraid I just can't rationalise this deferred execution in terms of function composition
Kurt Milam
@kurtmilam
Sep 18 2017 15:42
I keep mentioning observables because I have been working with the recently for similar functionality, and they really shine for this kind of thing.
Barney Carroll
@barneycarroll
Sep 18 2017 15:42
So in the absence of Streams or Observables I'm going to write this as a Promise-based API :grimacing:
Kurt Milam
@kurtmilam
Sep 18 2017 15:43
You might be able to use R.call to defer the call in a composable way.
Barney Carroll
@barneycarroll
Sep 18 2017 15:43
    const inputThrottle   = throttle({min: 1000, max: delay});
    const formStateDedupe = dedupe();

    this._save = () =>
      inputThrottle()
        .then(() => $(this.$form).serialize())
        .then(formStateDedupe)
        .then(data => $.post(endpoint, data, undefined, 'json'))
        .then(receipt => {
          this.setState({[identifier]: receipt[identifier]})
        });
Kurt Milam
@kurtmilam
Sep 18 2017 15:47
Or write your own that's a little more suited to the problem and is also composable:
const barneysCall = fn = fn()
compose(
  compose( memoize( doStuff ), barneysCall ),
  throttle
)( getInput )
Barney Carroll
@barneycarroll
Sep 18 2017 15:47
Oh hang on
Yeah that looks like it could work
Kurt Milam
@kurtmilam
Sep 18 2017 15:49
fingers crossed
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 15:52
I'm back. I was side tracked with some huge tables issue in postgres
Kurt Milam
@kurtmilam
Sep 18 2017 15:53
Call it 'lazyCall'.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 15:53
as for that promise issue, I'd probably need to wrap that result object in to new Promise every time when I process next function
but this doesn't look like a nice looking solution
Kurt Milam
@kurtmilam
Sep 18 2017 15:53
@AdrianSkierniewski yeah, I'm not sure what the Promise-returning function looks like.
I guess it's set in stone?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 15:54
all those setUp methods are API calls
that's why I used pipeP to handle them, and when I was passing directly output, everything worked
Kurt Milam
@kurtmilam
Sep 18 2017 15:55
I understand. What Promise library are you using?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 15:55
Bluebird
Kurt Milam
@kurtmilam
Sep 18 2017 15:55
gimme a sec
This could work: Promise.props
You might be able to wrap like this: Promise.props( { user: Promise { <pending> } } ).
I have used that before to build up objects where each prop value comes from a Promise.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:01
I'm trying to fit that into my code
Kurt Milam
@kurtmilam
Sep 18 2017 16:01
Promise.props( R.chain(R.assoc(returnKey))(R.pipe(extractor, fun)) ), for instance. It might take a little work to figure out the best place to insert Promise.props.
I have actually avoided having to work with Promises and composition, so far. I usually convert the Promise into an observable or do it slightly more imperatively. Futures are another thing to look into down the road. They're a more functional replacement for Promises, and some libraries have toPromise and fromPromise methods to make it easy to convert back and forth for interop.
I have worked a ton with Promises, but it's been a while. I usually try to convert them to something else early rather than work with them directly.
Kurt Milam
@kurtmilam
Sep 18 2017 16:07
If you figure out where to insert Promise.props, you can probably write a single wrapper function to reuse for all of your calls.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:17
I tried to put it if front of R.chain but I'm getting arrity problem
trying to debug that
it's probably because R.chain isn't invoked here we still need to pass some arg and then we're passing it to Promise.props instead
Kurt Milam
@kurtmilam
Sep 18 2017 16:20
Right, I'm going a bit off of memory here.
x => Promise.props( R.chain(R.assoc(returnKey))(R.pipe(extractor, fun))( x ) ) may do the trick.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:21
I get some results
:)
Kurt Milam
@kurtmilam
Sep 18 2017 16:21
You may also be able to use pipe there, but I'm not certain:
pipe( R.chain(R.assoc(returnKey))(R.pipe(extractor, fun)), Promise.props )
This would be inside of the bigger pipeP call.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:22
yes
so this is something that I'm still not familiar with (like a explicit pass that arg if I need to deal with promise mix)
Kurt Milam
@kurtmilam
Sep 18 2017 16:24
Well, that just lets you decide exactly where to insert the arg inside your various function calls.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:24
yes now I get it but even when I know the problem I was thinking how to solve that by adding more ramda functions
Kurt Milam
@kurtmilam
Sep 18 2017 16:25
That's not bad. I often prefer to solve it without passing the argument explicitly if it's possible to do so without jumping through too many hoops.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:25
and wrapping it around with closure + passing explicit arg was so obvious :)
Kurt Milam
@kurtmilam
Sep 18 2017 16:25
Sometimes it's the quickest way, though, to try a few different combinations out.
Like, if the second version (using pipe) that I shared above worked, I'd probably prefer it over the one with the explicit parameter.
It's something you'll get a feel for as you gain more experience.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:28
today I definitely fell like from zero to hero as for FP and ramda
Kurt Milam
@kurtmilam
Sep 18 2017 16:28
:D You're doing great!
Bet your brain feels tired, but in a good way :)
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:30
I've read one book about fp in js but it's a lot harder to start thinking that way and start solving some real problems
Kurt Milam
@kurtmilam
Sep 18 2017 16:31
It can take a while to get into the fp mentality.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:32
I even had some episode in Common Lisp couple years ago but it was definitely to short
Barney Carroll
@barneycarroll
Sep 18 2017 16:33
Thanks so much for your help Kurt, I'm going to come at it fresh in the morning but for the time being a chain of possibly failing Promises, and compose / throttle / dedupe geared for promise semantics, was the only way I could wrap my head around things in a sequentially meaningful way
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:33
after that I always want to learn that but hadn't enough time
Kurt Milam
@kurtmilam
Sep 18 2017 16:34
@barneycarroll no worries! Feel free to swing back by for another round after you regroup :)
@AdrianSkierniewski I have a hard time going back to imperative style, although I do at times when necessary (say, in the context of a library where performance is key).
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:38
and how about mixing some OOP concepts and FP ? Do they they work together ?
I know that it's rather processing primitives
Kurt Milam
@kurtmilam
Sep 18 2017 16:45
You can mix them, although I hardly ever write this.
It depends on what parts of OOP you want to bring in.
It's been months since I used this anywhere.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:48
I can think on some example when you have a lot of data that mutates the state and then some parts that are pure and wrote using FP
Kurt Milam
@kurtmilam
Sep 18 2017 16:49
I try to avoid mutating state. Similar to the way Immutable works with React, although I don't use Immutable.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:50
I'm trying to figure out how to make our last example work with something like this R.pipeP(setUp.user, setUp.account, setUp.apps([64, 11]))({}) I'd like to pass additional param to curry function and then passe it further
Kurt Milam
@kurtmilam
Sep 18 2017 16:50
Rather, I use observables to store state and/or I replace state with an edited copy of state rather than mutate state directly.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:51
yes, but there are always places when you can't guarantee purity, right?
Kurt Milam
@kurtmilam
Sep 18 2017 16:51
Yes, I've been thinking about that, as well. It depends on what the requirements are. You could randomize the contents of the array you pass to setUp.apps.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:52
const makeWrappedVersion = (returnKey, extractor, fun) => R.pipe(
  R.chain(R.assoc(returnKey))(R.pipe(extractor, fun)),
  Bluebird.props 
)
yes, but I think that the problem is here in my "builder" function
Kurt Milam
@kurtmilam
Sep 18 2017 16:53
The only place I can't guarantee purity is when I pass data into a third party library, and even then I can mitigate it by passing in a copy of data. If the copy isn't used by anything else, I don't care if it's mutated.
Well, you could write a setUp.randomApps and call it. Have it generate the randomized array contents and call setUp.apps with that array.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:54
I'd like to pass that additional args to fun and when I'm calling setUp.apps() I'm calling wrapped function
Kurt Milam
@kurtmilam
Sep 18 2017 16:55
You're getting into optional arguments, which I would try to avoid.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:56
it worked nice when I had normal functions that accepts only (appIds, user) so I could just use curry to specify appIds and user was taken from pipe
Kurt Milam
@kurtmilam
Sep 18 2017 16:57
Have you tried it like that with the current setup?
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:57
but now I have wrapped function plus bluebird
I'm getting some stragne bluebird error
RR TypeError: Object [object Object] has no method '[object global]'
Kurt Milam
@kurtmilam
Sep 18 2017 16:58
hm, it may be passing a function to Bluebird.props. Do you ever need to pass more than 2 arguments to a function like that?
You might need to write a wrapper for functions that require two arguments.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 16:59
I'd like to specify those appIds on function definition
so I could have something like this
Kurt Milam
@kurtmilam
Sep 18 2017 16:59
const makeWrappedVersion2 = (returnKey, extractor, fun) => x => R.pipe(
  R.chain(R.assoc(returnKey))(R.pipe(extractor, fun( x ))),
  Bluebird.props 
)
That might work. x would be your appIds. I edited that to remove the flip.
Unfortunately, I have to run.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:01
ok
Thank you, Have nice evening :)
Kurt Milam
@kurtmilam
Sep 18 2017 17:02
you too! ciao!
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:02
it worked
Kurt Milam
@kurtmilam
Sep 18 2017 17:02
:+1:
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:03
so I just added curry on wrapper and passed that arg further to underlying function
nice
Kurt Milam
@kurtmilam
Sep 18 2017 17:04
cool. so you now have one wrapper for unary functions and one for binary. hopefully you don't have functions that require 3, 4 and 5 arguments :D
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:04
I know that I shouldn't have too much args :)
probably 3 is max for a fp function
Kurt Milam
@kurtmilam
Sep 18 2017 17:05
One is my max :)
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:05
callback + arg1, arg2
Kurt Milam
@kurtmilam
Sep 18 2017 17:06
I kid, a little, but my explicit currying leads to every function being unary...
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:06
so why use curry if you have only one arg? :D
Kurt Milam
@kurtmilam
Sep 18 2017 17:06
x => y => z => doStuff
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:06
k
Kurt Milam
@kurtmilam
Sep 18 2017 17:07
This is a unary function that returns a unary function that returns a unary function that does stuff :D
This is also how Haskell works. I try to follow principles from Haskell as much as possible.
Haskell only has unary functions.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:08
it's probably easier because then you don't need to check how many args you need to pass
everything is unary
Kurt Milam
@kurtmilam
Sep 18 2017 17:09
Yes, one of the Ramda maintainers recently said he sometimes thinks about releasing Ramda 2.0 with only explicit currying like that.
Another fp javascript library, Sanctuary, is also considering moving to unary functions only, rather than the mixed style that Ramda has.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:10
I didn't know that
Kurt Milam
@kurtmilam
Sep 18 2017 17:13
sanctuary-js/sanctuary#438 In that discussion, CrossEye is a Ramda maintainer and one of the initial two developers of the library, I believe. davidchambers is a Ramda contributor and the owner of Sanctuary.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:14
it's probably easier for new comers to use (x,y) at least at the beginning
so then they can move to (x)(y)
but it's harder to read if everyone mixing it together :)
Kurt Milam
@kurtmilam
Sep 18 2017 17:16
It's probably easier, but I think it can also be more confusing, because it hides the fact that (x, y) is actually being magically transformed into (x)(y) under the hood, and it hides the fact that all of the functions are curried.
I think, but am not certain, that it might be better to deal with the uncomfortable nature of the (x)(y) syntax up front, thereby having a better understanding of what's actually happening.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:17
I just move from lodash so for me it's ok for now :)
Kurt Milam
@kurtmilam
Sep 18 2017 17:18
Sure, I mean it can be less clear what's going on and how things actually work. I think it's valuable to know that all of the functions are curried, and the (x)(y) syntax makes that clear in a way that's impossible to ignore.
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:19
agree
Kurt Milam
@kurtmilam
Sep 18 2017 17:20
I'm not sure, though. The cost may be too high for new adopters. I'm no longer a new adopter, so I can't say how hard it would have been to have had to start out with the (x)(y) syntax. It's natural to me now, but it's possible that it would have been too difficult when I was first getting started with Ramda.
OK, now I really have to run. Have a nice evening ;)
Adrian Skierniewski
@AdrianSkierniewski
Sep 18 2017 17:20
:)
Darren
@dardub
Sep 18 2017 20:32

If I have the following:

const z = { foo: [1, 3, 5], bar: [2,4,6] }
const a = prop('foo')
const b = prop('bar')

How can I zip a and b in one chain (can't think of the right word.)

The following seems to work, but seems a little clunky.

useWith(zip, [a, b])(z, z)
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:40
@ram-bot
const z = { foo: [1, 3, 5], bar: [2,4,6] };
const a = prop('foo');
const b = prop('bar');

converge(zip, [a, b])(z);
ram-bot
@ram-bot
Sep 18 2017 20:40
[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:40
R.converge
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:41
@ram-bot
const z = { foo: [1, 3, 5], bar: [2,4,6] };
const a = prop('foo');
const b = prop('bar');

lift(zip)(a, b)(z);
ram-bot
@ram-bot
Sep 18 2017 20:41
[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
Darren
@dardub
Sep 18 2017 20:42
Thank you @Bradcomp for all your help!
I didn't understand lift too well before from teh description. Gonna re-read, maybe this will help my understanding.
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:43
lift is a kinda tricky one without understanding map and ap
Barry G
@bgits
Sep 18 2017 20:43
can evolve use the value of one field in as part of the function in another field?
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:43
@bgits I don't think so
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:49
@dardub You can lift a function to operate on other functions, and it pretty much does the same thing as converge
Darren
@dardub
Sep 18 2017 20:50
Cool. Would you say that lift is more commonly used than converge? More general?
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:57
I tend to reach for converge. lift is more general, which is good, and I think people probably use it more often. converge can take multiple arguments, which is a plus
@ram-bot
[
lift(add3)(add, subtract, multiply)(1, 2),
converge(add3)([add, subtract, multiply])(1, 2)
]
ram-bot
@ram-bot
Sep 18 2017 20:58
add3 is not defined
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:58
@ram-bot
```
whoops
Darren
@dardub
Sep 18 2017 20:58
Thanks for the info and context. :D
Brad Compton (he/him)
@Bradcomp
Sep 18 2017 20:59
Barry G
@bgits
Sep 18 2017 21:48
is there something like toFixed(2) in ramda?
chaining to a ramda compose seems ackward
Sean Cannon
@SeanCannon
Sep 18 2017 22:51
See R.invoker
Sean Cannon
@SeanCannon
Sep 18 2017 22:58
const foo     = 34.12345;
const toFixed = R.invoker(1, 'toFixed');

R.compose(
  toFixed(2),
  R.multiply(3)
)(foo); // "102.37"
Denis Stoyanov
@xgrommx
Sep 18 2017 23:25
@Bradcomp lift(add3)(...map(apply ,[add, subtract, multiply]))([1, 2])