Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change lens-view* #89

Merged
merged 3 commits into from
Jul 8, 2015
Merged

Change lens-view* #89

merged 3 commits into from
Jul 8, 2015

Conversation

jackfirth
Copy link
Owner

Closes #88, connects to #80 by making it unblock 1.0

jackfirth added a commit that referenced this pull request Jul 8, 2015
@jackfirth jackfirth merged commit f26cd40 into master Jul 8, 2015
@AlexKnauth
Copy link
Collaborator

What? Why would you change lens-view to do that instead?

@AlexKnauth
Copy link
Collaborator

It's not supposed to do that, it's supposed to be a nested reference.

@AlexKnauth
Copy link
Collaborator

And that's consistent with lens-set* and lens-transform* because those are nested updates.

@AlexKnauth
Copy link
Collaborator

Nothing to do with mapping over a list. This makes it inconsistent.

@jackfirth
Copy link
Owner Author

So it obeys the lens laws when paired with lens-set* and lens-transform*. The nested reference helper should be named something different. lens-set* and lens-transform* aren't nested, each uses the lenses separately, they aren't composed together. Now however, this relation holds:

> (lens-view* '(a b c d) first-lens third-lens)
'(a c)
> (lens-set* '(a b c d) first-lens 'a third-lens 'c)
'(a b c d)

That is, setting what you just viewed does nothing. This isn't the case with the old definition of lens-view*

@AlexKnauth
Copy link
Collaborator

No it doesn't.

@AlexKnauth
Copy link
Collaborator

lens-set* and lens-transform* do updates one after the other, and lens-view* previously did views one after the other.

@AlexKnauth
Copy link
Collaborator

What do you mean it follows the lens laws? I thought the lens laws were about how lenses were supposed to be constructed.

@jackfirth
Copy link
Owner Author

The updates were one after the other, but they were independent and each lens had the same domain of target values. It wouldn't matter what order you did them in (as long as you didn't update the same thing twice). For lens-view* previously, the order did matter.

@AlexKnauth
Copy link
Collaborator

Except the order does (and should, according to the lens laws) matter for lens-set* and lens-transform*.

@jackfirth
Copy link
Owner Author

The lens laws are about how lenses are supposed to behave with respect to view and set. If we're going to add variations of view and set, those variations should obey reasonable variations of the lens laws so as not to cause confusion and make the API easier to reason about.

@AlexKnauth
Copy link
Collaborator

Because if you do something like

(lens-set* '(a b c) first-lens '(1 2 3) (lens-thrush first-lens second-lens) "two")

@AlexKnauth
Copy link
Collaborator

Also, map is map and lens-view is lens-view, it should be the job of lens-view* to do something that you can't do so easily with a combination of those two, and something so that lens-view* with one lens should produce the same thing as lens-view with one lens.

@jackfirth
Copy link
Owner Author

In that case, the second lens is still viewing the full target, it's view just happens to collide with the view of the first lens. For lens-set*, the analogue be something like:

(lens-set* '(a b c) first-lens '(1 2 3) second-lens "two")

where later lenses view what was set by earlier lenses. That would be useful and should definitely be provided, but provided separately.

@AlexKnauth
Copy link
Collaborator

That's also something that lens-set* and lens-transform* do very well, and that lens-view* used to do very well, but this breaks that.
(Edit: this was meant to be a continuing of my previous comment)

@AlexKnauth
Copy link
Collaborator

Yes but I think it's more confusing and less useful (in reply to your last comment).

@jackfirth
Copy link
Owner Author

It's even more confusing to have one pair of viewing and setting functions that obey strict laws and another that don't. One of the big advantages of lenses over mutation or raw getters and setters is that the laws make it easy to reason about lenses and derive important properties from them.

@AlexKnauth
Copy link
Collaborator

lens-view*, lens-set*, and lens-transform* (when I added them) were supposed to be generalizations of their singular forms (with slightly different argument orders to accommodate easy rest arguments and apply).

But now lens-view* doesn't do that anymore because it returns a list of views instead of a view.

@AlexKnauth
Copy link
Collaborator

It would be better to rename your variant map-lens-view or something like that.

@AlexKnauth
Copy link
Collaborator

Because your lens-view* is no longer a generalization of lens-view.

@jackfirth
Copy link
Owner Author

There are multiple ways to generalize viewing and setting. And yes, this definition of viewing and setting should be renamed, but it should be renamed along with set* and transform* because together they obey the lens laws with respect to plural views and plural lenses.

@AlexKnauth
Copy link
Collaborator

No they don't, really, because the plural set and transform functions do not behave like a map.

@AlexKnauth
Copy link
Collaborator

Plus lens-view*, lens-set*, and lens-transform* were previously all consistent with hash-ref, hash-set, and hash-update, respectively.

@AlexKnauth
Copy link
Collaborator

If you wanted a lens-set/something that was "analogous" to the previous lens-view*, then it would be equivalent to (lens-set (lens-thrush lens ...) target value) not to whatever that was.

@AlexKnauth
Copy link
Collaborator

But I just realized we were talking about different kinds of nesting. You have been talking about nesting as in going further into nested data structures, whereas I was talking about nesting as in nested function calls, syntactically. Is that right?

@jackfirth
Copy link
Owner Author

Re what they all previously did: Yes, with the exception of lens-view*. What you're describing is the case where "I have one target, multiple lenses, and multiple new views". Both lenses and views are plural. The old behavior of lens-view* related to "I have one target, multiple lenses, and one new view". In the first case, the set* and transform* forms are analogous to hash-ref*, but viewing isn't because in order to obey the lens laws you'll need to get multiple views. If you lift lens-view into the list monad so that it returns a single-element list, then the singular case of lens-view* is equivalent to lens-view, which can be achieved by having lens-view* return multiple values. Multiple values are very awkward to work with however, and given the choice I'd rather preserve the laws.

Re nesting: Yes, we have been talking about different kinds of nesting. For what you're talking about, yes some sort of lens-set/thrush which dealt with singular views and targets and plural lenses would be great, but in order to obey the laws lens-set* would have to take only one view and set that at the end. For the case where you really want to set everything on a nested path through a data structure, that would be where those definitions for lens-set-nested come in.

@AlexKnauth
Copy link
Collaborator

Also, lens-transform* cannot be implemented the way you say according to the lens laws, because if one lens/transformer pair changes a part of the value, then according to the lens laws the next lens/transformer pair should get the new value and see that change.

@jackfirth
Copy link
Owner Author

That's a good point. So in addition to the above, it should be the case that:

(lens-set* target lens view ... ...) == (lens-set (lens-set (...) view) view)

And that:

(lens-transform* target lens f ... ...) == (lens-transform (lens-transform (...) f) f)

That should probably be the formal definition of this behavior extension.

@AlexKnauth
Copy link
Collaborator

And that's the syntactic nesting, not going deeper into the data structure.
The previous lens-view* did that same syntactic nesting, but also ended up going deeper.

@jackfirth
Copy link
Owner Author

Precisely. I think the semantic consistency is more important than the syntactic consistency, but it would be nice to have both.

@AlexKnauth
Copy link
Collaborator

Except that the * symbol normally means the syntactic nesting.

@AlexKnauth
Copy link
Collaborator

Ok. I just realized something.

@AlexKnauth
Copy link
Collaborator

If you want a "consistent" set of functions, they should all be "plural" in the same way.
So lens-set* and lens-transform* both deal with one target, and multiple lens-view pairs.
The previous lens-view* didn't do that (it dealt with one target, multiple lenses, and one view), so that's inconsistent.

@AlexKnauth
Copy link
Collaborator

But if you make a new set of functions that follow the previous lens-view*, (call them lens-view** etc. for now), then:

(lens-view** target lens ...) = (lens-view (lens-thrush lens ...) target)
(lens-set** target lens ... #:-> view) = (lens-set (lens-thrush lens ...) target view)
;; yay! keyword arguments solve the rest argument issue!
(lens-transform** target lens ... #:-> transformer) = (lens-transform (lens-thrush lens ...) target transformer)

And that's the consistent set of functions that the previous lens-view* belongs to.

@AlexKnauth
Copy link
Collaborator

Ok. So you're right. That's not where the previous lens-view* belonged.

@AlexKnauth
Copy link
Collaborator

And by the same "plurality" argument, the lens-view* that belongs in a set with the current lens-set* and lens-transform* should return a view for every lens. I see what you're saying now.

@jackfirth
Copy link
Owner Author

Yes that's exactly it. This current family of variations should be named something else though. Maybe lens-view/list? I suggest that over lens-view/map because I just realized a nice property these functions have with respect to #15, assuming the function mentioned in that issue is named compound-list-lens:

(lens-view/list target lens ...) == (lens-view target (compound-list-lens lens ...))
(lens-set/list target lens view ... ...) == (lens-set target (compound-list-lens lens ...) view ...)

This wouldn't quite hold for transformations however due to the property that later transformation functions see the results of earlier ones.

As for the new family of variations, they could be named to imply composition due to their relationships with lens-compose and lens-thrush

@AlexKnauth
Copy link
Collaborator

Ok.

@jackfirth jackfirth mentioned this pull request Jul 8, 2015
@AlexKnauth
Copy link
Collaborator

Well if you follow this reasoning, then a lot of them end up producing lists, for the boring way of defining these anyway:

> One target, multiple lenses, multiple views

(lens-view* target lens ...)
= (vals (lens-view lens target) ...) ; where vals is either `values` or `list`
(lens-set* target (~seq lens view) ...)
= (for/fold ([tgt target]) ([lns (in-list (list lens ...))]
                            [v (in-list (list view ...))])
    (lens-set lns tgt v))
(lens-transform* target (~seq lens transformer) ...)
= (for/fold ([tgt target]) ([lns (in-list (list lens ...))]
                            [f (in-list (list tarnsformer ...))])
    (lens-transform lns tgt f))

> One target, multiple lenses, one view

(lens-view** target lens ...)
= (lens-view (lens-thrush lens ...) target)
(lens-set** target lens ... #:-> view)
= (lens-set (lens-thrush lens ...) target view)
(lens-transform** target lens ... #:-> transformer)
= (lens-transform (lens-thrush lens ...) target transformer)

> Or another way for one target, multiple lenses, one view:

No view variant
(lens-set** target lens ... #:-> view)
= (for/fold ([tgt target]) ([lns (in-list (list lens ...))])
    (lens-set lns tgt view))
(lens-transform** target lens ... #:-> transformer)
= (for/fold ([tgt target]) ([lns (in-list (list lens ...))])
    (lens-transform lns tgt transformer))

> One target, one lens, multiple views?

This doesn't seem to work.

That's all the "one target" ones.

> Multiple targets, one lens, multiple views

(lens-view** lens target ...)
= (list (lens-view lens target) ...)
(lens-set** lens (~seq target view) ...)
= (list (lens-set lens target view) ...)
(lens-transform** lens (~seq target transformer) ...)
= (list (lens-transform lens target transformer) ...)

> Multiple targets, multiple lenses, one view

No view variant
(lens-set** (~seq target lens) ... #:-> view)
= (list (lens-set lens target view) ...)
(lens-transform** (~seq target lens) ... #:-> transformer)
= (list (lens-transform lens target transformer) ...)

> Multiple targets, multiple lenses, multiple views

(lens-view** (~seq target lens) ...)
= (list (lens-view lens target) ...)
(lens-set** (~seq target lens view) ...)
= (list (lens-set lens target view) ...)
(lens-transform** (~seq target lens transformer) ...)
= (list (lens-transform lens target transformer) ...)

I think that's all of those cases.

@jackfirth
Copy link
Owner Author

A lot of them do, yeah. I think in the singular cases the way you get interesting behavior is usually by making a new lens.

@AlexKnauth
Copy link
Collaborator

But I feel like most of the ones that just produce (list (lens-view lens target) ...) or (list (lens-set lens target view) ...) aren't really that useful, because it's so easy to do (map (lens-view _ _) lenses targets) or something similar.

@jackfirth
Copy link
Owner Author

I definitely don't think we should add any of them just to add them. The plurality principles are more just something to keep in mind when we come across a useful extension to viewing and setting.

@AlexKnauth
Copy link
Collaborator

Actually, given this, it seems like the new lens-view* belongs with the set:

(lens-view** target lens ...)
= (list (lens-view lens target) ...)
(lens-set** target (~seq lens view) ...)
= (list (lens-set lens target view) ...)
(lens-transform** target (~seq lens transformer) ...)
= (list (lens-transform lens target transformer) ...)

Even though the last two return multiple targets (they all consume one target), because they all have the same map behavior.

@jackfirth
Copy link
Owner Author

All three a family should share the same plurality as a rule, because I don't think it would be possible or useful to extend the lens laws to a view/set pair where the two have different pluralities. Consistency with the laws supersedes shared behavior - if the behavior can't be common across a set of functions without breaking the laws, they should probably be separately-named functions.

@AlexKnauth
Copy link
Collaborator

Re: the lens-thrush-related set of functions, would it make sense to call them lens-view->, lens-set-> and lens-transform->? I'm thinking of the clojure -> threading macro.

@jackfirth
Copy link
Owner Author

Yes. However while symbols are easier to read once you're familiar with them, words are easier to read when you're learning them. My point-free package provides the symbol ~> as an alias for thrush, it would be nice to follow that convention and have something like both lens-view/thrush and lens-view~>.

@AlexKnauth
Copy link
Collaborator

Ok, but ~ is harder to type, and this time there's no collision with -> from racket/contract or typed/racket.

@jackfirth
Copy link
Owner Author

Using slightly different symbols for the same meaning would affect readability, and I'd rather optimize for readability than keystrokes.

@AlexKnauth
Copy link
Collaborator

Yes, but that's in a different package, so readability for this package shouldn't be affected by some other package that "happens" to provide a useful function for a very similar concept, even if that happened to inspire this.

Racket programmers are used to typing dashes in between words, and this would be a dash after a word, and to typing -> arrows in things like number->string and function contracts, so word-> flows much better, and is also readable, once you know the convention.

@jackfirth
Copy link
Owner Author

It's not just a similar concept though, it's the exact same concept. And if you're the type who prefers that argument order, you're likely to have the other package as well. Additionally searching for this concept usually lands at Greg Hendershott's threading macro blog post which sets a strong precedent for ~>.

@AlexKnauth
Copy link
Collaborator

Yes, but only because -> would conflict with the contract combinator.

@jackfirth jackfirth deleted the fix-view-set-#88 branch July 11, 2015 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants