Scala 有部分函数,这些函数仅适用于输入类型的某些值,但不是全部:
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)
而且,更有用的是,scala 允许将“偏爱”应用于
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)
java 8 中的等价物是什么?
大多数谷歌结果将“部分函数”与柯里化混淆,例如“部分应用函数”。
Java 8 中最典型的就是可选类:
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));
}
}
如果您熟悉Optional,那么您会发现,仅当过滤条件
i + " is even"
为真时,才会计算字符串连接 i % 2 == 0
。如果你不熟悉Java的Optional,你也可以用if/else
写出来:
Optional<String> isEven(final int x) {
if (x % 2 == 0) {
return Optional.of(x + " is even");
} else {
return Optional.empty();
}
}
这应该完全清楚,当且仅当保护条件评估为 true 时,才会评估字符串连接。
使用简单的方法,您调用该函数,并获取一个Optional,它要么包含函数的实际值,要么不包含。但 Scala 的 PartialFunction 稍微复杂一些。从您链接到的文档中:
即使 isDefinedAt 对于 a: A 返回 true,调用 apply(a) 仍然可能抛出异常,因此以下代码是合法的:
val f: PartialFunction[Int, Any] = { case _ => 1/0 }
因此,我们希望能够检查函数是否为输入“定义”,即使尝试计算该输入是否实际上会出现错误。
因此,更可靠的方法是使用
Optional<Supplier<...>>
。外部 Optional
让您知道是否需要执行计算,内部 Supplier
让您执行该计算(如果您选择)。所以这个例子将变成:
Optional<Supplier<String>> isEven(final int x) {
return Optional.of(x)
.filter(i -> i % 2 == 0)
.map(i -> () -> i + " is even");
}
或者,带有
if/else
:
Optional<Supplier<String>> isEven(final int x) {
if (x % 2 == 0) {
return Optional.of(() -> x + " is even");
} else {
return Optional.empty();
}
}
和
isPresent()
仍然检查函数是否已定义,但get()
现在将返回Supplier
,其get()
方法将实际计算值:
@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));
}
Java 似乎没有直接提供
PartialFunction
,但它提供了一些接口,您可以使用它们来定义自己的 PartialFunction<X,Y>
。 PartialFunction<X, Y>
本质上就是:
Predicate<X>
用于测试X
类型的值是否在域中Function<X, Y>
用于调用的实际函数定义 if 该函数是为参数值定义的。下面,我概述了如何利用
Predicate
和 Function
来实现 PartialFunction
。在此草图中,我仅定义了三个最重要的方法:
isDefinedAt
本质上只是Predicate
s test
。apply
调用 test
,如果成功,则调用 applyIfDefined
,它代表“函数的实际主体”(Scala 中 case
的右侧)orElse
是后备行为的组合结构的演示(与普通的函数组合有很大不同)这是完整的Java代码:
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));
}
}
上面例子的输出是:
42 is even
java.lang.IllegalArgumentException: Match error on 43
100 is even
101 is odd
如果您想将它与某种“案例类”一起使用,您也可以这样做,但为此,您必须首先提供案例类的实现。
Vavr 为 Java 带来了很多来自 Scala 的很酷的东西。
如果您同意第 3 方库,它可能是最好的选择。
@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));
}