
@notjack has joined the channel

@alexknauth has joined the channel

Hi

hi!

so first of all: you and stephen use lens a bunch right?

We use the syntax lenses for processing nested ellipses

how’s that working so far?

Good

I’m not sure whether or not Stephen has used them for more things

does every turnstile
implemented lang depend on lens
transitively? what’s the minimum Racket version turnstile would like to support?

Yes, every turnstile
-implemented lang depends on lens
transitively.

I don’t see any reason why turnstile
shouldn’t support everything from 6.3 (when the new macro expander was introduced) onward, but how much a priority is that? I don’t know.

Currently it says it supports everything from 6.6 onward, but I’m not sure why

I’m wondering which nice features I’d have to refrain from using in lens’s implementation due to version support

and how much version compatibility lens
should promise

proper (non-unstable) isomorphism + prism support might require backwards incompatible changes

Do you mean backwards-incompatible as in breaking uses of non-unstable lens modules?

Maybe, I’m not sure yet

first-class isos and prisms mean a lot of things that are currently lens features that can be used in dangerous-ish ways (like lens-join
) might be replaced by things that operate in terms of isos and prisms to provide more safety

think it should just be a second package?

In that case lens-join
could be marked with a warning that using a different feature might fit better. Is that backwards-incompatible?

It could, but then I can’t use the name lens-join
for the safe interface and I can’t remove the code for it. There’s a maintenance cost to keeping the old implementation and interfaces around.

lens is probably my oldest project and I’ve learned a lot more about lenses since I started it

Do you mean that the safe interface should be able to use the name lens-join
, but have different behaviour?

yes

Would this safe interface be safe through extra restrictions in the input, or through an output with less guarantees / less known-usefulness, or both, or something different?

I think something different

basically, I want traversables, lenses, prisms, and isomorphisms all together - not just lenses

in that world, a lot of the current lens functionality would be defined on some of the other constructs instead

things that can’t respect lens laws wouldn’t be defined in terms of lenses, for example

I don’t know anything about traversables or prisms, yet.

basically there’s a hierarchy that looks like this:
Traversable
/ \
/ \
Lens Prism
\ /
\ /
Isomorphism

(man it is hard to format that in slack’s message editor thing)

You showed me that at Racket Con last year. What operations can you do with traversals and prisms, and how are they useful?

- An isomorphism says two types have the same information
- A lens says one type has the same information as another type plus some extra stuff
- A prism says one type has multiple forms, and one of those forms has the same information of another type
- A traversable says one type has zero to many instances of another type plus some some extra stuff

it’s helpful to think of them with ADTs

data SomeType = Foo String String \| Bar Int

Okay, so there is prism for an ADT with multiple variants……

Yes, sort of. If you’ve got these other types in addition to that SomeType
above:
data FooEquivalent = FooEquivalent String String
data BarEquivalent = Bar Int

Is there a good explanation of them that doesn’t rely on the Haskell “a lens is a function for all functors f” representation?

Then the following statements are true:
- There’s a
Prism SomeType FooEquivalent
- There’s a
Prism SomeType BarEquivalent
- There’s two different
Lens FooEquivalent String
values (one for first field and one for second) - There’s an
Iso BarEquivalent Int

unfortunately not, all the haskell explanations I’ve found are extremely opaque and hard to understand

but anyway

a lens deals with fields that are always there, and a prism deals with alternative forms and doesn’t handle getting stuff out of fields at all

you have to combine the two together to do something like get the Int
value out of the Bar
case of a SomeType
value

Okay. An X is one of A, B, or C. There’s a (Prism X A), a (Prism X B), and a (Prism X C). How do you use these prisms? What are the operations that define them?

there’s two operations on prisms: review
and preview

preview :: Prism a b -> a -> Maybe b
review :: Prism a b -> b -> a
Preview figures out if a value is in the right form. Review “upcasts” a value in a particular form to the parent many-form type.

Okay, so (review x-a A) is an X of the A variant.

And (preview x-a X) is (Just A) if X is the A variant, Nothing otherwise

right

Is there a reason behind the names? Why not something like “try-variant” for preview and “fit-variant” for review?

no idea ¯_(ツ)_/¯

```

Can

There

Be

Multiple lines

In a codeblock?

```

yes, you fence them the same way as on github but without specifying a lang


bar
baz

can
there
be

hmmm

Can
there
be
multiple lines
in a codeblock
this way?

that seems to work

are you doing it as separate messages or all in a single message?

I don’t know how to do multiple lines in a single message?



lines like this?

I think I hate the “chat” format.

shift-enter instead of enter on a laptop

Okay like this thanks.

yes like that

and this is a
multi-line
code block
it's annoying to type

the reason I wanted chat over github issues or something was because it’s less async which helps when there’s lots of back-and-forth questions

I’m not sure what another good format for that would be


that looks right yes

(try-variant x/a (fit-variant x/a A)) = (just A)

yes exactly

that’s in the haskell prism docs as a law


preview l (review l b) ≡ Just b

sidenote: I think I like the names “upcast” and “downcast” for “fit” and “try” (or “review” and “preview”)

I don’t like “upcast” and “downcast”

what naming ideas make sense to you?

Is there something wrong with “try-variant” and “fit-variant”?

“try” makes me think of exceptions, “fit” makes me think of statistical approximation stuff, and I’m not sure how to link them to the name “prism”

the abstractness of prisms makes them hard to name and describe

good names are probably hard to find and it’s worth looking for lots of ideas

Maybe prism is the wrong word. This seems more like a polarizing filter than a prism.

it’s part of the lens
metaphor - a lens lets you focus on a small part of an image, while a prism lets you split the view of the image into components and only look at once component

not using prism but using lens would be confusing, so they’d probably either have to both be used or neither

Multiple polarizing filters let you split the view of an image into multiple components. But one polarizing filter only lets you see one variant.

hmmm

what about using filter
to describe what a prism does to stuff?

Huh?

as in, naming the try-variant
/ preview
operation something like prism-filter

but list filtering isn’t prismatic so maybe that’s confusing

prisms abstract over the case-selection part of match, so maybe a name like prism-match
, prism-case
, prism-if
, or something like that would work

Match, case, and if all take some form of code to execute in each case (even in lazy function versions). try-variant
doesn’t do that, and doesn’t imply that.

try
implies it by way of exceptions and try { ... } catch
though

hmm, what about using option
/ maybe
for naming

prism-optional

There are plenty of uses of try-___
that don’t imply that.

I’m looking more to explore options than to commit to something - this is pretty early in the design process

channel-try-get
, regexp-try-match
, thread-try-receive
, will-try-execute
are a few

all of those are related to concurrent events and whether a synchronizable event is ready for synchronization

we don’t have to pick a name yet though - do you have more questions about prisms / isos / traversals?

How would the “safe interface” use lens-join
for something different?

ah yes! forgot about lens-join
for a bit

so in this new world, I think lens-join
would probably be something that lets you apply a list of lenses to a list of values like this:
> (struct posn (x y))
> (struct health (current max))
> (lens-view
(lens-join posn-x-lens health-current-lens)
(list (posn 2 -5) (health 65 100)))
(list 2 65)

you’d use isomorphisms to convert other things to and from lists

Okay. I guess that fits with what the join
word means in point-free
. But what about when people want the functionality similar to what’s currently provided by lens-join/list?

the core problem is that lenses aren’t functors and can’t be - there’s no way to take a Lens a b
and a b -> c
function and produce a Lens a c

you can extend them in a functor-like way with isomorphisms though, and I think current uses of lens-join
ought to be doable with that approach instead

is lens-join
used in turnstile
?

No, lens-join
is not used in turnstile
as far as I know.

Bt

But

My current uses of lens-join
could (and maybe should) be re-written using something like:

also I think this paper is the “modern” definition of lenses: https://arxiv.org/pdf/1703.10857.pdf


hmm, I think that’s equivalent to saying make-from-A-B-C
must be an isomorphism from Target
to (List A B C)

so if you used isos and lenses to do this you might have something like this:

> (struct target (a b c))
> (define target<->list
(make-iso
#:to (lambda (t) (list (target-a t) (target-b t) (target c t)))
#:from (lambda (abc) (apply target abc)))
> (iso-to target<->list (target 1 2 3))
'(1 2 3)
> (iso-from target<->list '(1 2 3))
(target 1 2 3)

So then it could be re-written to:


yes that’s it

and then you could compose it with a reverse of the iso to go back to the struct form at the end

(provided the view of each field has a type that’s appropriate for use as that entire field)

wait, I think I was thinking of something else in those last two sentences so disregard that

your example is correct though

a (struct<->list <struct-id>)
macro would be more appropriate than specifying each field (what happens if you forget a field? it’s no longer an iso in that case)

But in some cases it’s not a struct. It could be a natural number that I’m getting the quotient and remainder of, or something like that.

in that case explicitly constructing an iso that turns it into a list or struct would be the way to go

I’m not sure a quotient and remainder forms an isomorphism

So that would be (make-constructor-accessor-list-iso (λ (q r) (+ (* N q) r)) (curryr quotient N) (curryr remainder N))

For 0 <= r < N
of course.

yes you could do that - but that’s roughly equivalent to making an iso with a pair of functions and using something that applies multiple functions to the same value and produces a list

call that fork

then you’d do it like this:

> ((fork add1 sub1) 10) ;; applies multiple functions to the same input
'(11 9)
> (define (natural<->quotient+remainder N)
(make-iso
#:to (fork (quotient _ N) (remainder _ N))
#:from (λ (q r) (+ (* N q) r))))

> (iso-to (natural<->quotient+remainder 10) 436)
'(43 6)
> (iso-from (natural<->quotient+remainder 10) '(43 6))
436

Right. So make-constructor-accessor-list-iso
could be implemented by


exactly, yes

hmmm

I can see a use for providing that directly instead of having people use something like fork
manually

it’s a very long name though

maybe just accessor-iso

ah of course, now I think I see what you were going for - a struct-iso
macro just needs to look up the constructor and fields and pass that to accessor-iso

given (struct target (a b c))
, then (struct<->list target)
is (accessor-iso target target-a target-b target-c)

which is (make-iso #:to (fork target-a target-b target-c) #:from target)

I like long descriptive names better than enigmatic, ambiguous names

agreed

it’s probably not worth it to have that function at all then - without a good short name for it it’s longer than the equivalent use of make-iso
and fork

No, I would understand make-constructor-accessor-list-iso
a lot better than make-iso
/ fork
if I saw them in (someone else’s) code.

I don’t think there are enough uses for it though

about the only time it applies is when there are multiple-valued functions that happen to split a value up in some weird way, like quotient/remainder
or partition

that’s pretty rare

what would be more readable than either a helper for that or explicit use of make-iso
and fork
is to not return things as multiple values in the first place and make a named struct for the pair of values

> (struct division (quotient remainder))
> (define (divide n m) (call/values (thunk (quotient/remainder n m)) division))

I think make-iso
/ fork
would be more confusing….

I think they’re both confusing in this case, because there’s nothing to suggest that the functions used as “accessors” actually behave as accessors - they’re just any random functions picked by the caller

that makes it really easy to pick functions that make bad isomorphisms

But the name suggests properties on the behavior like (get-A (make-the-thing A B C)) = A
, etc.

It suggests it, but doesn’t enforce it. But a struct definition with a smart constructor that pairs the right functions together does enforce it.

People who understand structs well enough will be able to see when those properties hold and when they don’t.

the people who don’t will get bad and confusing results that the lens functions can’t detect and raise errors for

in the quotient+remainder case I’d just use (compose list quotient/remainder)
anyway

There are more examples than quotient/remainder
. That just happened to be the one I remembered doing myself recently.

I think there aren’t many use cases but there are a lot of cases that look like they should work but don’t - that’s why I’m wary of a special function for this

partition
doesn’t work for example

it destroys position information

What’s the constructor to reverse partition
? There is none. That’s why it’s important that both “constructor” and “accessor” be part of the name.

the #:from
arg name of make-iso
does that too

I’d want to see a couple more use cases before making something like that

Tx-exprs are another one

(make-constructor-accessor-list-iso txexpr get-tag get-attrs get-elements)

there’s already a txexpr->list
function though

so you’d just do (make-iso #:to txexpr->list #:from (apply txexpr _))