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