Skip to content

Commit b170a51

Browse files
committed
Lazy For() Comprehension
1 parent e3937c4 commit b170a51

File tree

2 files changed

+3800
-0
lines changed

2 files changed

+3800
-0
lines changed

vavr/generator/Generator.scala

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,150 @@ def generateMainClasses(): Unit = {
743743
"""
744744
}
745745

746+
def genLazyFor(im: ImportManager, packageName: String, className: String): String = {
747+
xs"""
748+
${
749+
monadicTypesFor
750+
.filterNot(_ == "Iterable")
751+
.gen(mtype => (2 to N).gen(i => {
752+
val forClassName = s"ForLazy$i$mtype"
753+
val isComplex = monadicTypesThatNeedParameter.contains(mtype)
754+
val parameterInset = if (isComplex) "L, " else ""
755+
val generics = parameterInset + (1 to i).gen(j => s"T$j")(", ")
756+
757+
val params = (1 to i).gen { j =>
758+
if (j == 1)
759+
s"$mtype<${parameterInset}T1> ts1"
760+
else {
761+
val inputTypes = (1 until j).gen(k => s"T$k")(", ")
762+
s"Function${j - 1}<$inputTypes, $mtype<${parameterInset}T$j>> ts$j"
763+
}
764+
}(", ")
765+
766+
val ctorArgs = (1 to i).gen(j => s"ts$j")(", ")
767+
768+
xs"""
769+
/$javadoc
770+
* Creates a lazy {@code For}-comprehension over ${i.numerus(mtype)}.
771+
*
772+
* <p>The first argument ({@code ts1}) is the initial ${mtype}. Each subsequent
773+
* argument ({@code ts2} .. {@code ts$i}) is a function that receives all values
774+
* bound so far and returns the next ${mtype}. This only builds the lazy
775+
* comprehension; effects are evaluated when {@code yield(...)} is called.</p>
776+
*
777+
${(0 to i).gen(j => if (j == 0) "*" else s"* @param ts$j the ${j.ordinal} ${mtype}")("\n")}
778+
${if (isComplex) s"* @param <L> the common left-hand type of all ${mtype}s\n" else ""}
779+
${(1 to i).gen(j => s"* @param <T$j> the component type of the ${j.ordinal} ${mtype}")("\n")}
780+
* @return a new {@code $forClassName} builder of arity $i
781+
* @throws NullPointerException if any argument is {@code null}
782+
*/
783+
public static <$generics> $forClassName<$generics> For($params) {
784+
${(1 to i).gen(j => xs"""$Objects.requireNonNull(ts$j, "ts$j is null");""")("\n")}
785+
return new $forClassName<>($ctorArgs);
786+
}
787+
"""
788+
})("\n\n"))("\n\n")
789+
}
790+
791+
${
792+
monadicTypesFor
793+
.filterNot(_ == "Iterable")
794+
.gen(mtype => (2 to N).gen(i => {
795+
val rtype = mtype
796+
val forClassName = s"ForLazy$i$mtype"
797+
val parameterInset = if (monadicTypesThatNeedParameter.contains(mtype)) "L, " else ""
798+
val generics = parameterInset + (1 to i).gen(j => s"T$j")(", ")
799+
val functionType = i match {
800+
case 2 => BiFunctionType
801+
case _ => s"Function$i"
802+
}
803+
val args = (1 to i).gen(j => s"? super T$j")(", ")
804+
805+
val fields = (1 to i).gen { j =>
806+
if (j == 1)
807+
s"private final $mtype<${parameterInset}T1> ts1;"
808+
else {
809+
val inputTypes = (1 until j).gen(k => s"T$k")(", ")
810+
s"private final Function${j - 1}<$inputTypes, $mtype<${parameterInset}T$j>> ts$j;"
811+
}
812+
}("\n")
813+
814+
val ctorParams = (1 to i).gen { j =>
815+
if (j == 1)
816+
s"$mtype<${parameterInset}T1> ts1"
817+
else {
818+
val inputTypes = (1 until j).gen(k => s"T$k")(", ")
819+
s"Function${j - 1}<$inputTypes, $mtype<${parameterInset}T$j>> ts$j"
820+
}
821+
}(", ")
822+
823+
val assignments = (1 to i).gen(j => s"this.ts$j = ts$j;")("\n")
824+
825+
val yieldBody = {
826+
def nestedLambda(j: Int): String = {
827+
val base = " "
828+
val indent = " " * j
829+
if (j == i) {
830+
val argsList = (1 to i).map(k => s"t$k").mkString(", ")
831+
val inputArgs = (1 until i).map(k => s"t$k").mkString(", ")
832+
s"ts$i.apply($inputArgs).map(t$i -> f.apply($argsList))"
833+
} else if (j == 1) {
834+
s"ts1.flatMap(t1 -> {\n" +
835+
s"${base}${indent} return ${nestedLambda(j + 1)};\n" +
836+
s"${base}${indent}})"
837+
} else {
838+
val inputArgs = (1 until j).map(k => s"t$k").mkString(", ")
839+
s"ts$j.apply($inputArgs).flatMap(t$j -> {\n" +
840+
s"${base}${indent} return ${nestedLambda(j + 1)};\n" +
841+
s"${base}${indent}})"
842+
}
843+
}
844+
845+
nestedLambda(1)
846+
}
847+
848+
xs"""
849+
/$javadoc
850+
* A lazily evaluated {@code For}-comprehension with ${i.numerus(mtype)}.
851+
*
852+
* <p>Constructed via {@code For(...)} and evaluated by calling {@code yield(...)}.
853+
* Construction is side-effect free; underlying ${i.plural(mtype)} are only
854+
* traversed when {@code yield(...)} is invoked.</p>
855+
*
856+
${if (monadicTypesThatNeedParameter.contains(mtype)) s"* @param <L> the common left-hand type of all ${mtype}s\n" else ""}
857+
${(1 to i).gen(j => s"* @param <T$j> the component type of the ${j.ordinal} ${mtype}")("\n")}
858+
*/
859+
public static class $forClassName<$generics> {
860+
861+
$fields
862+
863+
private $forClassName($ctorParams) {
864+
$assignments
865+
}
866+
867+
/$javadoc
868+
* Produces results by mapping the Cartesian product of all bound values.
869+
*
870+
* <p>Evaluation is lazy and delegated to the underlying ${i.plural(mtype)} by
871+
* composing flatMap/map chains.</p>
872+
*
873+
* @param f a function that maps a tuple of bound values to a result
874+
* @param <R> the element type of the resulting {@code $rtype}
875+
* @return an {@code $rtype} containing mapped results
876+
* @throws NullPointerException if {@code f} is {@code null}
877+
*/
878+
public <R> $rtype<${parameterInset}R> yield($functionType<$args, ? extends R> f) {
879+
$Objects.requireNonNull(f, "f is null");
880+
return $yieldBody;
881+
}
882+
}
883+
"""
884+
})("\n\n"))("\n\n")
885+
}
886+
"""
887+
}
888+
889+
746890
def genFor(im: ImportManager, packageName: String, className: String): String = {
747891
xs"""
748892
//
@@ -1440,6 +1584,8 @@ def generateMainClasses(): Unit = {
14401584

14411585
${genFor(im, packageName, className)}
14421586

1587+
${genLazyFor(im, packageName, className)}
1588+
14431589
${genMatch(im, packageName, className)}
14441590
}
14451591
"""

0 commit comments

Comments
 (0)