Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Scala's Partial Function have a Java Equivalent?

Tags:

java

java-8

scala

Scala has partial functions which are functions that only apply to some values of the input type, but not all:

val isEven: PartialFunction[Int, String] = {
  case x if x % 2 == 0 => x+" is even"
} 

assert(isEven(10) equalsIgnoreCase "10 is even")
assert(isEven.isDefinedAt(11) == false)

And, even more useful, scala allows the "partialness" to be applied to the sub-type of a trait:

sealed trait BaseTrait

case class Foo(i : Int) extends BaseTrait
case class Bar(s : String) extends BaseTrait

val fooPartialFunc : PartialFunction[BaseTrait, Int] = {
  case f : Foo => 42 + f.i
}

assert(fooPartialFunc(Foo(8)) == 50)
assert(fooPartialFunc.isDefinedAt(Bar("hello")) == false)

What is the equivalent in java 8?

Most google results confuse "partial function" with currying, e.g. "partially applied function".

like image 301
Ramón J Romero y Vigil Avatar asked Oct 12 '18 20:10

Ramón J Romero y Vigil


3 Answers

A Java 8 Idiomatic, But Not Entirely Faithful, Approach

The most typical thing in Java 8 is going to be the Optional class:

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;

import java.util.Optional;

public class OptionalAsPartialFunction {

  Optional<String> isEven(final int x) {
    return Optional.of(x)
        .filter(i -> i % 2 == 0)
        .map(i -> i + " is even");
  }

  @Test
  public void example() {
    assertThat(isEven(10).get(), equalTo("10 is even"));
    assertThat(isEven(11).isPresent(), is(false));
  }
}

If you're familiar with Optional, then you'll see that the string concatenation i + " is even" is only evaluated if the filter condition, i % 2 == 0, is true. If you're not familiar with with Java's Optional, you can write this out with an if/else, as well:

Optional<String> isEven(final int x) {
  if (x % 2 == 0) {
    return Optional.of(x + " is even");
  } else {
    return Optional.empty();
  }
}

This should make is completely clear that the string concatenation is evaluated if and only if the guard condition evaluates to true.

A More Faithful, But Less Idiomatic (in Java) Approach

With the naive approach, you're calling the function, and getting an Optional which either contains the actual value of the function or it doesn't. But Scala's PartialFunction is a little bit more complex. From the documentation you linked to:

Even if isDefinedAt returns true for an a: A, calling apply(a) may still throw an exception, so the following code is legal:

val f: PartialFunction[Int, Any] = { case _ => 1/0 }

So we'd like to be able to check whether the function "is defined" for an input, even if trying to compute if for that input would actually be an error.

So a more faithful approach would be to use an Optional<Supplier<...>>. The outer Optional lets you know whether there's a computation to perform, and the inner Supplier lets you perform that computation (if you choose). So the example would become:

  Optional<Supplier<String>> isEven(final int x) {
    return Optional.of(x)
        .filter(i -> i % 2 == 0)
        .map(i -> () -> i + " is even");
  }

or, with an if/else:

Optional<Supplier<String>> isEven(final int x) {
  if (x % 2 == 0) {
    return Optional.of(() -> x + " is even");
  } else {
    return Optional.empty();
  }
}

and isPresent() still checks whether the function is defined, but get() will now return the Supplier whose get() method will actually compute the value:

  @Test
  public void example() {
    assertThat("isPresent() checks whether the function is defined for the input", //
        isEven(10).isPresent(), equalTo(true));
    assertThat("get() returns the supplier that actually computes the value", //
        isEven(10).get().get(), equalTo("10 is even"));
    assertThat("isPresent() checks whether the function is defined for the input", //
        isEven(11).isPresent(), is(false));
  }
like image 97
Joshua Taylor Avatar answered Oct 23 '22 16:10

Joshua Taylor


Java does not seem to provide PartialFunctions directly, but it provides a few interfaces that you can use to define your own PartialFunction<X,Y>. A PartialFunction<X, Y> is essentially just:

  • a Predicate<X> for testing whether a value of type X is in the domain
  • Function<X, Y> for the actual function definition that is invoked if the function is defined for the argument value.

Below, I've sketched how you could make use of Predicate and Function to implement PartialFunction. In this sketch, I defined only the three most important methods:

  • isDefinedAt is essentially just Predicates test.
  • apply invokes test and, if successful, invokes the applyIfDefined, which represents the "actual body of the function" (right hand side of cases in Scala)
  • orElse is a demonstration of a compositional structure different from ordinary function composition, just as a proof of concept

Here is the full Java code:

import java.util.function.*;

abstract class PartialFunction<X, Y> implements Predicate<X>, Function<X, Y> {
  public boolean isDefinedAt(X x) {
    return this.test(x);
  }
  public Y apply(X x) {
    if (isDefinedAt(x)) {
      return applyIfDefined(x);
    } else {
      throw new IllegalArgumentException("Match error on " + x);
    }
  }
  public abstract Y applyIfDefined(X x);

  public PartialFunction<X, Y> orElse(PartialFunction<X, Y> fallback) {
    PartialFunction<X, Y> outer = this;
    return new PartialFunction<X, Y>(){
      public boolean test(X x) {
        return outer.test(x) || fallback.test(x);
      }
      public Y applyIfDefined(X x) {
        if (outer.isDefinedAt(x)) {
          return outer.applyIfDefined(x);
        } else {
          return fallback.apply(x);
        }
      }
      @Override
      public Y apply(X x) {
        return applyIfDefined(x);
      }
    };
  }

  public static void main(String[] args) {
    PartialFunction<Integer, String> f = new PartialFunction<Integer, String>() {
      public boolean test(Integer i) {
        return i % 2 == 0;
      }
      public String applyIfDefined(Integer i) {
        return i + " is even";
      }
    };
    PartialFunction<Integer, String> g = new PartialFunction<Integer, String>() {
      public boolean test(Integer i) {
        return i % 2 == 1;
      }
      public String applyIfDefined(Integer i) {
        return i + " is odd";
      }
    };
    System.out.println(f.apply(42));
    try {
      System.out.println(f.apply(43));
    } catch (Exception e) {
      System.out.println(e);
    }
    PartialFunction<Integer, String> h = f.orElse(g);
    System.out.println(h.apply(100));
    System.out.println(h.apply(101));
  }
}

The output of the above example is:

42 is even
java.lang.IllegalArgumentException: Match error on 43
100 is even
101 is odd

If you wanted to use it with some kind of "case-classes", you could do this too, but for that, you would have to provide an implementation of the case classes first.

like image 34
Andrey Tyukin Avatar answered Oct 23 '22 15:10

Andrey Tyukin


Vavr brings to Java a lot of cool stuff from Scala.

If you ok with 3rd party libraries, it could be the best choice.

@Test
public void test() {
    PartialFunction<Integer, String> isEven =
            Function1.<Integer, String>of(integer -> integer + " is even")
                    .partial(integer -> integer % 2 == 0);

    Assert.assertEquals("10 is even", isEven.apply(10));
    Assert.assertFalse(isEven.isDefinedAt(11));
}

More complex example you can find here

like image 2
Alexander Pankin Avatar answered Oct 23 '22 16:10

Alexander Pankin