notjack
2017-12-17 00:56:42

@notjack has joined the channel


alexknauth
2017-12-17 00:56:42

@alexknauth has joined the channel


alexknauth
2017-12-17 00:58:40

Hi


notjack
2017-12-17 00:58:57

hi!


notjack
2017-12-17 00:59:32

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


alexknauth
2017-12-17 01:00:23

We use the syntax lenses for processing nested ellipses


notjack
2017-12-17 01:01:24

how’s that working so far?


alexknauth
2017-12-17 01:02:02

Good


alexknauth
2017-12-17 01:02:47

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


notjack
2017-12-17 01:04:41

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


alexknauth
2017-12-17 01:05:23

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


alexknauth
2017-12-17 01:06:54

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.


alexknauth
2017-12-17 01:07:59

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


notjack
2017-12-17 01:08:28

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


notjack
2017-12-17 01:08:39

and how much version compatibility lens should promise


notjack
2017-12-17 01:10:40

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


alexknauth
2017-12-17 01:12:53

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


notjack
2017-12-17 01:13:02

Maybe, I’m not sure yet


notjack
2017-12-17 01:14:02

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


notjack
2017-12-17 01:14:58

think it should just be a second package?


alexknauth
2017-12-17 01:16:18

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


notjack
2017-12-17 01:19:06

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.


notjack
2017-12-17 01:20:12

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


alexknauth
2017-12-17 01:20:41

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


notjack
2017-12-17 01:20:48

yes


alexknauth
2017-12-17 01:26:23

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?


notjack
2017-12-17 01:27:34

I think something different


notjack
2017-12-17 01:27:51

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


notjack
2017-12-17 01:28:04

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


notjack
2017-12-17 01:28:42

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


alexknauth
2017-12-17 01:29:09

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


notjack
2017-12-17 01:30:52

basically there’s a hierarchy that looks like this:

       Traversable
       /       \
      /         \
 Lens          Prism
     \            /
       \         /
     Isomorphism

notjack
2017-12-17 01:31:48

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


alexknauth
2017-12-17 01:33:04

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


notjack
2017-12-17 01:34:06
  • 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

notjack
2017-12-17 01:34:54

it’s helpful to think of them with ADTs


notjack
2017-12-17 01:35:29
data SomeType = Foo String String \| Bar Int

alexknauth
2017-12-17 01:35:36

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


notjack
2017-12-17 01:37:39

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

alexknauth
2017-12-17 01:38:49

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


notjack
2017-12-17 01:39:15

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

notjack
2017-12-17 01:39:37

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


notjack
2017-12-17 01:39:47

but anyway


notjack
2017-12-17 01:40:25

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


notjack
2017-12-17 01:41:02

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


alexknauth
2017-12-17 01:41:14

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?


notjack
2017-12-17 01:42:46

there’s two operations on prisms: review and preview


notjack
2017-12-17 01:44:22
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.


alexknauth
2017-12-17 01:45:01

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


alexknauth
2017-12-17 01:45:43

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


notjack
2017-12-17 01:47:38

right


alexknauth
2017-12-17 01:47:50

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


notjack
2017-12-17 01:48:06

no idea ¯_(ツ)_/¯


alexknauth
2017-12-17 01:51:38

```


alexknauth
2017-12-17 01:51:43

Can


alexknauth
2017-12-17 01:51:44

There


alexknauth
2017-12-17 01:51:46

Be


alexknauth
2017-12-17 01:51:52

Multiple lines


alexknauth
2017-12-17 01:51:57

In a codeblock?


alexknauth
2017-12-17 01:51:58

```


notjack
2017-12-17 01:53:09

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


alexknauth
2017-12-17 01:53:23

notjack
2017-12-17 01:53:33
bar
baz

notjack
2017-12-17 01:55:14
can
there
be

notjack
2017-12-17 01:55:16

hmmm


notjack
2017-12-17 01:55:28
Can
there
be
multiple lines
in a codeblock
this way?

notjack
2017-12-17 01:55:33

that seems to work


notjack
2017-12-17 01:55:51

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


alexknauth
2017-12-17 01:56:11

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


alexknauth
2017-12-17 01:56:34
Can I

alexknauth
2017-12-17 01:56:40
write multilpe

alexknauth
2017-12-17 01:56:45

lines like this?


alexknauth
2017-12-17 01:57:14

I think I hate the “chat” format.


notjack
2017-12-17 01:57:22

shift-enter instead of enter on a laptop


alexknauth
2017-12-17 01:57:43

Okay like this thanks.


notjack
2017-12-17 01:57:49

yes like that


alexknauth
2017-12-17 01:58:27
and this is a
multi-line
code block
it's annoying to type

notjack
2017-12-17 02:00:08

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


notjack
2017-12-17 02:00:30

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


alexknauth
2017-12-17 02:01:49

notjack
2017-12-17 02:06:58

that looks right yes


alexknauth
2017-12-17 02:08:06

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


notjack
2017-12-17 02:08:12

yes exactly


notjack
2017-12-17 02:08:27

that’s in the haskell prism docs as a law



notjack
2017-12-17 02:08:55
preview l (review l b) ≡ Just b

notjack
2017-12-17 02:09:35

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


alexknauth
2017-12-17 02:09:50

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


notjack
2017-12-17 02:10:54

what naming ideas make sense to you?


alexknauth
2017-12-17 02:14:04

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


notjack
2017-12-17 02:16:38

“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”


notjack
2017-12-17 02:18:05

the abstractness of prisms makes them hard to name and describe


notjack
2017-12-17 02:18:24

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


alexknauth
2017-12-17 02:18:26

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


notjack
2017-12-17 02:19:28

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


notjack
2017-12-17 02:20:03

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


alexknauth
2017-12-17 02:20:19

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


notjack
2017-12-17 02:21:55

hmmm


notjack
2017-12-17 02:22:15

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


alexknauth
2017-12-17 02:22:45

Huh?


notjack
2017-12-17 02:23:13

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


notjack
2017-12-17 02:24:28

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


notjack
2017-12-17 02:26:01

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


alexknauth
2017-12-17 02:28:42

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.


notjack
2017-12-17 02:29:05

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


notjack
2017-12-17 02:29:31

hmm, what about using option / maybe for naming


notjack
2017-12-17 02:29:49

prism-optional


alexknauth
2017-12-17 02:30:35

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


notjack
2017-12-17 02:32:57

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


alexknauth
2017-12-17 02:34:53

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


notjack
2017-12-17 02:35:46

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


notjack
2017-12-17 02:36:40

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


alexknauth
2017-12-17 02:38:12

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


notjack
2017-12-17 02:38:39

ah yes! forgot about lens-join for a bit


notjack
2017-12-17 02:44:02

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)

notjack
2017-12-17 02:44:43

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


alexknauth
2017-12-17 02:45:28

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?


notjack
2017-12-17 02:45:52

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


notjack
2017-12-17 02:46:59

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


notjack
2017-12-17 02:47:14

is lens-join used in turnstile?


alexknauth
2017-12-17 02:47:32

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


alexknauth
2017-12-17 02:47:34

Bt


alexknauth
2017-12-17 02:47:36

But


alexknauth
2017-12-17 02:48:12

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


notjack
2017-12-17 02:53:37

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


alexknauth
2017-12-17 02:55:50

notjack
2017-12-17 02:58:22

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


notjack
2017-12-17 02:58:53

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


notjack
2017-12-17 03:01:54
> (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)

alexknauth
2017-12-17 03:03:00

So then it could be re-written to:


alexknauth
2017-12-17 03:04:54

notjack
2017-12-17 03:05:30

yes that’s it


notjack
2017-12-17 03:05:52

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


notjack
2017-12-17 03:07:08

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


notjack
2017-12-17 03:10:44

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


notjack
2017-12-17 03:11:05

your example is correct though


notjack
2017-12-17 03:12:08

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)


alexknauth
2017-12-17 03:14:35

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.


notjack
2017-12-17 03:15:40

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


notjack
2017-12-17 03:16:39

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


alexknauth
2017-12-17 03:17:01

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


alexknauth
2017-12-17 03:17:32

For 0 <= r < N of course.


notjack
2017-12-17 03:19:01

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


notjack
2017-12-17 03:19:05

call that fork


notjack
2017-12-17 03:19:09

then you’d do it like this:


notjack
2017-12-17 03:23:47
> ((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))))

notjack
2017-12-17 03:24:55
> (iso-to (natural<->quotient+remainder 10) 436)
'(43 6)
> (iso-from (natural<->quotient+remainder 10) '(43 6))
436

alexknauth
2017-12-17 03:25:22

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


alexknauth
2017-12-17 03:26:00

notjack
2017-12-17 03:26:08

exactly, yes


notjack
2017-12-17 03:26:33

hmmm


notjack
2017-12-17 03:27:01

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


notjack
2017-12-17 03:27:14

it’s a very long name though


notjack
2017-12-17 03:28:50

maybe just accessor-iso


notjack
2017-12-17 03:29:59

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


notjack
2017-12-17 03:32:02

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


notjack
2017-12-17 03:33:07

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


alexknauth
2017-12-17 03:34:14

I like long descriptive names better than enigmatic, ambiguous names


notjack
2017-12-17 03:35:01

agreed


notjack
2017-12-17 03:35:36

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


alexknauth
2017-12-17 03:36:50

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.


notjack
2017-12-17 03:38:05

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


notjack
2017-12-17 03:38:36

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


notjack
2017-12-17 03:38:42

that’s pretty rare


notjack
2017-12-17 03:39:23

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


notjack
2017-12-17 03:40:42
> (struct division (quotient remainder))
> (define (divide n m) (call/values (thunk (quotient/remainder n m)) division))

alexknauth
2017-12-17 03:41:08

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


notjack
2017-12-17 03:41:57

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


notjack
2017-12-17 03:42:17

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


alexknauth
2017-12-17 03:42:56

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


notjack
2017-12-17 03:43:40

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.


alexknauth
2017-12-17 03:44:45

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


notjack
2017-12-17 03:45:31

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


notjack
2017-12-17 03:45:58

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


alexknauth
2017-12-17 03:48:44

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


notjack
2017-12-17 03:50:47

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


notjack
2017-12-17 03:51:06

partition doesn’t work for example


notjack
2017-12-17 03:51:11

it destroys position information


alexknauth
2017-12-17 03:52:38

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.


notjack
2017-12-17 03:54:21

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


notjack
2017-12-17 03:55:39

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


alexknauth
2017-12-17 03:56:16

Tx-exprs are another one


alexknauth
2017-12-17 03:57:37

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


notjack
2017-12-17 04:01:10

there’s already a txexpr->list function though


notjack
2017-12-17 04:02:15

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