Live data from Hacker News

Pattern Matching for Java

cr.openjdk.java.net

141–150 of 152 posts

Re: Pattern Matching for Java

#141
post #139

Earlier quoted context omitted.

The point is that if you forget to write one of the ReactSpecialization methods nothing detects it. Perhaps more importantly, if you add a new type of Animal there's nothing to tell you you need to write a bunch of new ReactSpecializations.

Aha! There's the crux of an argument. It's a valid point actually, that ties into the expression problem. As an OO apologist I do feel compelled to point out that type Animal = Cat | Dog | Mouse let react = function (a, b) | Cat, Dog -> ... ... | x, y -> $"{x} is not interested in {y}." Would suffer from the exact same issue though.

Only if you deliberately add the catch-all case. If you don't and just handle all the cases explicitly, many languages will give you a warning or error when you add a new unhandled variant.

Re: Pattern Matching for Java

#142
post #141

Earlier quoted context omitted.

Aha! There's the crux of an argument. It's a valid point actually, that ties into the expression problem. As an OO apologist I do feel compelled to point out that type Animal = Cat | Dog | Mouse let react = function (a, b) | Cat, Dog -> ... ... | x, y -> $"{x} is not interested in {y}." Would suffer from the exact same issue though.

Only if you deliberately add the catch-all case. If you don't and just handle all the cases explicitly, many languages will give you a warning or error when you add a new unhandled variant.

Yeah, it's a valid point.

Re: Pattern Matching for Java

#143

Earlier quoted context omitted.

To me it seems pattern matching without possibility to actually enforce that all cases are matched seems to miss the most useful scenario: What I want to do is create proper sum types. For some reason most OO languages were designed around the idea that being "open for extension" was a good thing. Normally if I make a base class, I want a closed and known set of sub classes. E.g. if I make a payment method then I wan…

In the C# example in the code I posted, all cases are matched. If you add a new animal subclass, that's not "explicitly" handled, it will route to the default (Animal, Animal) method. It's equivalent of _ -> default_function At the end of a pattern matching statement. Essentially it's impossible for not all the cases to be matched, as far as I can see.

Yes but making new classes map to default is what a default clause already does (which is basically what a base case in an abstract base class does with normal inheritance).

What I want is to make a new type added mean the code won't compile until it is explicitly handled in all pattern matches that do not have a default clause. Basically what I'm saying is that this:

   bool HandlePayment(PaymentMethod pm) {
      switch(pm) {
         case Invoice(addr):
              SendInvoice(addr); 
              return true;
         case CreditCard(ccdetails)
              return PrcessCreditcard(ccdetails);
         default:
              // What?
      }
   }
Would be a lot better if it didn't require the default, and the compiler could detect the error that occurs when someone adds a new case (BitCoin) to the types of payment method. Inheritance and abstract baseclass for the processing means that you'd do this

   bool HandlePayment(PaymentMethod pm)
   {
      return pm.Process(order);  // sends an invoice, charges credit card etc
   }

   and you simply have 

   abstract class PaymentMethod { abstract bool Process(order); }
   class CreditCard : PaymentMethod {  ... }
   class Invoice : PaymentMethod {  ... }
And this is of course the regular way of writing OO, but I find it unnatural and cumbersome in many cases. It's hard to define concise descriptions of what types are actually available, and you have to write a ton of boilerplate to ensure a closed hierarchy and complete matching in all scenarios where you can't enclose the logic inside each type.

Re: Pattern Matching for Java

#144
post #107

You can do this in Java using derive4j: https://github.com/derive4j/derive4j . It generates code for algebraic data types which offer a typesafe interface similar to the `case` statements in languages that natively support pattern matching.

In the same way you could "do" lambdas with single method interfaces in Java pre-8. It was possible, but it was still a pain to do.

It depends. Bare minimum, in scala you have to do:

  sealed trait Either[A, B]
  final case class Left[A, B](a: A) extends Either[A, B]
  final case class Right[A, B](b: B) extends Either[A, B]
and it is still not a real sum type due to exposing subtyping (cf. https://github.com/quasar-analytics/quasar/wiki/Faking-Sum-T...)

with derive4j:

  @Data
  interface Either {
     X match(Function left, Function right);
  }
or, at your preference:

  @Data
  interface Either {
    interface Cases {
      X left(A leftValue);
      X right(B rightValue);
    }
     X match(Cases cases);
  }
So it is actually not much boilerplate.

The real drawback of (any) java implementation is lack of TCO.

Re: Pattern Matching for Java

#145
post #108

Earlier quoted context omitted.

To me it seems pattern matching without possibility to actually enforce that all cases are matched seems to miss the most useful scenario: What I want to do is create proper sum types. For some reason most OO languages were designed around the idea that being "open for extension" was a good thing. Normally if I make a base class, I want a closed and known set of sub classes. E.g. if I make a payment method then I wan…

Scala gets this right, in my opinion, with sealed traits and case classes. "enum classes" feel like a much more limited option by comparison. Although I'd take either over the status quo to be sure.

What's the difference between the enum class I outlined and scalas case classes? I don't know Scala but reading the docs on its case classes, their description seem to fit exactly what I tried to describe (immutable types, closed for further inheritance etc).

The example they give for notification = email | sms | voice looks a lot like what I was trying to sketch with the paymentMethod example.

http://docs.scala-lang.org/tutorials/tour/case-classes.html

I didn't intend for my proposal to be more limited than than the Scala case classes at least :). I want exactly that. An FP closed type hierarchy with enforced pattern matching.

Re: Pattern Matching for Java

#146

Earlier quoted context omitted.

I wonder what the programming world would look like now if functional programming concepts had broken through in the 80s and 90s, instead of OO. C would no doubt still be C, filling the same minimalistic imperative close-to-iron niche, but what about higher-level languages?

The move towards Interactive Applications has helped OO and scripting languages. There are still plenty of applications for functional programming in the most demanding of environments, for safety and high availability. However, most students are not good enough at math to learn functional programming concepts. Whether that's the teachers' fault or the students' fault or society's fault, I can't say. But if we paid p…

Half the world runs on Excel, a functional programming environment. If your average manager is mathy enough to understand functional programming, the average programmer should be as well.

Re: Pattern Matching for Java

#147

Earlier quoted context omitted.

Yep, there is no way to check the exhaustiveness in the compile time. Say you have an Expression parent class and a list of child classes: say Add, Const, Var, and a corresponding visitor. Add new child class, and the visitor would be non-exhaustive. Multiple dispatch is not very different in that sense. I think that Alan Key spoke about dynamic nature of OOP, that's one of the examples. Even in the statically typed…

I'm lost here. Either people didn't read the article and are just relying on preconceived notions about what stuff can and can't happen with multiple dispatch, or I am missing something blindingly obvious. Anyway, consider this example: class Expression {} class Add : Expression {} class Const : Expression {} class Var : Expression {} void FSpecialization(Expression e1, Expression e2) { ... } void FSpecialization(Con…

>How is this not exhausted?

If you would remove FSpecialization(Expression e1, Expression e2) a.k.a wildcard pattern, compiler would not warn you about your dispatching is not exhaustive. And if you'd add another type of expression, all your dispatchers and visitors without wildcard would become non exhaustive, staying valid code from a compiler perspective at the same time.

Re: Pattern Matching for Java

#148

Earlier quoted context omitted.

Yep, there is no way to check the exhaustiveness in the compile time. Say you have an Expression parent class and a list of child classes: say Add, Const, Var, and a corresponding visitor. Add new child class, and the visitor would be non-exhaustive. Multiple dispatch is not very different in that sense. I think that Alan Key spoke about dynamic nature of OOP, that's one of the examples. Even in the statically typed…

I'm lost here. Either people didn't read the article and are just relying on preconceived notions about what stuff can and can't happen with multiple dispatch, or I am missing something blindingly obvious. Anyway, consider this example: class Expression {} class Add : Expression {} class Const : Expression {} class Var : Expression {} void FSpecialization(Expression e1, Expression e2) { ... } void FSpecialization(Con…

It's not exhaustive, because you're only dealing with the Const+Var case. You ignore the other 5 cases. Having a default case doesn't suddenly make it exhaustive, it's just an explicit acknowledgement that you can't.

Because what happens if you introduce a new expression, but not realize this means the implementation of F should be updated?

Regarding algebraic pattern matching, you can let Haskell check exhaustiveness and then drop the default case. The compiler will then warn you if you forgot one.

To give you an example, in our codebase we have an enum that is used 3068 times in one solution. A very similar one (you would need domain knowledge to understand the difference) is used 2985 times. Both are not defined in that solution by the way.

It's not out of the question they will receive a new enum member down the line. It would be very useful if the compiler was able to tell you a switch that uses either enum is no longer exhaustive.

If you could exhaustively switch on an enum, you could skip the default: case and have the compiler enforce you covered all cases. (I know, the C# enum==int would make that impossible.)

Re: Pattern Matching for Java

#149

Earlier quoted context omitted.

Yep, there is no way to check the exhaustiveness in the compile time. Say you have an Expression parent class and a list of child classes: say Add, Const, Var, and a corresponding visitor. Add new child class, and the visitor would be non-exhaustive. Multiple dispatch is not very different in that sense. I think that Alan Key spoke about dynamic nature of OOP, that's one of the examples. Even in the statically typed…

I'm sorry would you mind going into a bit more depth about that example? It's not obvious to me why the compiler can't figure out the the visitor is no longer exhaustive.

Visitor is just a collection of functions for all type's subtypes. The problem is that visitor knows nothing about class' subtypes, just like the base class knows nothing about it's children, so you have to add visit cases manually for each child class. The only way to detect that visitor is non exhaustive is to apply it to the child class it has no visit function for.

Re: Pattern Matching for Java

#150
post #108

Earlier quoted context omitted.

Scala gets this right, in my opinion, with sealed traits and case classes. "enum classes" feel like a much more limited option by comparison. Although I'd take either over the status quo to be sure.

What's the difference between the enum class I outlined and scalas case classes? I don't know Scala but reading the docs on its case classes, their description seem to fit exactly what I tried to describe (immutable types, closed for further inheritance etc). The example they give for notification = email | sms | voice looks a lot like what I was trying to sketch with the paymentMethod example. http://docs.scala-lang…

Case classes can have methods on them, for one - I can see how that might be added to yours, but it'd probably be very awkward syntactically if you wanted different methods on different cases. To me, it also feels like your enum class is more of a special case. Scala's case classes and sealed traits work together as independent features that come together to provide value. I've used case classes before without using the 'enum-like' property of sealed traits, where I just want to get equality, unapplying, etc... for free.

They also have more configurability - while a case class provides default implementations for things like equality, you can override those (this could be a pro or a con depending on whether you prefer flexibiltiy or performance, I guess - although arguably the flexibility should only have cost if you use it, assuming it's implemented well).

It's definitely not a huge difference, and arguably Scala can suffer from it's philosophy of providing tools that can do something rather than tools for something that means the language often comes across as huge and as having too much stuff in it. I like it, but it's not the C# way right now for sure.

Post reply on HN