[QUOTE=cmrudolph]Ich habe für das List
Interface mal einen Vorschlag für ein paar Default-Methoden:
public default A head() throws NoSuchElementException {
return headOpt().orElseThrow(() -> new NoSuchElementException("head() call on empty List"));
}
public default Optional<A> headOpt() {
return getOpt(0);
}
public List<A> tail() throws NoSuchElementException;
public Optional<List<A>> tailOpt();
public default A last() throws NoSuchElementException {
return lastOpt().orElseThrow(() -> new NoSuchElementException("last() call on empty List"));
}
public default Optional<A> lastOpt() {
return getOpt(size() - 1);
}
public List<A> init() throws NoSuchElementException;
public Optional<List<A>> initOpt();
public default A get(int index) throws IndexOutOfBoundsException {
return getOpt(index).orElseThrow(
() -> new IndexOutOfBoundsException(String.format("get(%d) call on list of size %d", index, size()))
);
}
public Optional<A> getOpt(int index);
public int size();
public default boolean isEmpty() {
return size() == 0;
}
public List<A> with(int index, A value);
public List<A> concat(List<? extends A> that);
public List<A> take(int count);
public List<A> drop(int count);
public <B> List<B> map(Function<? super A, ? extends B> fn);
public <B> List<B> flatMap(Function<? super A, ? extends Iterable<? extends A>> fn);
public List<A> filter(Predicate<? super A> predicate);
}```
[/quote]
An sich richtig, aber `head` und `tail` sind normalerweise die primitiven Operationen auf einer immutablen Liste, deshalb würde ich es eher andersherum machen, nämlich `get` und `getOpt` mittels Default-Methoden implementieren (wie z.B: auch `drop`, `take` u.s.w
> Da ginge noch einiges mehr, aber wie weit soll man gehen?
Es fehlen noch völlig Methoden zum Aufbauen einer Liste. takeWhile und dropWhile würde ich auch noch dazunehmen, außerdem reverse.
> Und: wie testet man die Default-Methoden am geschicktesten?
Über die Standard-Implementierung.
> Inwieweit macht es überhaupt Sinn, Default-Methoden zu verwenden?
Ich denke hier macht es schon Sinn. Insbesondere wenn wir bei Listen mehrere (wahrschienlich anonyme) Implementierungen haben, die Laziness unterstützen.
> Den Vorschlag habe ich noch nicht commited. Allerdings habe ich die Methoden noch etwas umsortiert und die xxxOpt-Varianten für `init` und `last` eingefügt.
Lass mich noch mal drüberschauen, ich poste einen Entwurf.
> Es fehlen auch noch `foldl` und `foldr`.
Sind als `foldLeft` und `foldRight` vorhanden. Finde ich lesbarer, und bisher haben wir das meiste ausgeschrieben (mti Ausnahme von xxxOpt), können wir aber diskutieren.
*** Edit ***
public interface List extends Iterable {
public A head() throws NoSuchElementException;
@SuppressWarnings("unchecked")
public static <A> List<A> empty() {
return (List<A>) Nil.NIL;
}
public static <A> List<A> cons(A head, List<A> tail) {
return new Cons<>(head, tail);
}
public static <A> List<A> of(A... values) {
List<A> result = empty();
for (int i = values.length - 1; i >= 0; i--) {
result = cons(values**, result);
}
return result;
}
public default Optional<A> headOpt() {
return isEmpty() ? Optional.<A>empty() : Optional.of(head());
}
public List<A> tail() throws NoSuchElementException;
public default Optional<List<A>> tailOpt() {
return isEmpty() ? Optional.<List<A>>empty() : Optional.of(tail());
}
public default A last() throws NoSuchElementException {
if (isEmpty()) {
throw new NoSuchElementException("last() called on Nil");
}
A value = null;
for(A a : this) {
value = a;
}
return value;
}
public default Optional<A> lastOpt() {
if (isEmpty()) {
return Optional.empty();
}
A value = null;
for(A a : this) {
value = a;
}
return Optional.of(value);
}
public default List<A> init() throws NoSuchElementException {
if (isEmpty()) {
throw new NoSuchElementException("init() called on Nil");
}
return reverse().tail().reverse();
}
public default Optional<List<A>> initOpt() {
if (isEmpty()) {
return Optional.empty();
}
return Optional.of(reverse().tail().reverse());
}
public default A get(int index) throws IndexOutOfBoundsException {
List<A> current = this;
for (int i = 0; i < index && !current.isEmpty(); i++) {
current = current.tail();
}
if (current.isEmpty()) {
throw new IndexOutOfBoundsException();
}
return current.head();
}
public default Optional<A> getOpt(int index) {
List<A> current = this;
for (int i = 0; i < index && !current.isEmpty(); i++) {
current = current.tail();
}
if (current.isEmpty()) {
return Optional.empty();
}
return Optional.of(current.head());
}
public default int size() {
return foldLeft(0, (n, a) -> n + 1);
}
public default boolean isEmpty() {
return false;
}
public default List<A> with(int index, A value) throws IndexOutOfBoundsException {
int i = 0;
List<A> result = empty();
for(A a : this) {
cons(i++ == index ? value : a, result);
}
if (i < index) {
throw new IndexOutOfBoundsException();
}
return result.reverse();
}
public default List<A> concat(List<A> that) {
List<A> result = that;
for (A a : this.reverse()) {
result = cons(a, result);
}
return result;
}
public default List<A> take(int count) {
List<A> result = empty();
int index = 0;
for (A a : this) {
if (index++ >= count) {
break;
}
result = cons(a, result);
}
return result.reverse();
}
public default List<A> takeWhile(Predicate<A> predicate) {
List<A> result = empty();
for (A a : this) {
if (! predicate.test(a)) {
break;
}
result = cons(a, result);
}
return result.reverse();
}
public default List<A> drop(int count) {
List<A> current = this;
for (int index = 0; index < count && !current.isEmpty(); index++) {
current = current.tail();
}
return current;
}
public default List<A> dropWhile(Predicate<A> predicate) {
List<A> current = this;
while(! current.isEmpty() && predicate.test(current.head())) {
current = current.tail();
}
return current;
}
public default List<A> reverse() {
List<A> result = empty();
for (A a : this) {
result = cons(a, result);
}
return result;
}
public default <B> List<B> map(Function<? super A, ? extends B> fn) {
List<B> result = empty();
for (A a : this) {
cons(fn.apply(a), result);
}
return result.reverse();
}
public default <B> List<B> flatMap(Function<? super A, ? extends Iterable<? extends B>> fn) {
List<B> result = empty();
for (A a : this) {
for(B b : fn.apply(a)) {
cons(b, result);
}
}
return result.reverse();
}
public default List<A> filter(Predicate<? super A> predicate) {
List<A> result = empty();
for (A a : this) {
if (predicate.test(a)) {
cons(a, result);
}
}
return result.reverse();
}
public default <B> B foldLeft(B start, BiFunction<? super B, ? super A, ? extends B> fn) {
B value = start;
for(A a : this) {
value = fn.apply(value, a);
}
return value;
}
public default <B> B foldRight(BiFunction<? super A, ? super B, ? extends B> fn, B start) {
B value = start;
for(A a : this.reverse()) {
value = fn.apply(a, value);
}
return value;
}
@Override
public default Iterator<A> iterator() {
return new Iterator<A>() {
private List<A> current = List.this;
@Override
public boolean hasNext() {
return ! current.isEmpty();
}
@Override
public A next() {
if (! hasNext()) {
throw new NoSuchElementException();
}
A value = current.head();
current = current.tail();
return value;
}
};
}
}
public class Nil implements List {
final static Nil<?> NIL = new Nil<>();
private Nil() {
}
@Override
public Iterator<A> iterator() {
return Collections.emptyIterator();
}
@Override
public A head() throws NoSuchElementException {
throw new NoSuchElementException("head() call on Nil");
}
@Override
public List<A> tail() throws NoSuchElementException {
throw new NoSuchElementException("tail() call on Nil");
}
@Override
public boolean isEmpty() {
return true;
}
public String toString() {
return "[]";
}
}
public class Cons implements List {
private final A head;
private final List<A> tail;
Cons(A head, List<A> tail) {
this.head = Objects.requireNonNull(head);
this.tail = Objects.requireNonNull(tail);
}
public A head() {
return head;
}
public List<A> tail() {
return tail;
}
public String toString() {
StringBuilder sb = new StringBuilder();
for(A a : this) {
sb.append(sb.length() == 0 ? "[" : ",").append(a);
}
return sb.append("]").toString();
}
}
Im Repo-Code sind noch ein paar Fehlerchen drin. Das hier ist alles ungetestet.