2015-03-13 4 views
3

Я пытаюсь реализовать церковные цифры в Java 1.8. Моя первая попытка:Как внедрить церковные цифры с использованием Java 1.8

import java.util.function.UnaryOperator; 

@FunctionalInterface 
public interface ChurchNumeral { 
    public static ChurchNumeral valueOf(int n) { 
    if (n < 0) { 
     throw new IllegalArgumentException("Argument n must be non-negative."); 
    } 
    if (n == 0) { 
     return (f, arg) -> arg; 
    } 
    return (f, arg) -> f(valueOf(n-1).apply(f, arg)); 
    } 

    <T> T apply(UnaryOperator<T> f, T arg); 
} 

Это не работает, потому что функциональный метод имеет параметр типа. (В частности, строки с лямбда-выражениями дают ошибку: «Нелегированное выражение лямбда: метод применяется к типу ChurchNumeral является общим».)

На основании ответов на связанные вопросы об использовании дженериков с функциональными интерфейсами я попытался параметризовать класс:

import java.util.function.UnaryOperator; 

@FunctionalInterface 
public interface ChurchNumeral<T> {    // This line changed. 
    public static ChurchNumeral<?> valueOf(int n) { // This line changed. 
    if (n < 0) { 
     throw new IllegalArgumentException("Argument n must be non-negative."); 
    } 
    if (n == 0) { 
     return (f, arg) -> arg; 
    } 
    return (f, arg) -> f(valueOf(n-1).apply(f, arg)); 
    } 

    T apply(UnaryOperator<T> f, T arg);    // This line changed. 
} 

Первая лямбда-выражение в настоящее время составляет, но второй один терпит неудачу с этой ошибкой:

The method apply(UnaryOperator, capture#1-of ?) in the type ChurchNumeral is not applicable for the arguments (UnaryOperator, Object)

Кроме того, я не хочу иметь различные версии ChurchNumeral.ZERO для всех возможного FUNC/аргумент.

Любые предложения?

ответ

2

Is there a way to do it so I don't need to create a ChurchNumeral for every possible type? I'd like to be able to apply ZERO (for example) to any UnaryOperator and argument of type T

Я предполагаю, что вы имеете в виду вы хотите сделать что-то вроде этого:

ChurchNumeral five = ChurchNumeral.valueOf(5); 
five.apply(s -> s + s, "s"); 
five.apply(Math::sqrt, Double.MAX_VALUE); 

, что означает, что метод подписи в первом примере:

<T> T apply(UnaryOperator<T> f, T arg); 

является тот, который необходимо.

Однако you can't use a lambda expression for a functional interface, if the method in the functional interface has type parameters.

Обходной является создание подинтерфейса, который совместит с лямбдой и делегировать вызовы apply к его методе, как показано ниже.

public static void main(String[]a){ 
    ChurchNumeral five = ChurchNumeral.valueOf(5); 
    System.out.println(five.apply(s -> s + s, "s")); 
    System.out.println(five.apply(Math::sqrt, Double.MAX_VALUE)); 
} 
@FunctionalInterface 
private interface ChurchNumeralT<T> extends ChurchNumeral { 
    @SuppressWarnings({ "rawtypes", "unchecked" }) 
    @Override 
    default<U> U apply(UnaryOperator<U> f, U arg){ 
     return (U)((ChurchNumeralT)this).tapply(f, arg); 
    } 
    T tapply(UnaryOperator<T> f, T arg); 
} 
public interface ChurchNumeral { 

    <T> T apply(UnaryOperator<T> f, T arg); 

    static ChurchNumeral valueOf(int n) { 
     if (n < 0) { 
      throw new IllegalArgumentException("Argument n must be non-negative."); 
     } 
     if (n == 0) { 
      return (ChurchNumeralT<?>)(f, arg) -> arg; 
     } 
     return (ChurchNumeralT<?>)(f, arg) -> f.apply(valueOf(n - 1).apply(f, arg)); 
    } 
} 
+0

Спасибо. FYI, я смог удалить «rawtypes» из @SuppressWarnings, заменив бросок на ChurchNumeralT на приведение к ChurchNumeralT . –

3
static interface Church<T> extends UnaryOperator<UnaryOperator<T>> { 

    static <T> Church<T> of(int n) { 
     if (n < 0) { 
      throw new IllegalArgumentException(); 
     } else if (n == 0) { 
      return f -> (t -> t); 
     } else { 
      return sum(f -> f, Church.of(n-1)); 
     } 
    } 

    static <T> Church<T> sum(Church<T> a, Church<T> b) { 
     return f -> b.apply(f).andThen(a.apply(f))::apply; 
    } 
} 

public static void main(String[] args) { 
    Church<Integer> five = Church.of(5); 
    Church<Integer> three = Church.of(3); 
    Church<Integer> eight = Church.sum(five, three); 

    assert 3 == three.apply(x -> x + 1).apply(0); 
    assert 5 == five.apply(x -> x + 1).apply(0); 
    assert 8 == eight.apply(x -> x + 1).apply(0); 
} 

Edit: Если вы хотите apply(UnaryOperator<T> f, T arg) прямо в интерфейсе Церкви, так что вы могли бы назвать .apply(x->x+1,0) вместо .apply(x->x+1).apply(0), вы можете добавить метод по умолчанию, как это вышеприведенном интерфейс Церкви:

default T apply(UnaryOperator<T> f, T t) { 
    return this.apply(f).apply(t); 
} 

Edit 2: Вот обновленный класс, который может конвертировать между различными типами. Я также добавил метод mul, чтобы умножить только на то, как это работает:

static interface Church<T> extends UnaryOperator<UnaryOperator<T>> { 

    static <T> Church<T> of(int n) { 
     if (n < 0) { 
      throw new IllegalArgumentException(); 
     } else if (n == 0) { 
      return zero(); 
     } else { 
      return sum(one(), Church.of(n - 1)); 
     } 
    } 

    static <T> Church<T> zero() { 
     return f -> (t -> t); 
    } 

    static <T> Church<T> one() { 
     return f -> f; 
    } 

    static <T> Church<T> sum(Church<T> a, Church<T> b) { 
     return f -> b.apply(f).andThen(a.apply(f))::apply; 
    } 

    static <T> Church<T> mul(Church<T> a, Church<T> b) { 
     return f -> a.apply(b.apply(f))::apply; 
    } 

    default <U> Church<U> convert() { 
     return (Church<U>) this; 
    } 
} 

public static void main(String[] args) { 
    Church<Integer> zero = Church.zero(); 
    Church<Integer> five = Church.of(5); 
    Church<Integer> three = Church.of(3); 
    Church<Integer> eight = Church.sum(five, three); 
    Church<Integer> fifteen = Church.mul(three, five); 

    assert 0 == zero.apply(x -> x + 1).apply(0); 
    assert 3 == three.apply(x -> x + 1).apply(0); 
    assert 5 == five.apply(x -> x + 1).apply(0); 
    assert 8 == eight.apply(x -> x + 1).apply(0); 
    assert 15 == fifteen.apply(x -> x + 1).apply(0); 

    Church<String> strOne = Church.one();  
    Church<String> strThree = three.convert(); // make Church<String> 
               // from a Church<Integer> 

    assert "foo:bar".equals(strOne.apply("foo:"::concat).apply("bar")); 
    assert "foo:foo:foo:bar".equals(strThree.apply("foo:"::concat).apply("bar")); 

} 
+0

Есть ли способ сделать это, поэтому мне не нужно создавать ChurchNumeral для всех возможных типов? Я хотел бы иметь возможность применять ZERO (например) к любому UnaryOperator и аргументу типа T. –

+0

@espertus Я не думаю, что вы можете сделать это напрямую, продолжая использовать lambdas. Функциональный интерфейс не может представлять общий метод. Но вы можете подделать его, используя zero() как метод вместо константы. Вы также можете использовать метод convert(), который выполняет непроверенное преобразование, и он должен работать нормально. См. Обновленный ответ. – Misha

 Смежные вопросы

  • Нет связанных вопросов^_^