9

Я пытаюсь создать небольшую функциональную библиотеку программирования для Java (просто чтобы поцарапать свой собственный зуд). Определяя higher-order functions для List s, Set s и Map s Я столкнулся с этой проблемой: функции, которые берут коллекцию и возвращают коллекцию того же типа, имеют почти одну и ту же реализацию и все же должны быть переопределены для каждого из структура данных - List s, Set s, и Map s.Удаление дублирования кода

Например, вот реализация map функции для List с, и Set с:

public static <A, B> List<B> map(
    List<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    List<B> ys = new ArrayList<B>(); 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
    return ys; 
} 

public static <A, B> Set<B> map(
    Set<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    Set<B> ys = new HashSet<B>(); 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
    return ys; 
} 

filter функция:

public static <A> List<A> filter(
    List<? extends A> xs, 
    Func1<? super A, Boolean> predicate 
) { 
    List<A> ys = new ArrayList<A>(); 
    for(A a : xs) { 
    if(predicate.apply(a)) { 
     ys.add(a); 
    } 
    } 
    return ys; 
} 

public static <A> Set<A> filter(
    Set<? extends A> xs, 
    Func1<? super A, Boolean> predicate 
) { 
    Set<A> ys = new HashSet<A>(); 
    for(A a : xs) { 
    if(predicate.apply(a)) { 
     ys.add(a); 
    } 
    } 
    return ys; 
} 

Как видно из этого примера, органы реализаций для Set и List почти одинаковы.

Есть много много функций, как map и filter в моей библиотеке, и каждый из них определяются трижды для каждого типа коллекций я заинтересован (т.е. List, Set и Map). Это приводит к большому дублированию кода и запаху кода. Я хотел знать, есть ли какой-то способ в Java, который поможет мне избежать дублирования кода.

Любая помощь будет принята с благодарностью. Благодарю.

EDIT:

Func1 представляет собой интерфейс определяется как:

interface Func1<A, B> { 
    public B apply(A a); 
} 
+0

Похоже, вы можете просто использовать интерфейс 'Collection', чтобы исключить отдельные случаи для интерфейсов' List' и 'Set'. –

+0

@Bears: Проблема заключается в следующем: 'map' для' List' должен возвращать 'List',' map' для 'Set' должен возвращать' Set' и т. Д. –

+0

Итак, реализуйте для 'Collection' с аргументом' List' или 'Set' as и вызывайте эту реализацию из ваших классов класса List и' Set'. – rsp

ответ

4

Java не имеет полиморфизм более высокого порядка (он же выше-виды), так что это в системе типов невозможно. Многие Java-программисты прибегают к XML и/или рефлексии (т. Е. Избегают системы типов) для устранения этого недостатка.

Scala может справиться с этим и то, что вы описываете, называется ковариантным функтором. Этот довольно фундаментальный тип данных (наряду с гораздо большим) был реализован в библиотеке Scalaz и включает в себя реализации для java.util. *.

Кроме того, существует еще много ковариантных функторов, которые не являются коллекциями и многими другими функторами, которые не являются ковариантными.

Возможно, вы захотите обратиться к «20 промежуточным упражнениям Scala», если хотите изучить эту конкретную концепцию.

1

Эффективно список только монаду для типа T, давая ему возможность хранить несколько экземпляров типа. Вот почему здесь применяются все обычные законы монад, поэтому вы можете реализовать все операции с использованием элементов bind и return.

Прошу прощения, что у меня нет времени для объяснения до сих пор, но в пространстве .NET мы имеем SelectMany и Enumerable.Repeat (1, element) для тех же целей. Об этом есть много информации.

Любой оператор (например, filter в вашем примере) может быть реализован с использованием SelectMay соответственно.

+0

Спасибо за ответ Johannes, но я не использую никаких функциональных структур данных здесь. 'List' и' Set' в моих примерах - 'java.util.List' и' java.util.Set' соответственно. –

+0

уверен, но они реализуют что-то вроде IEnumerable или ICollection (монада коллекции в этом случае) –

+0

Можете ли вы добавить код, чтобы объяснить вашу мысль? –

6
public static <A, B> List<B> map(
    List<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    List<B> ys = new ArrayList<B>(); 
    map(xy, transformer, ys); 
    return ys; 
} 

public static <A, B> Set<B> map(
    Set<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    Set<B> ys = new HashSet<B>(); 
    map(xy, transformer, ys); 
    return ys; 
} 
private static <A, B> map(
    Collection<? extends A> xs, 
    Func1<? super A, ? extends B> transformer, 
    Iterable<B> ys 
) { 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
} 

Работа выполнена.

Обратите внимание, что это типично для API Java, чтобы передать измененную коллекцию, а не создавать новую в методе. Лично я не поклонник изменчивости на уровне коллекции, но с этим мы должны работать (на Java).

(Я не увлекаюсь A и B как общие параметры с такими вещами.)

Или вы могли бы использовать завод:.

public static <A, B> List<B> map(
    List<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    return map(xs, transformer, new CollectionFactory<B, List<B>>() { 
     public List<B> create() { return new ArrayList<B>(); } 
    }); 
} 

public static <A, B> Set<B> map(
    Set<? extends A> xs, 
    Func1<? super A, ? extends B> transformer 
) { 
    return map(xs, transformer, new CollectionFactory<B, Set<B>>() { 
     public Set<B> create() { return new HashSet<B>(); } 
    }); 
} 

private interface CollectionFactory<E, C extends Collection<E>> { 
    C create(); 
} 

private static <A, B, C extends Collection<B>> C map(
    Iterable<? extends A> xs, 
    Func1<? super A, ? extends B> transformer, 
    CollectionFactory<B, C> factory 
) { 
    C ys = factory.create(); 
    for(A a : xs) { 
    ys.add(transformer.apply(a)); 
    } 
    return ys; 
} 

(Если вы можете мириться с бессмысленным многословием анонимных внутренних классов)

Если бы это было не для Collection то вы бы нужно положить немного (безобразно) адаптер в

для полноты (хотя не проверял, может сделать с несколькими ухищрений), неприятный решение с помощью наследования:.

Set<String> strs = hashSets().map(things, formatter); 

... 

public static <E> Functions<E, Set<E>> hashSets() { 
    return new Functions<E, Set<E>>() { 
     protected Set<E> createCollections() { 
      return new HashSet<E>(); 
     } 
    }; 
} 

public abstract class Functions<E, C extends Collection<E>> { 
    protected abstract C createCollection(); 

    public <S> C map(
     Set<? extends S> xs, 
     Func1<? super S, ? extends E> transformer 
    ) { 
     C ys = createCollection(); 
     for(S a : xs) { 
     ys.add(transformer.apply(a)); 
     } 
     return ys; 
    } 

    public <S> C filter(
     List<? extends S> xs, 
     Func1<? super S, Boolean> predicate // Predicate<? super S> might be nicer!! 
    ) { 
     C ys = createCollection(); 
     for(A a : xs) { 
     if(predicate.apply(a)) { 
      ys.add(a); 
     } 
     } 
     return ys; 
    } 
} 
+0

API такой же, новый метод карты является приватным –

+0

Это все еще много дублирования кода. Для каждого нового метода, который я могу добавить, мне нужно написать частную реализацию, используя «Коллекции», а затем метод удобства для каждого типа данных. Пойдем, должен быть лучший способ сделать это. :( –

+0

@ one-zero-zero-one Вам понадобится метод с общим кодом и метод, который решает, какую реализацию использовать, да. Вы могли бы просто использовать метод реализации. Вы можете использовать наследование, но для этих видов статические методы, я бы назвал это неприятным. –

2

Я не верю, что система типов Java достаточно сложна, чтобы справиться с этим, но Scala's is. В версии 2.8 библиотеки коллекций они создали систему для автоматического создания коллекции соответствующего типа на основе коллекции, с которой вы работаете. Итак, если вы звоните filter на List, он возвращает новый List. Позвоните по телефону filter по номеру Set, и вы получите Set назад. Он делает это, хотя имеет единственную реализацию filter.

Чтобы узнать больше, взгляните на Traversable и на материал, который его использует. Я считаю, что CanBuildFrom - это место, где много волшебства.

4

Я не думаю, что вы можете сделать лучше, чем Том предложил в his answer. Java не поддерживает более высокосортные типы - функцию, которая может помочь вам абстрагироваться по типу коллекции и, таким образом, избежать дублирования одного и того же кода для каждого из типов коллекций.

Scala поддерживает эту функцию и широко используется в своей стандартной библиотеке. This paper Adriaan Moors обсуждает, как Scala избегает такого рода дублирования кода с помощью более высоких типов.

Два скриншоты из вышеупомянутой статье:


alt text


alt text

+2

Согласен. Том (вверху) неверен. –

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