Skip to content

Commit a55dd6f

Browse files
authored
Merge pull request #279 from ThoughtWorksInc/lazylist
Make Yield lazy in LazyList domain (fix #278)
2 parents 20f392a + dc147d1 commit a55dd6f

File tree

2 files changed

+275
-4
lines changed

2 files changed

+275
-4
lines changed

keywords-Yield/src/main/scala/com/thoughtworks/dsl/keywords/Yield.scala

+29-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import scala.language.higherKinds
3838
*/
3939
final case class Yield[Element](element: Element) extends AnyVal with Keyword[Yield[Element], Unit]
4040

41-
private[keywords] trait LowPriorityYield2 {
41+
private[keywords] trait LowPriorityYield3 {
4242

4343
def apply[A](elements: A*) = {
4444
From(elements)
@@ -66,7 +66,7 @@ private[keywords] object YieldScalaVersions {
6666
@enableMembersIf(scala.util.Properties.versionNumberString.matches("""^2\.1(1|2)\..*$"""))
6767
object Scala211Or212 {
6868

69-
trait LowPriorityYield1 extends LowPriorityYield2 {
69+
trait LowPriorityYield1 extends LowPriorityYield3 {
7070

7171
implicit def seqViewYieldFromDsl[A, FromCollection <: Traversable[A], Coll1, Coll2](
7272
implicit canBuildFrom: CanBuildFrom[SeqView[A, Coll1], A, SeqView[A, Coll2]])
@@ -112,7 +112,7 @@ private[keywords] object YieldScalaVersions {
112112
@enableMembersIf(scala.util.Properties.versionNumberString.matches("""^2\.13\..*$"""))
113113
object Scala213 {
114114

115-
trait LowPriorityYield1 extends LowPriorityYield2 {
115+
trait LowPriorityYield2 extends LowPriorityYield3 {
116116

117117
implicit def viewYieldFromDsl[A, FromCollection <: View.SomeIterableOps[A]]
118118
: Dsl[From[FromCollection], View[A], Unit] =
@@ -144,7 +144,7 @@ private[keywords] object YieldScalaVersions {
144144
}
145145
}
146146

147-
trait LowPriorityYield0 extends LowPriorityYield1 {
147+
trait LowPriorityYield1 extends LowPriorityYield2 {
148148
implicit def seqYieldFromDsl[A,
149149
FromCollection <: View.SomeIterableOps[A],
150150
Collection[X] <: SeqOps[X, Collection, Collection[X]]]
@@ -162,7 +162,32 @@ private[keywords] object YieldScalaVersions {
162162
keyword.element +: generateTail(())
163163
}
164164
}
165+
}
166+
167+
trait LowPriorityYield0 extends LowPriorityYield1 {
165168

169+
implicit def lazyListYieldFromDsl[A, FromCollection <: Iterable[A]]
170+
: Dsl[From[FromCollection], LazyList[A], Unit] =
171+
new Dsl[From[FromCollection], LazyList[A], Unit] {
172+
def cpsApply(keyword: From[FromCollection], generateTail: Unit => LazyList[A]): LazyList[A] = {
173+
keyword.elements.to(LazyList) #::: generateTail(())
174+
}
175+
}
176+
177+
implicit def lazyListYieldDsl[Element, That >: Element]: Dsl[Yield[Element], LazyList[That], Unit] =
178+
new Dsl[Yield[Element], LazyList[That], Unit] {
179+
def cpsApply(keyword: Yield[Element], generateTail: Unit => LazyList[That]): LazyList[That] = {
180+
keyword.element #:: generateTail(())
181+
}
182+
}
183+
184+
implicit def futureLazyListYieldDsl[Element, That >: Element]: Dsl[Yield[Element], LazyList[Future[That]], Unit] =
185+
new Dsl[Yield[Element], LazyList[Future[That]], Unit] {
186+
def cpsApply(keyword: Yield[Element],
187+
generateTail: Unit => LazyList[Future[That]]): LazyList[Future[That]] = {
188+
Future.successful(keyword.element) #:: generateTail(())
189+
}
190+
}
166191
}
167192
}
168193

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package com.thoughtworks.dsl.keywords
2+
3+
import com.thoughtworks.dsl.Dsl.!!
4+
import com.thoughtworks.enableMembersIf
5+
import org.scalatest.{FreeSpec, Matchers}
6+
7+
import scala.annotation.tailrec
8+
import scala.collection.{LinearSeq, SeqView}
9+
import scala.runtime.NonLocalReturnControl
10+
11+
/**
12+
* @author 杨博 (Yang Bo)
13+
*/
14+
class YieldSpec213 extends FreeSpec with Matchers {
15+
16+
"lazylist" - {
17+
18+
def shouldCompile = {
19+
!Yield("naked")
20+
LazyList.empty[String]
21+
}
22+
23+
"local method" in {
24+
def generator: LazyList[Int] = {
25+
def id[A](a: A) = a
26+
id(!Yield(100))
27+
LazyList.empty[Int]
28+
}
29+
generator should be(LazyList(100))
30+
}
31+
32+
"yield from" in {
33+
def generator: LazyList[Int] = {
34+
def id[A](a: A) = a
35+
id(!Yield(100, 200))
36+
LazyList.empty
37+
}
38+
generator should be(LazyList(100, 200))
39+
}
40+
41+
"local function" in {
42+
def generator: LazyList[Int] = {
43+
def id[A](a: A) = a
44+
(id[Unit] _)(!Yield(100))
45+
LazyList.empty[Int]
46+
}
47+
generator should be(LazyList(100))
48+
}
49+
50+
"do/while" - {
51+
"empty body" in {
52+
def generator: LazyList[Int] = {
53+
do {} while ({
54+
!Yield(100)
55+
false
56+
})
57+
LazyList.empty[Int]
58+
}
59+
generator should be(LazyList(100))
60+
}
61+
62+
"false" in {
63+
def generator: LazyList[Int] = {
64+
do {
65+
!Yield(100)
66+
} while (false)
67+
LazyList.empty[Int]
68+
}
69+
generator should be(LazyList(100))
70+
}
71+
72+
"with var" in {
73+
def generator: LazyList[Int] = {
74+
var i = 5
75+
do {
76+
i -= {
77+
!Yield(i)
78+
1
79+
}
80+
} while ({
81+
!Yield(-i)
82+
i > 0
83+
})
84+
LazyList.empty[Int]
85+
}
86+
generator should be(LazyList(5, -4, 4, -3, 3, -2, 2, -1, 1, 0))
87+
}
88+
}
89+
90+
"while" - {
91+
"false" in {
92+
def whileFalse: LazyList[Int] = {
93+
while (false) {
94+
!Yield(100)
95+
}
96+
LazyList.empty[Int]
97+
}
98+
99+
whileFalse should be(LazyList.empty)
100+
}
101+
}
102+
103+
"match/case" in {
104+
105+
def loop(i: Int): LazyList[Int] = {
106+
i match {
107+
case 100 =>
108+
LazyList.empty
109+
case _ =>
110+
!Yield(i)
111+
loop(i + 1)
112+
}
113+
}
114+
115+
loop(90) should be(LazyList(90, 91, 92, 93, 94, 95, 96, 97, 98, 99))
116+
117+
}
118+
119+
"recursive" in {
120+
def loop(i: Int): LazyList[Int] = {
121+
if (i < 100) {
122+
!Yield(i)
123+
loop(i + 1)
124+
} else {
125+
LazyList.empty
126+
}
127+
}
128+
129+
loop(90) should be(LazyList(90, 91, 92, 93, 94, 95, 96, 97, 98, 99))
130+
131+
}
132+
133+
"Given a generator that contains conditional Yield" - {
134+
def generator = {
135+
if (false) {
136+
!Yield(0)
137+
}
138+
if (true) {
139+
!Yield(1)
140+
}
141+
if ({ !Yield(2); false }) {
142+
!Yield(3)
143+
} else {
144+
!Yield(4)
145+
}
146+
LazyList.empty[Int]
147+
}
148+
149+
"Then the generator should contains values in selected branches" in {
150+
generator should be(Seq(1, 2, 4))
151+
}
152+
153+
}
154+
155+
"Given a continuation that uses Yield" - {
156+
157+
def yield4243: LazyList[Int] !! Unit = _ {
158+
!Yield(42)
159+
!Yield(43)
160+
}
161+
162+
"when create a generator that contains multiple Yield expression followed by a bang notation and a LazyList.empty" - {
163+
164+
def generator: LazyList[Int] = {
165+
!Yield(0)
166+
!Shift(yield4243)
167+
!Yield(1)
168+
LazyList.empty[Int]
169+
}
170+
171+
"Then the generator should contains yield values" in {
172+
generator should be(Seq(0, 42, 43, 1))
173+
}
174+
175+
}
176+
177+
}
178+
179+
"apply" in {
180+
def generator: LazyList[Int] = {
181+
val f = {
182+
!Yield(1)
183+
184+
{ (x: Int) =>
185+
-x
186+
}
187+
}
188+
189+
val result = f({
190+
!Yield(2)
191+
42
192+
})
193+
LazyList(result)
194+
}
195+
generator should be(LazyList(1, 2, -42))
196+
}
197+
198+
"return" in {
199+
def generator: LazyList[Int] = {
200+
if (true) {
201+
return {
202+
!Yield(100)
203+
LazyList(42)
204+
}
205+
}
206+
LazyList.empty[Int]
207+
}
208+
209+
a[NonLocalReturnControl[LazyList[Int]]] should be thrownBy generator.last
210+
}
211+
"partial function" - {
212+
"empty" in {
213+
Seq.empty[Any].flatMap {
214+
case i: Int =>
215+
!Yield(100)
216+
LazyList(42)
217+
} should be(empty)
218+
}
219+
220+
"flatMap" in {
221+
Seq(100, 200).flatMap {
222+
case i: Int =>
223+
!Yield(100)
224+
LazyList(42 + i)
225+
} should be(Seq(100, 142, 100, 242))
226+
}
227+
}
228+
229+
"nested function call" - {
230+
"call by value" in {
231+
def nested() = {
232+
"foo" +: !Yield("bar") +: LazyList.empty[Any]
233+
}
234+
nested() should be(LazyList("bar", "foo", ()))
235+
}
236+
"call by name" in {
237+
def nested() = {
238+
"foo" #:: !Yield("bar") #:: LazyList.empty[Any]
239+
}
240+
nested() should be(LazyList("bar", "foo", ()))
241+
}
242+
}
243+
244+
}
245+
246+
}

0 commit comments

Comments
 (0)