It's the data, stupid !

Brian Goetz brian.goetz at
Mon May 30 18:40:33 UTC 2022

> The problem is that what you propose is a leaky abstraction, because 
> pattern matching works on classes and not on types, so it's not a 
> reverse link.

("Leaky abstraction" is sort of an inflammatory term.)

What I think you're getting at is that some objects will have state that 
you can "put in", but can't "take out".  The mathematical relationship 
here is "embedding projection pair" (this is similar to an adjoint 
functor pair in some ways.)

A good example of this relationship is int and Integer.  Every int 
corresponds to an Integer, and *almost* every Integer (except null) 
corresponds to an int.  Imagine there are two functions e : int -> 
Integer and p : Integer -> int, where p(null) = bottom. Composing 
e-then-p is an identity; composing p-then-e can lose some information, 
but we can characterize the information loss.  Records form this same 
relationship with their cartesian product space (assuming you follow the 
refined contract outlined in Record::equals).  When you have this 
relationship, you get some very nice properties, such as "withers" and 
serialization basically for free.  The relationship between a ctor and 
the corresponding dtor also has this structure.  So yes, going backwards 
is "lossy", but in a controlled way.  This turns out to be good enough 
for a lot of things.

> Let say we have a class with two shapes/deconstruction
> class A {
>   deconstructor (B) { ... }
>   deconstructor (C) { ... }
> }
> With the pattern A(D d), D is a runtime class not a type, you have no 
> idea if it means
>   instanceof A a && B b = a.deconstructor() && b instanceof D
> or
>   instanceof A a && C c = a.deconstructor() && c instanceof D

You can have types in the dtor bindings, just as you can have types in 
the constructor arguments.  Both may use the class type variables, as 
they are instance "members".

> Unlike with a method call (constructor call) where the type of the 
> arguments are available, with the pattern matching, you do not have 
> the types of the arguments, only runtime classes to match.

This is where the rule of "downcast compatible" comes in.  We see this 
show up in GADT-like examples (the rules of which are next on our 
parade.)  For example, if we have:

     sealed class Node<T> { }
     record IntNode(int x) implements Node<int> { }

then when we switch on a Node<T>:

     switch (aNode) {
         case IntNode n: ...

we may conclude, in the consequent of the appropriate case, that T=int.  
(Read the Kennedy and Russo paper for details.)

Similarly, if we have:

     List<String> list = ...

then when matching, we may conclude that if its an ArrayList, its an 

     switch (list) {
         case ArrayList<String> a: ...

but could not say `case ArrayList<Integer>`, because that is 
inconsistent with the target type.

So, while we can't necessarily distinguish between Foo<String> and 
Foo<Integer> because of erasure, that doesn't mean we can't use types; 
its just that we can't conclude things that the generic type system 
won't let us.

> As i said, it's a question where OOP and DOD (data oriented design ?) 
> disagree one with the other.

I don't think they disagree at all.  They are both useful tools for 
modeling things; one is good for modeling entities and processes, the 
other for modeling data, using a common vocabulary.  Our systems may 
have both!

> And this is a problem specific to the deconstructor, for named pattern 
> method, there is no such problem, obviously a user can add as many 
> pattern methods he/she want.

Because there's no name, we are limited to overloads that are distinct 
up to erasure; constructors have the same restriction.

>     But for each way of putting together the data, there should be a
>     corresponding way to take it apart. 
> if the pattern matching was a real inverse link, yes, maybe.

I think the word "real" is doing too much lifting in that sentence.

> The problem is that varargs pattern can also recognizes a record with 
> no record or class with a deconstructor with no varargs.

As can a constructor.

> You think term of inverse function, we have varargs constructors so we 
> should have varargs pattern, but a pattern is not an inverse function.

You are interpreting "inverse" too strictly -- and then attempting to 
use that to prematurely bury the concept.

> We have the freedom to provide a simpler model.

I think the reality is that you want this to be a smaller, less 
ambitious feature than is being planned here.  That's a totally valid 
opinion!  But I think its just a difference of opinion on how much to 
invest vs how much we get out.

But by all means, try to outline (in a single mail, please) your vision 
for a simpler model.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the amber-spec-experts mailing list