These are chat archives for ramda/ramda

24th
Apr 2016
Hardy Jones
@joneshf
Apr 24 2016 02:39 UTC
As @Avaq says, you should consider MaybeT m a. It's really made for this case specifically.
Also, to be pedantic, the parenthesis are important here. So Query -> Future (Maybe Document), Future (Maybe (Future a)), etc.
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 03:14 UTC

@joneshf A quick google search for MaybeT brings me here: https://hackage.haskell.org/package/transformers-0.5.2.0/docs/Control-Monad-Trans-Maybe.html which is... somewhat helpful I think. It looks like a monad that's created by composing a Maybe with another arbitrary monad. I don't know anything about Monad transformers at this point, so there's that.

I'm incredibly interested in incorporating more of the FL structures into my code, and the basic concepts seem straightforward. I tend to get hung up when I actually try to write code using Maybes, Futures, etc. because they require very different patterns from what I am used to in normal JS land.

Hardy Jones
@joneshf
Apr 24 2016 03:59 UTC
Okay
So monad's don't compose in general
compose meaning, you can't arbitrarily take two monads and call chain on them and expect anything reasonable to happen
like you can't just take a Future (Maybe a) and expect chain to work on it
that sort of composition
this is not true of the applicative, apply or functor parts though
for any two applicativess, you can write a general of where it does the right thing.
for any two functors, you can write a general map where it does the right thing.
similarly for applys
but chain doesn't work that way
however, certain monads compose with any other monad in general
like Identity, or Maybe or Reader.
this is what a monad transformer is
a monad that can compose with any other monad in general
Hardy Jones
@joneshf
Apr 24 2016 04:05 UTC
So, while we can't write a general chain that works with any two monads, if we know one of the monads is a transformer, we can chain through the composition without having to write all that boilerplate all the time.
So that package defines a data type MaybeT m a which is implemented like m (Maybe a) for any properly kinded data type m: Identity, [], Either e, IO, Future, Maybe, etc.
any data type that is polymorphic in an argument, basically.
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:10 UTC
Does that include basically all monads and functors?
Hardy Jones
@joneshf
Apr 24 2016 04:11 UTC
yes, and more
the line with the instance declaration: Monad m => Monad (MaybeT m) states that, so long as m is a Monad, MaybeT m is also a Monad.
So, if you can construct a Future (Maybe a), you have a Monad.
meaning, you can use chain less cumbersomely.
you should be able to do things like foo.chain(bar).chain(baz), rather than having to dig into the Future or the Maybe.
here foo :: MaybeT Future a, bar :: a -> MaybeT Future b, baz :: b -> MaybeT Future c
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:15 UTC
That's really cool! I was about to ask about the type sig's of bar and baz
Hardy Jones
@joneshf
Apr 24 2016 04:16 UTC
:+1:
So that seems like what you were asking about originally.
so you'd be able to do something like findOne(sql).chain(useDocData)
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:18 UTC
Yeah, but I'm still struggling with the patterns for using these structures in my code
Hardy Jones
@joneshf
Apr 24 2016 04:18 UTC
or since you're in ramda town, R.pipeK(findOne, useDocData)(sql)
:)
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:19 UTC
I'm basically refactoring a personal project I have as a learning exercise.
Converting it to using futures instead of promises, and trying to eliminate null checks with Maybe. It's slow going but at least I have a good test suite to keep me going
David Chambers
@davidchambers
Apr 24 2016 04:21 UTC
I'd be happy to take a look at your project, @Bradcomp, and provide feedback if you'd like me to.
Hardy Jones
@joneshf
Apr 24 2016 04:22 UTC
ditto
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:22 UTC
Damn, really?! I'll try to get my branch up on my github
Aldwin Vlasblom
@Avaq
Apr 24 2016 04:22 UTC
@joneshf I find that using transformers only really makes things less cumbersome if the Monad you're creating is very common throughout the code. If it's not, all the places where I used to do double mapping just become places where I'm lifting regular Monads into my transformed Monad. It's a different kind of clutter. Am I missing something vital?
Hardy Jones
@joneshf
Apr 24 2016 04:22 UTC
probably not
though
maybe
:)
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:23 UTC
It seems like a findOne is pretty common database activity though, which will always return a Future (Maybe Document)
Hardy Jones
@joneshf
Apr 24 2016 04:24 UTC
@Avaq I guess it depends on what the regular stuff used.
if you use explicit types, it's going to be cluttered when you change.
if you use the interfaces more, it might be less painful.
really depends on what you're doing in those functions
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:29 UTC

@joneshf @davidchambers This is the first major file I'm working with: https://github.com/Bradcomp/egghunt-server/blob/functional/routes/users.js

You can see I just kinda got stuck on the PUT /signature route.

Aldwin Vlasblom
@Avaq
Apr 24 2016 04:30 UTC
I think if you would return a MaybeT Future from findOne, you start to want all asynchronous functions to return the same structure, because you can't chain over your database result otherwise. For many asynchronous operations it doesn't actually make sense to return MaybeT Future over just Future, though, so you're going to have to lift. Or alternatively, you'd have to cast your MaybeT Future to a regular Future just so it becomes easier to chain all your other asynchronous operations. The latter solution is especially tempting because converting a Maybe to a Future is very straightforward and will usually happen at the exact place where you know the most about the nature of the Maybe, allowing you to create meaningful rejection branches.
I really like the idea of transformers, but I keep running into a wall whenever I try them. :P
Hardy Jones
@joneshf
Apr 24 2016 04:35 UTC
@Bradcomp which part in particular?
both of the commented parts?
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:37 UTC
Yeah, all the non-commented code in that route is what I had originally. The commented code is my first attempts at a functional approach
Hardy Jones
@joneshf
Apr 24 2016 04:38 UTC
k, so you want {$set: req.body.signature} for the first part, yeah?
and req.body.signature is a string?
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:40 UTC
So it's a string, but in some sense it's a Maybe(String) as it comes straight from the client, so who knows?
Hardy Jones
@joneshf
Apr 24 2016 04:41 UTC
const signature = R.map(R.objOf('$set'), S.gets(String, ['body', 'signature'], req));
I think.
R.objOf
Hardy Jones
@joneshf
Apr 24 2016 04:41 UTC
yeah, should be
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:42 UTC
I was seriously looking for something like that earlier when I was writing this stuff!
Hardy Jones
@joneshf
Apr 24 2016 04:42 UTC
similarly for req.user.id.
:)
yeah, it's kind of hard to find some of these things
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:42 UTC
So we would end up with {$set: {signature: Just(String)}} then?
wait, not
Just({$set: ...})
Hardy Jones
@joneshf
Apr 24 2016 04:43 UTC
yeah
oh, you do need that nested part.
@ram-bot R.assocPath(['$set', 'signature'], S.Just('wat'), {})
ram-bot
@ram-bot
Apr 24 2016 04:45 UTC
{ '$set': { signature: Just("wat") } }
Hardy Jones
@joneshf
Apr 24 2016 04:46 UTC
const signature = R.map(R.assocPath(['$set', 'signature'], R.__, {}), S.gets(String, ['body', 'signature'], req));
clean thatup though
:)
feels weird to have the same string on both sides though
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:47 UTC
@ram-bot
var req = {body: {signature: 'wat'}};
R.map(R.assocPath(['$set', 'signature'], R.__, {}), S.gets(['body', 'signature'], req))
ram-bot
@ram-bot
Apr 24 2016 04:47 UTC

`TypeError: Invalid value

gets :: Accessible a => TypeRep -> Array String -> a -> Maybe b
^^^^^^^
1

1) ["body", "signature"] :: Array String

The value at position 1 is not a member of ‘TypeRep’.
`

Hardy Jones
@joneshf
Apr 24 2016 04:47 UTC
really the only thing that's changing is body => $set
and projecting just that part out
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:47 UTC
Assuming the signature exists
Hardy Jones
@joneshf
Apr 24 2016 04:47 UTC
forgot an Accessible.
S.gets(String...)
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:48 UTC
@ram-bot
var req = {body: {signature: 'wat'}};
R.map(R.assocPath(['$set', 'signature'], R.__, {}), S.gets(String, ['body', 'signature'], req))
ram-bot
@ram-bot
Apr 24 2016 04:48 UTC
Just({"$set": {"signature": "wat"}})
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:48 UTC
Those type errors are really helpful @davidchambers
Hardy Jones
@joneshf
Apr 24 2016 04:48 UTC
is that the shape you want?
yeah, seriously!
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:48 UTC
Yes
Hardy Jones
@joneshf
Apr 24 2016 04:49 UTC
even though i said Accessible, when it's TypeRep...
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:49 UTC
@ram-bot
var req = {body: {}};
R.map(R.assocPath(['$set', 'signature'], R.__, {}), S.gets(String, ['body', 'signature'], req))
ram-bot
@ram-bot
Apr 24 2016 04:49 UTC
Nothing()
Hardy Jones
@joneshf
Apr 24 2016 04:50 UTC
so then id would be somewhat similar.
@ram-bot
var req = {body: {signature: 'wat'}, user: {id: 123}};
R.map(R.assocPath(['id'], R.__, {}), S.gets(String, ['user', 'id'], req))
ram-bot
@ram-bot
Apr 24 2016 04:51 UTC
Nothing()
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:51 UTC
Well, yeah, but it doesn't need to be in a Maybe because of how routing works in express
Hardy Jones
@joneshf
Apr 24 2016 04:51 UTC
oh
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:51 UTC
Er, wait, I mean because of how I wrote the code
Hardy Jones
@joneshf
Apr 24 2016 04:51 UTC
@ram-bot
var req = {body: {signature: 'wat'}, user: {id: 123}};
R.map(R.assocPath(['id'], R.__, {}), S.gets(Number, ['user', 'id'], req))
ram-bot
@ram-bot
Apr 24 2016 04:51 UTC
Just({"id": 123})
Hardy Jones
@joneshf
Apr 24 2016 04:52 UTC
:)
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:52 UTC
cuz it's coming from req.user, not req.params
Hardy Jones
@joneshf
Apr 24 2016 04:52 UTC
k
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:56 UTC
I get stuck once I have the Maybe, and I need to perform some async operations with it, and send various error messages based on the result. I end up getting tangled up in the various Monads
Hardy Jones
@joneshf
Apr 24 2016 04:57 UTC
k
so what type should updateUser have?
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 04:58 UTC
It's Query -> Updates -> Future(UpdateResult)
Where Query, Updates, and UpdateResult are all objects
Hardy Jones
@joneshf
Apr 24 2016 04:59 UTC
gotcha
Do you want it to be Future UpdateResult or do you want it to be Future (Maybe UpdateResult)?
ah, but you have a Maybe Updates anyway.
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:01 UTC
In the case of an update it will always return a result, so no need for a maybe. if the query doesn't match you get {n: 0}
Exactly
You can see in the old code there's a null check before I actually make the query.
Hardy Jones
@joneshf
Apr 24 2016 05:04 UTC
okay, I can sort of see how you might be able to put these things together.
probably going to need traverse
R.traverse
Hardy Jones
@joneshf
Apr 24 2016 05:05 UTC
erm, wat
okay, yeah
R.traverse(F.of, updateUser({id}), signature) :: Future (Maybe UpdateResult)
then what types do the other things have?
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:09 UTC
R.prop('n') is UpdateResult -> Number (which should be 1 or 0)
Hardy Jones
@joneshf
Apr 24 2016 05:10 UTC
k, so straightforwardly you should be able to R.map(R.map(R.prop('n')))
might want to clean that up though.
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:10 UTC
And sendResult is ExpressResponse -> ResponseBody -> Void... unfortunately
Hardy Jones
@joneshf
Apr 24 2016 05:11 UTC
okay
so you have 0 and 1, that you're branching off of
can 0 => Nothing and 1 => Just(true)?
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:13 UTC
I don't see why not.
Working on that right now
Hardy Jones
@joneshf
Apr 24 2016 05:16 UTC
i'm crashing fast, sorry
and grasping here
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:17 UTC
No prob
Hardy Jones
@joneshf
Apr 24 2016 05:17 UTC
didn't have lunch or dinner yet
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:17 UTC
I really appreciate the help
I have some directions to go in. Like I said, it's a learning experience, and I need to learn the patterns
David Chambers
@davidchambers
Apr 24 2016 05:19 UTC
Eat something, Hardy!
Hardy Jones
@joneshf
Apr 24 2016 05:19 UTC
:meat_on_bone:
David Chambers
@davidchambers
Apr 24 2016 05:20 UTC
:)
Hardy Jones
@joneshf
Apr 24 2016 05:20 UTC
so you can get to Future (Maybe Int)
then you can get to Future (Maybe Bool) by chaining Int -> Maybe Bool
erm
R.map(R.chain(int2MBool)) or something
can you make sendResult :: ExpressResponse -> ResponseBody -> Future Void?
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:22 UTC
Yeah, sendResult is my escape hatch. it just happens when I call it]
R.map(R.chain(R.ifElse(R.equals(1), S.Just(true), S.Nothing())))
Hardy Jones
@joneshf
Apr 24 2016 05:23 UTC
similarly sendError :: ExpressResponse -> Int -> String -> Future Void
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:23 UTC
yup!
Hardy Jones
@joneshf
Apr 24 2016 05:24 UTC
then you could do: R.chain(S.maybe(sendError(res, 500, 'something went wrong'), sendResult(res))) :: Future (Maybe ResonseBody) -> Future Void
actually, forget int2MBool
make it Int -> Maybe ResponseBody
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:25 UTC
:D
I have one more question, but I would rather you eat than help me ;)
Hardy Jones
@joneshf
Apr 24 2016 05:25 UTC
then it should probably work out...
:)
ask away
I think after that works, consider if it's worthwhile to switch to MaybeT Future a. Since like @Avaq was saying, it might be jsut a different kind of boilerplate.
oh wait, but signature :: Maybe {$ref :: {signature :: String}}
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:28 UTC
That was what my question was on
Hardy Jones
@joneshf
Apr 24 2016 05:29 UTC
updated ? R.map(R.assoc('updated', true), signature) : S.Nothing()
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:30 UTC
Is there a principled way to hold on to bits of state as you go through a pipe like this? Is that what the `State monad is for?
Hardy Jones
@joneshf
Apr 24 2016 05:30 UTC
make sense?
umm, which state?
in this case, probably not, since we're changing the type so many times.
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:32 UTC
Sorry, one sec...
updated ? R.map(R.assoc('updated', true), signature.$set) : S.Nothing()
Hardy Jones
@joneshf
Apr 24 2016 05:32 UTC
oh, right you just want the actual signature object.
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:33 UTC
Gotcha. I find myself often wanting to hold on to certain bits of data as I go through a pipeline. Another example is here: https://github.com/Bradcomp/egghunt-server/blob/507ab686e886d3a206b3fdb7928ada536170ea7e/routes/users.js#L25
It's those times when, in non-FP land, you want to declare a variable at the top of a function, just to assign it something inside your promise chain to save for later
Hardy Jones
@joneshf
Apr 24 2016 05:35 UTC
updated ? R.map(R.assoc('updated', true), S.get(Object, '$set', signature)) : S.Nothing()
Brad Compton (he/him)
@Bradcomp
Apr 24 2016 05:36 UTC
R.chain(user => insertUser(user).map(() => user))
The InsertResult is useless to me, all I care is that the Future doesn't reject when it attempts an insert. But the User, the piece I really care about, is lost if I just go straight through the pipeline.
Does that make sense?
Hardy Jones
@joneshf
Apr 24 2016 05:37 UTC
yeah, I think that is user <$ insertUser user in haskell, but I can't be entirely sure right now.
Hardy Jones
@joneshf
Apr 24 2016 05:49 UTC
oh! yes! you could do that with state
James Forbes
@JAForbes
Apr 24 2016 06:30 UTC

Is there a function like lift, but without the intermediate application?

e.g. instead of lift(add)(Container.of(1), Container.of(2) ) //=> Container(3)

You could do: lift(add, Container.of(1), Container(2) )

Scott Christopher
@scott-christopher
Apr 24 2016 08:52 UTC
@JAForbes Assuming the function is already curried (like R.add), the same can be achieved with:
ap(map(add, Container.of(1)), Container.of(2))
This is often the pattern you'll see in Haskell, though it reads nicer when you can use the operator forms of map/fmap (<$>) and ap (<*>)
add <$> pure 1 <*> pure 2
James Forbes
@JAForbes
Apr 24 2016 09:58 UTC
@scott-christopher thanks, that is pretty interesting, kind of prefer the lift(f)(...args) when the operators aren't available, but that is interesting
Scott Christopher
@scott-christopher
Apr 24 2016 09:59 UTC
Yeah, I tend to agree.
The alternative which tends to read a little better is:
map(add, Container.of(1)).ap(Container.of(2))
Though that is obviously limited to types that have an ap method.
James Forbes
@JAForbes
Apr 24 2016 10:00 UTC
That is a lot more palatable :+1:
I'm fooling around with functors + css, I have no idea if it is a good idea yet, but it's definitely helping me get my head around functors.
const em = css.unit('em')

lift(add)(em(2), em(4) ) // => em(6)

em(2).map(multiply(2))+"" //=> "4em"
I'll try out your map(f, a).ap(a)approach and see how that feels, thank you @scott-christopher
Scott Christopher
@scott-christopher
Apr 24 2016 10:26 UTC
Sounds interesting.
Keep in mind that a functor should allow for containing all types, not just numbers like you'd need to keep these valid em values.
So you might not have a functor per se, but you will have an interesting group of types that can interact in a similar fashion.
James Forbes
@JAForbes
Apr 24 2016 10:57 UTC
Sounds like I need to do more reading!
Denis Stoyanov
@xgrommx
Apr 24 2016 11:04 UTC
fapx1ap...apxn is equivalent liftMn f x1 x2 ... xn
James Forbes
@JAForbes
Apr 24 2016 12:29 UTC
@scott-christopher Would applicative still be a valid term in this context?

I try to google these terms and its just completely impenetrable to me:

https://en.wikipedia.org/wiki/Functor

@xgrommx I had come across that approach too. But thank you though.