diff --git a/builtin/core.wren b/builtin/core.wren index a3722dd9..a127cdda 100644 --- a/builtin/core.wren +++ b/builtin/core.wren @@ -26,21 +26,9 @@ class Sequence { return result } - map(f) { - var result = new List - for (element in this) { - result.add(f.call(element)) - } - return result - } + map(transformation) { new MapSequence(this, transformation) } - where(f) { - var result = new List - for (element in this) { - if (f.call(element)) result.add(element) - } - return result - } + where(predicate) { new WhereSequence(this, predicate) } all(f) { for (element in this) { @@ -100,6 +88,32 @@ class Sequence { } } +class MapSequence is Sequence { + new(seq, f) { + _seq = seq + _f = f + } + + iterate(n) { _seq.iterate(n) } + iteratorValue(iterator) { _f.call(_seq.iteratorValue(iterator)) } +} + +class WhereSequence is Sequence { + new(seq, f) { + _seq = seq + _f = f + } + + iterate(n) { + while (n = _seq.iterate(n)) { + if (_f.call(_seq.iteratorValue(n))) break + } + return n + } + + iteratorValue(iterator) { _seq.iteratorValue(iterator) } +} + class String is Sequence { bytes { new StringByteSequence(this) } } diff --git a/doc/site/core/sequence.markdown b/doc/site/core/sequence.markdown index 807e6c84..c8a6c8d4 100644 --- a/doc/site/core/sequence.markdown +++ b/doc/site/core/sequence.markdown @@ -74,14 +74,13 @@ Creates a [list](list.html) containing all the elements in the sequence. ### **map**(transformation) -Creates a new list by applying `transformation` to each element in the -sequence. +Creates a new sequence that applies the `transformation` to each element in the +original sequence while it is iterated. -Iterates over the sequence, passing each element to the function -`transformation`. Generates a new list from the result of each of those calls. +The `list` method can be used to turn the resulting sequence into a list. :::dart - [1, 2, 3].map {|n| n * 2} // [2, 4, 6]. + [1, 2, 3].map {|n| n * 2}.list // [2, 4, 6]. ### **reduce**(function) @@ -95,10 +94,13 @@ Similar to above, but uses `seed` for the initial value of the accumulator. If t ### **where**(predicate) -Produces a new list containing only the elements in the sequence that pass the -`predicate`. +Creates a new sequence containing only the elements from the original sequence +that pass the `predicate`. -Iterates over the sequence, passing each element to the function `predicate`. -If it returns `true`, adds the element to the result list. +During iteration, each element in the original sequence is passed to the +function `predicate`. If it returns `false`, the element is skipped. - (1..10).where {|n| n % 2 == 1} // [1, 3, 5, 7, 9]. +The `list` method can be used to turn the resulting sequence into a list. + + :::dart + (1..10).where {|n| n % 2 == 1}.list // [1, 3, 5, 7, 9]. diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index ea75e896..93440552 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -72,21 +72,9 @@ static const char* libSource = " return result\n" " }\n" "\n" -" map(f) {\n" -" var result = new List\n" -" for (element in this) {\n" -" result.add(f.call(element))\n" -" }\n" -" return result\n" -" }\n" +" map(transformation) { new MapSequence(this, transformation) }\n" "\n" -" where(f) {\n" -" var result = new List\n" -" for (element in this) {\n" -" if (f.call(element)) result.add(element)\n" -" }\n" -" return result\n" -" }\n" +" where(predicate) { new WhereSequence(this, predicate) }\n" "\n" " all(f) {\n" " for (element in this) {\n" @@ -146,6 +134,32 @@ static const char* libSource = " }\n" "}\n" "\n" +"class MapSequence is Sequence {\n" +" new(seq, f) {\n" +" _seq = seq\n" +" _f = f\n" +" }\n" +"\n" +" iterate(n) { _seq.iterate(n) }\n" +" iteratorValue(iterator) { _f.call(_seq.iteratorValue(iterator)) }\n" +"}\n" +"\n" +"class WhereSequence is Sequence {\n" +" new(seq, f) {\n" +" _seq = seq\n" +" _f = f\n" +" }\n" +"\n" +" iterate(n) {\n" +" while (n = _seq.iterate(n)) {\n" +" if (_f.call(_seq.iteratorValue(n))) break\n" +" }\n" +" return n\n" +" }\n" +"\n" +" iteratorValue(iterator) { _seq.iteratorValue(iterator) }\n" +"}\n" +"\n" "class String is Sequence {\n" " bytes { new StringByteSequence(this) }\n" "}\n" diff --git a/test/core/list/map.wren b/test/core/list/map.wren index 4ed3778a..5eb759a1 100644 --- a/test/core/list/map.wren +++ b/test/core/list/map.wren @@ -1,3 +1,3 @@ var a = [1, 2, 3] -var b = a.map {|x| x + 1 } +var b = a.map {|x| x + 1 }.list IO.print(b) // expect: [2, 3, 4] diff --git a/test/core/list/where.wren b/test/core/list/where.wren index ebc4a228..ab451bde 100644 --- a/test/core/list/where.wren +++ b/test/core/list/where.wren @@ -1,6 +1,6 @@ var a = [1, 2, 3] -var b = a.where {|x| x > 1 } +var b = a.where {|x| x > 1 }.list IO.print(b) // expect: [2, 3] -var c = a.where {|x| x > 10 } +var c = a.where {|x| x > 10 }.list IO.print(c) // expect: [] diff --git a/test/core/range/map.wren b/test/core/range/map.wren index 73766961..85d6fc25 100644 --- a/test/core/range/map.wren +++ b/test/core/range/map.wren @@ -1,3 +1,3 @@ var a = 1..3 -var b = a.map {|x| x + 1 } +var b = a.map {|x| x + 1 }.list IO.print(b) // expect: [2, 3, 4] diff --git a/test/core/range/where.wren b/test/core/range/where.wren index 796b4585..0a90e17a 100644 --- a/test/core/range/where.wren +++ b/test/core/range/where.wren @@ -1,6 +1,6 @@ var a = 1..3 -var b = a.where {|x| x > 1 } +var b = a.where {|x| x > 1 }.list IO.print(b) // expect: [2, 3] -var c = a.where {|x| x > 10 } +var c = a.where {|x| x > 10 }.list IO.print(c) // expect: [] diff --git a/test/core/sequence/map.wren b/test/core/sequence/map.wren new file mode 100644 index 00000000..45beba20 --- /dev/null +++ b/test/core/sequence/map.wren @@ -0,0 +1,46 @@ +// Infinite iterator demonstrating that Sequence.map is not eager +class FibIterator { + new { + _current = 0 + _next = 1 + } + + iterate { + var sum = _current + _next + _current = _next + _next = sum + } + + value { _current } +} + +class Fib is Sequence { + iterate(iterator) { + if (iterator == null) return new FibIterator + iterator.iterate + return iterator + } + + iteratorValue(iterator) { iterator.value } +} + +var squareFib = (new Fib).map {|fib| fib * fib } +var iterator = null + +IO.print(squareFib is Sequence) // expect: true +IO.print(squareFib) // expect: instance of MapSequence + +iterator = squareFib.iterate(iterator) +IO.print(squareFib.iteratorValue(iterator)) // expect: 0 + +iterator = squareFib.iterate(iterator) +IO.print(squareFib.iteratorValue(iterator)) // expect: 1 + +iterator = squareFib.iterate(iterator) +IO.print(squareFib.iteratorValue(iterator)) // expect: 1 + +iterator = squareFib.iterate(iterator) +IO.print(squareFib.iteratorValue(iterator)) // expect: 4 + +iterator = squareFib.iterate(iterator) +IO.print(squareFib.iteratorValue(iterator)) // expect: 9 diff --git a/test/core/sequence/where.wren b/test/core/sequence/where.wren new file mode 100644 index 00000000..e641ab22 --- /dev/null +++ b/test/core/sequence/where.wren @@ -0,0 +1,40 @@ +// Infinite iterator demonstrating that Sequence.where is not eager +class FibIterator { + new { + _current = 0 + _next = 1 + } + + iterate { + var sum = _current + _next + _current = _next + _next = sum + } + + value { _current } +} + +class Fib is Sequence { + iterate(iterator) { + if (iterator == null) return new FibIterator + iterator.iterate + return iterator + } + + iteratorValue(iterator) { iterator.value } +} + +var largeFibs = (new Fib).where {|fib| fib > 100 } +var iterator = null + +IO.print(largeFibs is Sequence) // expect: true +IO.print(largeFibs) // expect: instance of WhereSequence + +iterator = largeFibs.iterate(iterator) +IO.print(largeFibs.iteratorValue(iterator)) // expect: 144 + +iterator = largeFibs.iterate(iterator) +IO.print(largeFibs.iteratorValue(iterator)) // expect: 233 + +iterator = largeFibs.iterate(iterator) +IO.print(largeFibs.iteratorValue(iterator)) // expect: 377