sviperll / adt4j

adt4j - Algebraic Data Types for Java
BSD 3-Clause "New" or "Revised" License
143 stars 8 forks source link

Support for constructors belonging to multiple types? #27

Closed io7m closed 8 years ago

io7m commented 8 years ago

Hello.

I appreciate that the title of the ticket is somewhat confusing! I've stumbled onto this project again today and noticed that it's using pretty much the exact same encoding of algebraic data types that I've been writing manually for years. However, I frequently use an encoding that allows a "constructor" to belong to more than one type.

I have an article that documents the encoding I've been using, and I have a section that explains the "multiple types" aspect above:

http://io7m.com/documents/java/tt3-genvis/

Briefly, it avoids the "wrapper" types that you'd write in common garden Haskell:

data Apple = Apple Color Integer Integer
data Cookie = Cookie Integer Boolean
data Biltong = Biltong Integer

data Healthy =
    HealthyApple Apple
  | HealthyBiltong Biltong

data Sweet =
    SweetApple Apple
  | SweetCookie Cookie

data NotFruit =
    NotFruitCookie Cookie
  | NotFruitBiltong Biltong

... and replaces them with the ability for the constructors of each type to effectively exist within multiple types, to effectively allow for matching on "aspects" of each:

interface HealthyType
{
  <A> A matchHealthy(HealthyMatcherType<A> m);
}

interface HealthyMatcherType<A>
{
  A matchHealthyApple(Apple a);

  A matchHealthyBiltong(Biltong b);
}

interface SweetType
{
  <A> A matchSweet(SweetMatcherType<A> m);
}

interface SweetMatcherType<A>
{
  A matchSweetApple(Apple a);

  A matchSweetCookie(Cookie c);
}

interface NotFruitType
{
  <A> A matchNotFruit(NotFruitMatcherType<A> m);
}

interface NotFruitMatcherType<A>
{
  A matchNotFruitCookie(Cookie c);

  A matchNotFruitBiltong(Biltong b);
}

final class Apple implements SnackMatchableType, HealthyType, SweetType
{
  @Override <A> A matchSnack(SnackMatcherType<A> m)
  {
    return m.matchApple(this);
  }

  @Override <A> A matchHealthy(HealthyMatcherType<A> m)
  {
    return m.matchHealthyApple(this);
  }

  @Override <A> A matchSweet(SweetMatcherType<A> m)
  {
    return m.matchSweetApple(this);
  }
}

final class Cookie implements SnackMatchableType, NotFruitType, SweetType
{
  @Override <A> A matchSnack(SnackMatcherType<A> m)
  {
    return m.matchCookie(this);
  }

  @Override <A> A matchSweet(SweetMatcherType<A> m)
  {
    return m.matchSweetCookie(this);
  }

  @Override <A> A matchNotFruit(NotFruitMatcherType<A> m)
  {
    return m.matchNotFruitCookie(this);
  }
}

final class Biltong implements SnackMatchableType, HealthyType, NotFruitType
{
  @Override <A> A matchSnack(SnackMatcherType<A> m)
  {
    return m.matchBiltong(this);
  }

  @Override <A> A matchHealthy(HealthyMatcherType<A> m)
  {
    return m.matchHealthyBiltong(this);
  }

  @Override <A> A matchNotFruit(NotFruitMatcherType<A> m)
  {
    return m.matchNotFruitBiltong(this);
  }
}

I will almost certainly start using adt4j in my projects, but the above is something that I would certainly miss (not least because wrapper types aren't exactly free on the JVM as they often can be in Haskell and OCaml).

jbgi commented 8 years ago

@io7m not sure what you would want adt4j to generate? looks like your use case would be easily solve by using something like https://github.com/google/auto and implementing the match methods manually in the Apple/Cookie/Biltong classes.

io7m commented 8 years ago

Hello!

I was going to elaborate, but I think the way adt4j does things is pretty fundamentally incompatible with what it'd need to be doing to support the multiple-types approach. That's not intended as a criticism, it seems well suited for encoding ML-ish types directly.

I'll take a look at AutoValue, thanks!

jbgi commented 8 years ago

@io7m you may also be interested in https://github.com/derive4j/derive4j which is similar to adt4j, with a different feature set (that may or may not facilitate want do want to do).