These are chat archives for ramda/ramda

20th
Jun 2015
David Chambers
@davidchambers
Jun 20 2015 00:44
Why do Ramda’s lenses require a getter and a setter? Based on DrBoolean’s video I have the impressions that one of the things that make lenses so elegant is that both getter and setter can be derived from one function. /cc @joneshf
 *      headLens([10, 20, 30, 40]); //=> 10
 *      headLens.set('mu', [10, 20, 30, 40]); //=> ['mu', 20, 30, 40]
 *      headLens.map(function(x) { return x + 1; }, [10, 20, 30, 40]); //=> [11, 20, 30, 40]
It seems the headLens.set example could be written as:
headLens.map(R.always('mu'), [10, 20, 30, 40]);
At which point there’s no need for set, so we could simply return a function rather than a { set :: Function, map :: Function }.
Perhaps then we could get rid of R.composeL and R.pipeL.
David Chambers
@davidchambers
Jun 20 2015 00:55
Oh I see, one needs to specify how to get the value and how to rebuild the data structure.
Scott Sauyet
@CrossEye
Jun 20 2015 02:02
I think lenses aren't as useful in JS where nested property access is fairly normal. They are still interesting, and consolidate the handling of a particular property in a nice way, as well as provide pure-functional handling for something often done with object methods. But they're far from essential.
David Chambers
@davidchambers
Jun 20 2015 02:03
They seem very useful to me in the work I’ve been doing at Plaid!
I’m getting to grips with the implementations at the moment. I’m pretty sure we can make some nice simplifications. :)
Scott Sauyet
@CrossEye
Jun 20 2015 02:04
Cool. I just think they fall in the same group as Comonads; things that are important to Haskell, but can often be done more simply in JS.
David Chambers
@davidchambers
Jun 20 2015 02:04
Perhaps you’re right. Let me see.
Scott Sauyet
@CrossEye
Jun 20 2015 02:04
That would be fine. I haven't looked at them lately. I thought they were already fairly simple.
David Chambers
@davidchambers
Jun 20 2015 02:05
Our current implementation doesn’t seem at all simple to me.
We return a function which has set and map methods? Weird.
They need a special compose function? Also weird.
Scott Sauyet
@CrossEye
Jun 20 2015 02:06
Ok, it's the API you find complex, then?
David Chambers
@davidchambers
Jun 20 2015 02:06
Yes.
Scott Sauyet
@CrossEye
Jun 20 2015 02:07
I thought you were talking implementation, which I remember as pretty simple.
David Chambers
@davidchambers
Jun 20 2015 02:07
Yeah, I should have said API rather than implementation. There’s not too much code.
Scott Sauyet
@CrossEye
Jun 20 2015 02:13
We could certainly build a multiple-function API: one to construct a lens, one to get using a lens and an object, one to set/assoc using a lens, a value, and an object, and then figure out how to work with these as Functors, and maybe other algebraic types.
But that has a real disadvantage too: Ramda generally imposes no workflow at all. It generally doesn't matter where things come from. Unless we create some sort of OO-style API for our lenses that others could duplicate, it's likely that the only way users could use these get, etc. functions would be to pass in the output of R.lens. That feels odd to me.
Scott Sauyet
@CrossEye
Jun 20 2015 02:19
DrBoolean's lenses are something like that.
puffnfresh's lenses are something like ours, but the object context is bound in with .run(context) before you can call get or set on the result. He also has a custom compose.
Joseph Abrahamson
@tel
Jun 20 2015 04:18
@davidchambers the "getter + setter" formulation is a fairly straightforward one
but there's a tricky way to see it as just one very generic function
which is the basis of what're called van Laarhoven lenses
and the fundamental theory of the fairly popular lens Haskell package
Given that DrBoolean describes the library as "Kmett-style" I assume they're van Laarhoven
the neat part about van Laarhoven lenses is that they compose as normal higher-order functions
David Chambers
@davidchambers
Jun 20 2015 04:20
That’s great!
Joseph Abrahamson
@tel
Jun 20 2015 04:20
the (Haskell) type looks a bit like
David Chambers
@davidchambers
Jun 20 2015 04:20
Yes, I have a version of DrBoolean’s lenses working with R.compose.
Joseph Abrahamson
@tel
Jun 20 2015 04:20
Lens s a = (a -> F a) -> (s -> F s)
so the (_ -> F _) bits compose
yeah
David Chambers
@davidchambers
Jun 20 2015 04:21
Could you break down that type for me?
Joseph Abrahamson
@tel
Jun 20 2015 04:21
it's interesting to think of the best way to emulate Haskell's bound polymorphism in Javascript
I can, hm.
I need to write the honest one first, though
David Chambers
@davidchambers
Jun 20 2015 04:21
:)
Joseph Abrahamson
@tel
Jun 20 2015 04:21
here's a fairly simple, honest lens type
type Lens s a = forall f . Functor f => (a -> f a) -> (s -> f s)
in a simple sense, you can see that this has a type not too unlike a map
if you let F a = a
then instantiate f ---> F
it's (a -> a) -> (s -> s)
David Chambers
@davidchambers
Jun 20 2015 04:23
Hang on a sec.
You lost me at F a = a.
Joseph Abrahamson
@tel
Jun 20 2015 04:23
so if a is a "subpart" of s, then this function takes a transformation of subparts and turns it into a transformation of "wholes"
ah, yeah
I'm just trying to say that it's a valid thing
to turn the type
forall f . Functor f => (a -> f a) -> (s -> f s)
into
(a -> a) -> (s -> s)
by letting f be completely trivial
David Chambers
@davidchambers
Jun 20 2015 04:24
Ah, something like Identity you mean?
Joseph Abrahamson
@tel
Jun 20 2015 04:24
yep, exactly
David Chambers
@davidchambers
Jun 20 2015 04:24
Great. I’m with you now.
Joseph Abrahamson
@tel
Jun 20 2015 04:24
cool, so, that's really just a hint of intuition
or a suggestion that maybe this type does what we want
it transforms "subpart mappers" into "whole-thing mappers"
when f is Identity
David Chambers
@davidchambers
Jun 20 2015 04:25
Oh, I see!
(a -> a) is the subpart mapper, something like R.toUpper.
Joseph Abrahamson
@tel
Jun 20 2015 04:25
yep
and you might want it to be more general
like (a -> b) -> (s -> t)
David Chambers
@davidchambers
Jun 20 2015 04:25
And we return a function that operates on the top-level data structure but transforms the focus.
Joseph Abrahamson
@tel
Jun 20 2015 04:26
exactly!
:)
David Chambers
@davidchambers
Jun 20 2015 04:26
Gotcha.
Joseph Abrahamson
@tel
Jun 20 2015 04:26
so that's "setting"
since if you put a constant "subpart mapper" in you'll get a setter
David Chambers
@davidchambers
Jun 20 2015 04:26
I can see why (a -> b) is useful.
Joseph Abrahamson
@tel
Jun 20 2015 04:26
yeah, something like length might be nice to have
so you need "type changing lenses"
which work just the same... they're just more complex to type
so the other interesting choice for f is Const
newtype Const b a = Const b
so, Const b a is really just a b pretending to be an a
then if we have
forall f . Functor f => (a -> f a) -> (s -> f s)
and we let f become Const a
we'll get
(a -> Const a a) -> (s -> Const a s)
Now, we can construct an argument to this function
the data constructor Const itself works
David Chambers
@davidchambers
Jun 20 2015 04:28
Yes, it does!
Joseph Abrahamson
@tel
Jun 20 2015 04:28
Const :: b -> Const b a
and then since Const b a is really just b
(a -> Const a a) -> (s -> Const a s) becomes (a -> Const a a) -> (s -> a)
so when we feed it the data constructor Const it becomes a function (s -> a), a "getter"
David Chambers
@davidchambers
Jun 20 2015 04:29
Hang on again.
Joseph Abrahamson
@tel
Jun 20 2015 04:30
and we've recovered the setters and the getters from the van Laarhoven form :)
ko
ok
David Chambers
@davidchambers
Jun 20 2015 04:30
I don’t see that sustitution you made there.
Joseph Abrahamson
@tel
Jun 20 2015 04:30
okay, I went a little fast and loose
David Chambers
@davidchambers
Jun 20 2015 04:30
(a -> Const a a) -> (s -> Const a s) becomes (a -> Const a a) -> (s -> a)
Joseph Abrahamson
@tel
Jun 20 2015 04:30
yeah
I transformed Const a s into a
using the function
runConst (Const a) = a
David Chambers
@davidchambers
Jun 20 2015 04:30
Okay, yep.
Joseph Abrahamson
@tel
Jun 20 2015 04:30
runConst :: Const a b -> a
so using Identity and Const you can turn lens :: forall f . Functor f => (a - > f a) -> (s -> f s) into a setter (a -> a) -> (s -> s) and a getter (s -> a)
David Chambers
@davidchambers
Jun 20 2015 04:31
That is really cool.
Joseph Abrahamson
@tel
Jun 20 2015 04:31
and we can go the opposite way
if we assume we have set :: (a -> a) -> (s -> s) and get :: s -> a
David Chambers
@davidchambers
Jun 20 2015 04:32
Any reason for the choice of a and s, out of interest?
Joseph Abrahamson
@tel
Jun 20 2015 04:33
lens inj s = fmap (\a' -> set (const a') s) (inj (get s))
oh, the convention in Ed's library is that a type changing lens has the following full type
type Lens s t a b = forall f . Functor f => (a -> f b) -> (s -> f t)
which lets you have things like
_1 :: Lens a b (a, x) (b, x) a lens focused over the first element of a tuple
I think s t a b is kind of a joke
but it's also sort of nice
a and b are near one another in the alphabet and are both typing the "focused subpart"
s and t are likewise typing the "whole"
David Chambers
@davidchambers
Jun 20 2015 04:34
Haha, I didn’t notice “stab” until you pointed it out.
Joseph Abrahamson
@tel
Jun 20 2015 04:34
haha, yeah
the joke was that at one point they were adding more type variables and ended up with i s t a b u and then figured it was a sign ;)
David Chambers
@davidchambers
Jun 20 2015 04:35
So the lens inj s definition, is that creating a transformer from get and set?
Joseph Abrahamson
@tel
Jun 20 2015 04:35
yeah
it's kind of a lot, haha
but it turns out to have exactly the type we want
it works a bit like
lens inj s = ...
the inj bit (I call it an "injector", thus the name) has type forall f . Functor f => a -> f a
and s has type s
we use get :: s -> a to get a valid argument to inj
so
inj (get s) :: forall f . Functor f => f a
ultimately, we need lens inj s :: forall f . Functor f => f s
so if we had a function z :: a -> s we could fmap it
lens inj s = fmap z (inj (get s))
and we can construct such a z using set
z a = set (\_ -> a) s
where s is bound in the definition of lens
so the whole thing is
lens inj s = let z a = set (const a) s in fmap z (inj (get s))
since we only use fmap this is invariant to any choice of Functor
so later we can pick Identity or Const however we like in order to extra the set and get parts again
David Chambers
@davidchambers
Jun 20 2015 04:41
So we use something like runIdentity . Identity or getConst . (Const 42) or we provide a custom function which actually transforms the existing value of the focus?
Thank you very much for taking the time to explain this, @tel! I confess to not understanding all of it, but these things sometimes take multiple exposures. :)
Joseph Abrahamson
@tel
Jun 20 2015 04:42
Well, you can just write over :: Lens s t a b -> (a -> b) -> (s -> t) and view :: Lens s t a b -> (s -> a) directly as higher order functions
np!
it's certainly a tricky concept!
but really nice once you see what's going on
David Chambers
@davidchambers
Jun 20 2015 04:43
Yes!
Ramda provides some lens functions, but they don’t seem as elegant as they could be.
Joseph Abrahamson
@tel
Jun 20 2015 04:43
over l ab s = runIdentity (l (Identity . f) s)
view l s = runConst (l Const s)
Ramda's lenses are based on explicitly reifying the get and over parts
which is equivalent as we just saw
but there are two weaknesses
first, composition can't just be function composition anymore
which might just feel like a gimmick
David Chambers
@davidchambers
Jun 20 2015 04:45
That’s a real shame.
Joseph Abrahamson
@tel
Jun 20 2015 04:45
but the second one brings it home
Lens is just one kind of "focusy thing"
it has a dual called Prism, e.g.
each of them have a supertype called Iso or even Eq
there are Traversals and Folds as well
these things generalize many ideas inside of Lens
and they don't appear to play together at all on the surface
but
they all have a van Laarhoven representation
and when they are all written in that way
function composition works between them all
David Chambers
@davidchambers
Jun 20 2015 04:46
That’s very cool!
Joseph Abrahamson
@tel
Jun 20 2015 04:46
and automatically handles type coercion
so
the composition of a Lens and a Prism is naturally a Fold
and the van Laarhoven representation does that for you automatically
it's really cool
totally mind blowing when you first see it
David Chambers
@davidchambers
Jun 20 2015 04:47
Seems like magic!
Joseph Abrahamson
@tel
Jun 20 2015 04:47
there's some really deep theory that makes it all work
which is kind of awesome
it just ends up being various kinds of things you can demand from profunctors
or rather profunctor transformers, I suppose
they form a nice basis to the whole thing
when you write Iso s t a b = forall (~>) . Profunctor (~>) => (a ~> b) -> (s ~> t) ; )
but that's... another story!
haha
David Chambers
@davidchambers
Jun 20 2015 04:49
Yes, I think it is. :)
I’m going to look at the possibility of Ramda providing lens, view, set, and over rather than the OO-style lenses it currently provides. It would be nice to remove the need for R.composeL and R.pipeL!
Joseph Abrahamson
@tel
Jun 20 2015 04:52
: )
I think the big trick is finding the right way to represent forall f . Functor f => ...
it ought to be possible using some kind of subtyping relationship
I made ... a REALLY ugly draft of profunctor lenses like I was writing in Javascript a while back
so it's possible :)
but the design space needs to be worked through
it'd be really exciting to see it in Ramda
David Chambers
@davidchambers
Jun 20 2015 04:53
Haha, okay. I’ll mention you on the GitHub issue if I’m able to get something working.
Joseph Abrahamson
@tel
Jun 20 2015 04:56
sounds good : )
Joseph Abrahamson
@tel
Jun 20 2015 05:06
@davidchambers Here's a quick sketch of the idea https://gist.github.com/tel/ef6e4c3d936142b493a9
it's maybe a little raw... I'm using a trick with the "modules"
IdModule.fmap ought to have the type (a -> b) -> (Id a -> Id b)
but it actually has a type like (a -> b) -> (a -> b)
... but that's okay because Id a is "the same as" a
similarly, ConstModule.fmap has the type (a -> b) -> (c -> c)
when it ought to have (a -> b) -> (Const c a -> Const c b)
but again... those are the same
David Chambers
@davidchambers
Jun 20 2015 05:09
This is great! Thanks!
Joseph Abrahamson
@tel
Jun 20 2015 05:09
np :)
oh, and of course
const compose = (f) => (g) => (x) => f(g(x)) ;)
oh
nevermind, that won't work anymore
it'd have to be
const compose = (f) => (g) => (mod) => (x) => f(mod)(g(mod)(x))
that's why I didn't like this form :D
David Chambers
@davidchambers
Jun 20 2015 05:14
Yes!
Joseph Abrahamson
@tel
Jun 20 2015 05:14
you need a way to implicitly pass fmaps around
you only need to define them at the very end when you call get or set
so there needs to be a little more artifice
David Chambers
@davidchambers
Jun 20 2015 08:08
ramda/ramda#1205
Scott Sauyet
@CrossEye
Jun 20 2015 09:05
See what happens when I go to bed at a reasonable hour? I miss all the fun stuff! Very interesting discussion.
Raine Virta
@raine
Jun 20 2015 09:12
Hardy Jones
@joneshf
Jun 20 2015 13:20
@tel if you go with a less ml style and more oo formulation, it turns out not so bad. I played with van Laarhoven's in ruby a while back, it seemed to be pretty straight forward.
I'll throw up a gist when I get home if I remember and can find it.
asaf-romano @asaf-romano is curious why composeP and pipeP aren't the "default" compose and pipe.