001/* 002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon) 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 */ 009 010package gololang; 011 012import java.util.Collection; 013import java.util.Iterator; 014import java.util.List; 015import java.util.LinkedList; 016import java.util.Objects; 017 018/** 019 * Represents a lazy list object. 020 * <p> 021 * A lazy list behaves like a linked list, but each next element 022 * is represented by a closure that is evaluated only if needed. 023 * The value is cached, so that the closure representing the tail 024 * is evaluated only once. 025 * 026 * Since the tail closure will be called at most once, and we can't 027 * guarantee when, or even if, it will be called, this closure must be 028 * a pure, side-effect free, function. 029 */ 030public class LazyList implements Collection<Object>, HeadTail<Object> { 031 032 /** 033 * Represents the empty list. 034 */ 035 public static final LazyList EMPTY = new LazyList(null, null) { 036 @Override 037 public boolean equals(Object other) { 038 return other == this; 039 } 040 041 @Override 042 public int hashCode() { 043 return Objects.hash(null, null); 044 } 045 046 @Override 047 public boolean isEmpty() { 048 return true; 049 } 050 051 @Override 052 public int size() { 053 return 0; 054 } 055 056 @Override 057 public LazyList tail() { 058 return this; 059 } 060 061 @Override 062 public String toString() { 063 return "LazyList.EMPTY"; 064 } 065 }; 066 067 private final Object head; 068 private final FunctionReference tail; 069 private LazyList memoTail = null; 070 071 /** 072 * Create a new list from the head and tail values. 073 * 074 * @param head the first value of the list. 075 * @param tail a {@code FunctionReference} that returns a LazyList when invoked. 076 * @return a new {@code LazyList} 077 */ 078 public static LazyList cons(Object head, FunctionReference tail) { 079 if (tail == null) { 080 throw new IllegalArgumentException("Use the empty list instead of null as the last element of a LazyList"); 081 } 082 return new LazyList(head, tail); 083 } 084 085 private LazyList(Object head, FunctionReference tail) { 086 this.head = head; 087 this.tail = tail; 088 } 089 090 /** 091 * Gets the first element of the list (its head). 092 * 093 * @return an {@code Object}, or {@code null} if the list is empty. 094 */ 095 public Object head() { 096 return this.head; 097 } 098 099 /** 100 * Gets the rest of the list (its tail). 101 * 102 * @return a {@code LazyList}, or {@code EMPTY} if the list is empty, 103 * contains only one value, or if the closure failed. 104 */ 105 public LazyList tail() { 106 if (memoTail == null) { 107 try { 108 memoTail = (LazyList) (this.tail.invoke()); 109 } catch (Throwable e) { 110 memoTail = EMPTY; 111 } 112 } 113 return memoTail; 114 } 115 116 /** 117 * Checks whether the list is empty or not. 118 * 119 * @return {@code true} if the list is EMPTY, {@code false} otherwise. 120 */ 121 @Override 122 public boolean isEmpty() { 123 return false; 124 } 125 126 /** 127 * Creates an iterator over the list. 128 * <p>The iterator does not support removal. 129 * 130 * @return an iterator. 131 */ 132 @Override 133 public Iterator<Object> iterator() { 134 return new HeadTailIterator<Object>(this); 135 } 136 137 /** 138 * Convert the lazy list into a regular list. 139 * <p> 140 * Note that it evaluates the whole list. Take care to 141 * <b>not use</b> this method on infinite lists, since 142 * no check is done. 143 * 144 * @return a new {@code LinkedList} 145 */ 146 public List<Object> asList() { 147 List<Object> lst = new LinkedList<>(); 148 for (Object o : this) { 149 lst.add(o); 150 } 151 return lst; 152 } 153 154 /** 155 * Returns the number of elements in this list. 156 * <p> 157 * Note that it evaluates the whole list. Take care to 158 * <b>not use</b> this method on infinite lists, since 159 * no check is done. 160 * 161 * @return the number of elements in this list. 162 */ 163 @Override 164 public int size() { 165 return 1 + this.tail().size(); 166 } 167 168 /** 169 * Compares the specified object with this list. 170 * <p> 171 * This is a value comparison. 172 * Note that it may evaluate the whole list. Take care to 173 * <b>not use</b> this method on infinite lists, since 174 * no check is done. 175 * 176 * @param o the object to be compared for equality with this list 177 * @return {@code true} if the specified object is equal to this list. 178 */ 179 @Override 180 public boolean equals(Object o) { 181 if (o == this) return true; 182 if (o == null) return false; 183 if (!(o instanceof LazyList)) return false; 184 LazyList other = (LazyList) o; 185 if (this.isEmpty() && other.isEmpty()) return true; 186 if (!this.head.equals(other.head)) return false; 187 if (this.tail.equals(other.tail)) return true; 188 return this.tail().equals(other.tail()); 189 } 190 191 /** 192 * Compute the hashCode of this list. 193 * <p> 194 * Note that it evaluates the whole list. Take care to 195 * <b>not use</b> this method on infinite lists, since 196 * no check is done. 197 * 198 * @return the {@code hashCode}. 199 */ 200 @Override 201 public int hashCode() { 202 return Objects.hash(this.head, this.tail()); 203 } 204 205 /** 206 * Returns an array containing all of the elements in this list. 207 * <p> 208 * Note that it evaluates the whole list. Take care to 209 * <b>not use</b> this method on infinite lists, since 210 * no check is done. 211 * 212 * @return an array containing all of the elements in this list 213 */ 214 @Override 215 public Object[] toArray() { 216 return this.asList().toArray(); 217 } 218 219 /** 220 * Returns an array containing all of the elements in this list with a type 221 * of the given array. 222 * <p> 223 * Note that it evaluates the whole list. Take care to 224 * <b>not use</b> this method on infinite lists, since 225 * no check is done. 226 * 227 * @return an array containing all of the elements in this list 228 */ 229 @Override 230 public <T> T[] toArray(T[] a) { 231 return this.asList().toArray(a); 232 } 233 234 235 /** 236 * Destructuration helper. 237 * 238 * @return a tuple of head and tail 239 */ 240 public Tuple destruct() { 241 return new Tuple(head(), tail()); 242 } 243 244 /** 245 * Returns the element at the specified position in this list. 246 * <p> 247 * Note that it evaluates the list up to the required element. 248 * 249 * @param index index of the element to return 250 * @return the element at the specified position in this list 251 */ 252 public Object get(int index) { 253 if (index < 0 || this.isEmpty()) throw new IndexOutOfBoundsException(); 254 if (index == 0) return this.head(); 255 return this.tail().get(index - 1); 256 } 257 258 /** 259 * Returns the position of the first occurrence of the given element in the 260 * list. 261 * <p> 262 * Note that it evaluates the list up to the given element. Take care to 263 * <b>not use</b> this method on infinite lists, since 264 * no check is done. 265 * 266 * @param o element to search for 267 * @return the index of the first occurrence, or -1 if not present 268 */ 269 public int indexOf(Object o) { 270 int idx = 0; 271 for (Object elt : this) { 272 if (elt.equals(o)) return idx; 273 idx++; 274 } 275 return -1; 276 } 277 278 /** 279 * Check if the list contains the given object. 280 * <p> 281 * Note that it evaluates the list up to the given element. Take care to 282 * <b>not use</b> this method on infinite lists, since 283 * no check is done. 284 * 285 * @param o element to search for 286 * @return {@code true} if the element is in the list, {@code false} 287 * otherwise. 288 */ 289 @Override 290 public boolean contains(Object o) { 291 return this.indexOf(o) != -1; 292 } 293 294 /** 295 * Check if the list contains all the objects in the given collection. 296 * <p> 297 * Note that it evaluates the list up to the given element, *for each* 298 * element in the collection (at worse). This implementation is highly inefficient. 299 * <p> 300 * Take care to 301 * <b>not use</b> this method on infinite lists, since 302 * no check is done. 303 * 304 * @param c collection of elements to search for 305 * @return {@code true} if all the elements are in the list, {@code false} 306 * otherwise. 307 */ 308 @Override 309 public boolean containsAll(Collection<?> c) { 310 for (Object elt : c) { 311 if (!this.contains(elt)) return false; 312 } 313 return true; 314 } 315 316 @Override 317 public String toString() { 318 return String.format("LazyList<head=%s, tail=%s>", head, tail); 319 } 320 321 322 @Override 323 public boolean add(Object e) { 324 throw new UnsupportedOperationException("a LazyList is immutable"); 325 } 326 327 public void add(int index, Object element) { 328 throw new UnsupportedOperationException("a LazyList is immutable"); 329 } 330 331 @Override 332 public boolean addAll(Collection<?> c) { 333 throw new UnsupportedOperationException("a LazyList is immutable"); 334 } 335 336 public boolean addAll(int index, Collection<?> c) { 337 throw new UnsupportedOperationException("a LazyList is immutable"); 338 } 339 340 @Override 341 public boolean remove(Object e) { 342 throw new UnsupportedOperationException("a LazyList is immutable"); 343 } 344 345 public Object remove(int index) { 346 throw new UnsupportedOperationException("a LazyList is immutable"); 347 } 348 349 @Override 350 public boolean removeAll(Collection<?> c) { 351 throw new UnsupportedOperationException("a LazyList is immutable"); 352 } 353 354 @Override 355 public boolean retainAll(Collection<?> c) { 356 throw new UnsupportedOperationException("a LazyList is immutable"); 357 } 358 359 @Override 360 public void clear() { 361 throw new UnsupportedOperationException("a LazyList is immutable"); 362 } 363 364 public Object set(int index, Object element) { 365 throw new UnsupportedOperationException("a LazyList is immutable"); 366 } 367 368}