johnlcox / motif

Scala-like pattern matching for Java 8
http://john.leacox.com/motif
Apache License 2.0
148 stars 4 forks source link

Deep structural pattern matching #8

Open nobeh opened 9 years ago

nobeh commented 9 years ago

Let's consider the following naive example:

  static interface Map_<A, B> {
  }

  static class EmptyMap_<A, B> implements Map_<A, B> {
  }

  static class InsertAssoc_<A, B> implements Map_<A, B> {
    public final Pair<A, B> pair;
    public final Map_<A, B> map;

    public InsertAssoc_(Pair<A, B> pair, Map_<A, B> map) {
      this.pair = pair;
      this.map = map;
    }
  }

and now I'd like to define a method put with the following logic:

  public static <A, B> Map_<A, B> put_(final Map_<A, B> ms, final A k, final B v) {
    if (ms instanceof EmptyMap_ || ms == null) {
      return new InsertAssoc_(Pair(k, v), new EmptyMap_<>());
    }
    if (ms instanceof InsertAssoc_) {
      InsertAssoc_<A, B> is = (InsertAssoc_<A, B>) ms;
      if (k.equals(is.pair.getFirst())) {
        return new InsertAssoc_<>(Pair(k, v), is.map);
      } else {
        return new InsertAssoc_<>(Pair(k, v), put_(is.map, k, v));
      }
    }
    throw new IllegalArgumentException();
  }

Trying to do it with Motif as the following leads to compile errors:

  public static <A, B> Map_<A, B> put_motif(final Map_<A, B> ms, final A k, final B v) {
    match((Map_<A, B>) ms).when(caseThat(isA(EmptyMap_.class)))
        .get(x -> new InsertAssoc_<>(Pair(k, v), new EmptyMap_<>()))
        .when(caseThat(isA(InsertAssoc_.class)))
        .get((InsertAssoc_ x) -> k.equals(x.pair.getFirst()) ? new InsertAssoc_<>(Pair(k, v), x.map)
            : new InsertAssoc_<>(Pair(k, v), put_motif(x.map, k, v)));;
    return null;
  }

and the compilation error is basically type mismatch. In this example, I am trying to use Hamcrest matchers. Is there another way? What's the correct way to do this in Motif?

johnlcox commented 9 years ago

It looks like you are trying to do an instanceof match. There is a built in typeOf matcher in Motif that should work for this scenario. I built out some code quick to test it out, and it does work, but you'll need to cast the result. Make sure you called one of the getMatch or doMatch methods to actually perform the matching also.

  public static <A, B> Map_<A, B> put_motif(final Map_<A, B> ms, final A k, final B v) {
    InsertAssoc_<A, B> result = (InsertAssoc_) match(ms)
        .when(typeOf(EmptyMap_.class))
        .get(x -> new InsertAssoc_<>(Tuple2.of(k, v), new EmptyMap_<>()))
        .when(typeOf(InsertAssoc_.class))
        .get(x -> k.equals(x.pair.first()) 
                ? new InsertAssoc_<>(Tuple2.of(k, v), x.map) 
                : new InsertAssoc_<>(Tuple2.of(k, v), put_motif(x.map, k, v)))
        .getMatch();

    return result;
  }
nobeh commented 9 years ago

Thank you for your reply. It indeed works.

Let me continue with a more specific question. Let's focus on the part typeOf(InsertAssoc_.class). In a pure functional language with support of pattern matching, I would write the same check as the following as if can be done with Motif:

.when(eq(InsertAssoc_(Tuple2.of(kk, vv), innerMap)))
.get( x -> kk.equals(k)
             ? new InsertAssoc_<>(Tuple2.of(kk, v), innertMap)
             : new InsertAssoc_<>(Tuple2.of(kk, vv), put_motif(innerMap, k, v))

I tried to make Map_ an implementation of Case2<Tuple2<A, B>, Map_> and then tried:

    match(ms).when(typeOf(EmptyMap_.class))
        .get(x -> new InsertAssoc_<>(Tuple2.of(k, v), new EmptyMap_()))
        .when(case2(InsertAssoc_.class, typeOf(Tuple2.class), typeOf(Map_.class)))
        .get((Tuple2 p, Map_ m) -> p.first().equals(k)
            ? new InsertAssoc_<>(Pair(k, v), m)
            : new InsertAssoc_<>(Pair(p.first(), p.second()), put_motif(m, k, v)))
        ;

No luck as I get other compile errors.

To me this looks a deep extraction with parameters. But I do not know how to do it in Motif. Is this possible? Thanks.

johnlcox commented 9 years ago

Your Case2 code is really close. You can use the Tuple2 match pattern to extract the values directly.

Try this:

match(ms).when(typeOf(EmptyMap_.class))
        .get(x -> new InsertAssoc_<>(Tuple2.of(k, v), new EmptyMap_()))
        .when(case2(InsertAssoc_.class, tuple2(any(), any()), typeOf(Map_.class)))
        .get(
            (key, value, m) -> key.equals(k)
                ? new InsertAssoc_<>(Tuple2.of(k, v), m)
                : new InsertAssoc_<>(Tuple2.of(key, value), put_motif(m, k, v)))
    ;