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