Integrating Partial Functions into Javaslang
We are planning to introduce partial functions in a future release of Javaslang. This new feature is mainly driven by one use-case, fusing the map() method with the concept of pattern matching, called collect().
Pattern matching is like using a switch statement as expression, but more powerful. We are able to deconstruct objects.
Option<T> option = ...;
T result = Match(option).of(
Case(Some($()), value -> value),
Case(None(), () -> ...)
);
Given a List<Option<T>> we might want to collect the values of type T. Currently we achieve this by first filtering and then mapping:
List<T> values = list.filter(Option::isDefined)
.map(Option::get);
By introducing a new method collect(PartialFunction) and making Case a PartialFunction we are able to select the desired list elements and pull the values out of them at the same time:
List<T> values = list.collect(
Case(Some($()), t -> t)
)
The above is only a simple example. Pattern matching might help us deconstructing complex structures by inherently moving filter/map logic into the match cases.
Update: Non-trivial example
Emmanuel Touzery objected that the above example could be minified using flatMap().
List<T> values = list.flatMap(t -> t);
Therefore I want to give a slightly more complex example. Given a List<Person> we want to collect the customer ids of all customers that are called 'Peter' and live in 'Kiel'.
These are the underlying types:
interface Person {
String getName();
Address getAddress();
}
interface Customer extends Person {
String getCustomerId();
}
interface Address {
String getStreet();
String getCity();
}
Given a list of persons
List<Person> persons = ...;
we gather the customer ids by using the upcoming Javaslang collect() method (it is assumed that the necessary Customer and Address patterns are already generated using the javaslang-match module).
List<String> customerIds = persons.collect(
Case($(Customer($(), "Peter", Address($(), "Kiel"))),
(customerId, name, address) -> customerId));
The equivalent solution using plain Java:
List<String> customerIds = persons
.filter(person -> {
if (person instanceof Customer) {
Customer customer = (Customer) person;
return "Peter".equals(customer.getName())
&& customer.getAddress() != null
&& "Kiel".equals(customer.getAddress().getCity());
} else {
return false;
}
})
.map(person -> ((Customer) person).getCustomerId());
Please note that the above type cast (Customer) person
used in the map() method of the plain Java solution is unsafe. The collect() solutions uses pattern matching which decomposes the parts in a type-safe way. Also, please bear in mind that pattern matching is null-safe, e.g. no extra checks have to be performed when matching objects. However, the extracted objects are allowed to be null.
Design
We already have a type Function1 that extends java.util.function.Function. The question is how to cleanly integrate the concept of a partial function into the existing Javaslang 2.x.
There are basically five different ways:
In the following we will discuss the advantages and disadvantages of the different approaches.
Variant 1

We add a new type PartialFunction and treat Function1 as total function, i.e. a function that is defined for all inputs.
public interface PartialFunction<T, R> extends java.util.function.Function<T, R> {
boolean isDefinedAt(T value);
}
@FunctionalInterface
public interface Function1<T1, R> extends PartialFunction<T1, R> {
@Override
default boolean isDefinedAt(T1 t1) {
return true;
}
}
From the API perspective this makes sense because it enables us to use method references and lambdas as Function1 drop-in where a PartialFunction is expected.
// = List("1 is odd", "3 is odd")
List(1, 2, 3).collect(
// partial function
Case($(i -> i % 2 == 1), i -> i + " is odd")
);
// = List("1", "2", "3")
List(1, 2, 3).collect(
// total function
Function1.of(String::valueOf)
);
We could also add some syntactic sugar to create partial functions:
@FunctionalInterface
public interface Function1<T1, R> extends PartialFunction<T1, R> {
default Function1<T1, R> partial(Predicate<? super T1> isDefined) {
final Function1<T1, R> self = this;
return new Function1<T1, R>() {
@Override
public boolean isDefinedAt(T1 t1) {
return isDefined.test(t1);
}
@Override
public R apply(T1 t1) {
return self.apply(t1);
}
};
}
}
Example:
We create a function that returns the preferred programming language of a person and make it partial in the way that it is defined only for persons who are developers.
Function1<Person, Lang> getLang =
Function1.of(Person::preferredLang)
.partial(Person::isDeveloper);
But there is one drawback. PartialFunction extends java.util.function.Function. Given a list of persons we may use such a partial function in places where total functions are required.
List<Persons> persons = ...;
List<Lang> langs = persons.map(getLang); // 💣
The map() operation above will throw for persons who live a life without software development.
Of course there is another caveat: backward compatibility. Users of Javaslang may rely on the fact that java.util.function.Function is the super-type of Function1. But that is a different story...
Variant 2

In variant 1 Function1 represents both, total and partial functions. So (from our current viewpoint) there is not really the need to have an exclusive type PartialFunction.
We could just leave it away and add the new method isDefinedAt() to Function1. However, the problem remains that partial functions can be used in places where total functions are required.
Function1<Person, Lang> getLang = ...;
persons.map(getLang); // 💣
Variant 3

The practical object-oriented software-craftsman might think that a PartialFunction is a specialization of Function1 because it adds a new method to an existing type hierarchy. We did the same with Function1 which extends java.util.function.Function, right? #NoPunIntended
Same problem here: We might use partial functions in places where total functions are required.
PartialFunction<Person, Lang> getLang = ...;
persons.map(getLang); // 💣
Variant 4

This variant does not change anything. As long as Function1 and PartialFunction extend java.util.function.Function, partial functions can be used in places where total functions are required*).
*) Please note that we designed Javaslang for reasons of interoperability in the way that methods accept the most general types possible. Methods like map() that take a function generally accept a java.util.function.Function instead of a Function1.
This leads to the last variant.
Variant 5

In order to ensure that partial functions cannot be used in places where total functions are required, we need to separate PartialFunction from java.util.function.Function.
This means:
public interface PartialFunction<T, R> {
R apply(T t);
boolean isDefinedAt(T value);
}
Our existing Function1 may still provide a way to derive partial functions. We need only to change the return type.
@FunctionalInterface
public interface Function1<T1, R> extends java.util.function.Function<T1, R> {
default PartialFunction<T1, R> partial(Predicate<? super T1> isDefined) {
final Function1<T1, R> self = this;
return new PartialFunction<T1, R>() {
@Override
public boolean isDefinedAt(T1 t1) {
return isDefined.test(t1);
}
@Override
public R apply(T1 t1) {
return self.apply(t1);
}
};
}
}
Now it is ensured on the type level that partial functions and total functions are different concepts.
PartialFunction<Person, Lang> getLang =
Function1.of(Person::preferredLang)
.partial(person::isDeveloper);
List<Lang> devLangs = persons.collect(getLang); // 👍🏼 WORKS
List<Lang> personLangs = persons.map(getLang); // 👍🏼 COMPILE ERROR
Less dependencies, less problems.
Conclusion
Update: Nándor Előd Fekete and Mark Elston remarked in our chat that the violations mentioned in variant 1-4 can be subsumed to a violation of the Liskov Substitution Principle (LSP).
The LSP encompasses behavioral compatibility along a type hierarchy. In variant 1-4, both Function1 and PartialFunction extend j.u.f.Function. The List.map() function accepts any j.u.f.Function object. Such a function is expected to be defined for any input.
However, in variant 1-4 the subtype PartialFunction has a different semantic. A given function may be undefined for certain inputs. That is a clear violation of the LSP.
So variant 5 is the only way to integrate the concept of partial functions into Javaslang - as long as java.util.function.Function is on top of the type hierarchy.
Keep your code clean,
- Daniel