Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] New collection methods #1010

Merged
merged 13 commits into from
Oct 7, 2024
24 changes: 24 additions & 0 deletions core/shared/src/main/scala/sigma/Colls.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ trait Coll[@specialized A] {
*/
def apply(i: Int): A

/** The element at given index.
* Indices start at `0`; `xs.apply(0)` is the first element of collection `xs`.
* Note the indexing syntax `xs(i)` is a shorthand for `xs.apply(i)`.
*
* @param i the index
* @return the element at the given index
* @throws ArrayIndexOutOfBoundsException if `i < 0` or `length <= i`
*/
def get(i: Int): Option[A] = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalaDoc doesn't correspond to the method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if (isDefinedAt(i)) {
Some(apply(i))
} else {
None
}
}

/** Tests whether this $coll contains given index.
*
* The implementations of methods `apply` and `isDefinedAt` turn a `Coll[A]` into
Expand Down Expand Up @@ -76,6 +92,14 @@ trait Coll[@specialized A] {
* produces a collection ((x0, y0), ..., (xK, yK)) where K = min(N, M) */
def zip[@specialized B](ys: Coll[B]): Coll[(A, B)]

/** For this collection (x0, ..., xN) and other collection (y0, ..., yM)
* produces a collection ((x0, y0), ..., (xK, yK)) where K = min(N, M) */
def startsWith(ys: Coll[A]): Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalaDoc is not correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


/** For this collection (x0, ..., xN) and other collection (y0, ..., yM)
* produces a collection ((x0, y0), ..., (xK, yK)) where K = min(N, M) */
def endsWith(ys: Coll[A]): Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalaDoc is not correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


/** Tests whether a predicate holds for at least one element of this collection.
* @param p the predicate used to test elements.
* @return `true` if the given predicate `p` is satisfied by at least one element of this collection, otherwise `false`
Expand Down
8 changes: 8 additions & 0 deletions core/shared/src/main/scala/sigma/data/CollsOverArrays.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class CollOverArray[@specialized A](val toArray: Array[A], val builder: CollBuil

@inline def zip[@specialized B](ys: Coll[B]): PairColl[A, B] = builder.pairColl(this, ys)

@inline def startsWith(ys: Coll[A]): Boolean = toArray.startsWith(ys.toArray)

@inline def endsWith(ys: Coll[A]): Boolean = toArray.endsWith(ys.toArray)

def append(other: Coll[A]): Coll[A] = {
if (toArray.length <= 0) return other
val result = if (VersionContext.current.isJitActivated) {
Expand Down Expand Up @@ -350,6 +354,10 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R

def zip[@specialized B](ys: Coll[B]): PairColl[(L,R), B] = builder.pairColl(this, ys)

def startsWith(ys: Coll[(L, R)]): Boolean = toArray.startsWith(ys.toArray)

def endsWith(ys: Coll[(L, R)]): Boolean = toArray.endsWith(ys.toArray)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is simple, but inefficient ((ys.length + 1)*2 allocations) fallback implementation.
In the case when ys has type PairOfCols, it is possible to recursively call into ls and rs, and avoid allocations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, added tests also


override def indices: Coll[Int] = if (ls.length <= rs.length) ls.indices else rs.indices

override def flatMap[B: RType](f: ((L, R)) => Coll[B]): Coll[B] =
Expand Down
15 changes: 15 additions & 0 deletions core/shared/src/main/scala/sigma/reflection/ReflectionData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ object ReflectionData {
mkMethod(clazz, "apply", Array[Class[_]](classOf[Int])) { (obj, args) =>
obj.asInstanceOf[Coll[_]].apply(args(0).asInstanceOf[Int])
},
mkMethod(clazz, "get", Array[Class[_]](classOf[Int])) { (obj, args) =>
obj.asInstanceOf[Coll[_]].get(args(0).asInstanceOf[Int])
},
mkMethod(clazz, "append", Array[Class[_]](classOf[Coll[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].append(args(0).asInstanceOf[Coll[Any]])
},
Expand All @@ -170,6 +173,18 @@ object ReflectionData {
},
mkMethod(clazz, "map", Array[Class[_]](classOf[Function1[_, _]], classOf[RType[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].map(args(0).asInstanceOf[Any => Any])(args(1).asInstanceOf[RType[Any]])
},
mkMethod(clazz, "reverse", Array[Class[_]]()) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].reverse
},
mkMethod(clazz, "distinct", Array[Class[_]]()) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].distinct
},
mkMethod(clazz, "startsWith", Array[Class[_]](classOf[Coll[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].startsWith(args(0).asInstanceOf[Coll[Any]])
},
mkMethod(clazz, "endsWith", Array[Class[_]](classOf[Coll[_]])) { (obj, args) =>
obj.asInstanceOf[Coll[Any]].endsWith(args(0).asInstanceOf[Coll[Any]])
}
)
)
Expand Down
16 changes: 16 additions & 0 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,22 @@ object SigmaDataReflection {
mkMethod(clazz, "flatMap_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Function1[_,_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].flatMap_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Any => Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "reverse_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].reverse_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]])(args(2).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "distinct_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].distinct_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]])(args(2).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "startsWith_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].startsWith_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "endsWith_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SCollectionMethods.type].endsWith_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator])
}
)
)
Expand Down
136 changes: 113 additions & 23 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply {
SFunc(Array(ThisType, tIV, SInt), SInt, paramIVSeq),
26, PerItemCost(baseCost = JitCost(20), perChunkCost = JitCost(10), chunkSize = 2))
.withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Any, Int]("indexOf"))
.withInfo(MethodCall, "")
.withInfo(MethodCall, "Returns index of a collection element, or -1 if not found")

/** Implements evaluation of Coll.indexOf method call ErgoTree node.
* Called via reflection based on naming convention.
Expand Down Expand Up @@ -1052,8 +1052,7 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply {
baseCost = JitCost(10), perChunkCost = JitCost(1), chunkSize = 10)

val ZipMethod = SMethod(this, "zip",
SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)),
29, Zip_CostKind)
SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)), 29, Zip_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

Expand All @@ -1069,29 +1068,120 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply {
}
}

// ======== 6.0 methods below ===========

val ReverseMethod = SMethod(this, "reverse",
SFunc(Array(ThisType), ThisType, paramIVSeq), 30, Zip_CostKind) // todo: costing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to declare the descriptor by just cloning from Append (Zip is too small).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

/** Implements evaluation of Coll.reverse method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def reverse_eval[A](mc: MethodCall, xs: Coll[A])
(implicit E: ErgoTreeEvaluator): Coll[A] = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.reverse
}
}

val DistinctMethod = SMethod(this, "distinct",
SFunc(Array(ThisType), ThisType, paramIVSeq), 31, Zip_CostKind) // todo: costing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cost of distinct is larger then zip or Append, probably by a factor of 2.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

/** Implements evaluation of Coll.reverse method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def distinct_eval[A](mc: MethodCall, xs: Coll[A])
(implicit E: ErgoTreeEvaluator): Coll[A] = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.distinct
}
}

val StartsWithMethod = SMethod(this, "startsWith",
SFunc(Array(ThisType, ThisType), SBoolean, paramIVSeq), 32, Zip_CostKind) // todo: costing
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

/** Implements evaluation of Coll.zip method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def startsWith_eval[A](mc: MethodCall, xs: Coll[A], ys: Coll[A])
(implicit E: ErgoTreeEvaluator): Boolean = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.startsWith(ys)
}
}

val EndsWithMethod = SMethod(this, "endsWith",
SFunc(Array(ThisType, ThisType), SBoolean, paramIVSeq), 33, Zip_CostKind) // todo: costing
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

/** Implements evaluation of Coll.zip method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def endsWith_eval[A](mc: MethodCall, xs: Coll[A], ys: Coll[A])
(implicit E: ErgoTreeEvaluator): Boolean = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.endsWith(ys)
}
}

val GetMethod = SMethod(this, "get",
SFunc(Array(ThisType, SInt), SOption(tIV), Array[STypeParam](tIV)), 34, ByIndex.costKind) //todo: costing
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")

private val v5Methods = super.getMethods() ++ Seq(
SizeMethod,
GetOrElseMethod,
MapMethod,
ExistsMethod,
FoldMethod,
ForallMethod,
SliceMethod,
FilterMethod,
AppendMethod,
ApplyMethod,
IndicesMethod,
FlatMapMethod,
PatchMethod,
UpdatedMethod,
UpdateManyMethod,
IndexOfMethod,
ZipMethod
)

private val v6Methods = v5Methods ++ Seq(
ReverseMethod,
DistinctMethod,
StartsWithMethod,
EndsWithMethod,
GetMethod
)

/** This method should be overriden in derived classes to add new methods in addition to inherited.
* Typical override: `super.getMethods() ++ Seq(m1, m2, m3)`
*/
override protected def getMethods(): Seq[SMethod] = super.getMethods() ++
Seq(
SizeMethod,
GetOrElseMethod,
MapMethod,
ExistsMethod,
FoldMethod,
ForallMethod,
SliceMethod,
FilterMethod,
AppendMethod,
ApplyMethod,
IndicesMethod,
FlatMapMethod,
PatchMethod,
UpdatedMethod,
UpdateManyMethod,
IndexOfMethod,
ZipMethod
)
override protected def getMethods(): Seq[SMethod] = {
if (VersionContext.current.isV6SoftForkActivated) {
v6Methods
} else {
v5Methods
}
}

}

object STupleMethods extends MethodsContainer {
Expand Down
13 changes: 13 additions & 0 deletions sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,19 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext =>
val i = asRep[Int](argsV(0))
val d = asRep[t](argsV(1))
xs.getOrElse(i, d)
case SCollectionMethods.ReverseMethod.name =>
xs.reverse
case SCollectionMethods.DistinctMethod.name =>
xs.distinct
case SCollectionMethods.StartsWithMethod.name =>
val ys = asRep[Coll[t]](argsV(0))
xs.startsWith(ys)
case SCollectionMethods.EndsWithMethod.name =>
val ys = asRep[Coll[t]](argsV(0))
xs.endsWith(ys)
case SCollectionMethods.GetMethod.name =>
val idx = asRep[Int](argsV(0))
xs.get(idx)
case _ => throwError()
}
case (opt: ROption[t]@unchecked, SOptionMethods) => method.name match {
Expand Down
16 changes: 16 additions & 0 deletions sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,22 @@ object GraphIRReflection {
},
mkMethod(clazz, "exists", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[Any]].exists(args(0).asInstanceOf[ctx.Ref[Any => Boolean]])
},
// V6 methods
mkMethod(clazz, "reverse", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[ctx.Coll[Any]].reverse
},
mkMethod(clazz, "distinct", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[ctx.Coll[Any]].distinct
},
mkMethod(clazz, "startsWith", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[Any]].startsWith(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Any]]])
},
mkMethod(clazz, "endsWith", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[Any]].endsWith(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Any]]])
},
mkMethod(clazz, "get", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Coll[_]].apply(args(0).asInstanceOf[ctx.Ref[Int]])
}
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import sigma.compiler.ir.{Base, IRContext}
implicit def eA: Elem[A];
def length: Ref[Int];
def apply(i: Ref[Int]): Ref[A];
def get(index: Ref[Int]): Ref[WOption[A]];
def getOrElse(index: Ref[Int], default: Ref[A]): Ref[A];
def map[B](f: Ref[scala.Function1[A, B]]): Ref[Coll[B]];
def zip[B](ys: Ref[Coll[B]]): Ref[Coll[scala.Tuple2[A, B]]];
Expand All @@ -29,6 +30,10 @@ import sigma.compiler.ir.{Base, IRContext}
def updateMany(indexes: Ref[Coll[Int]], values: Ref[Coll[A]]): Ref[Coll[A]];
def slice(from: Ref[Int], until: Ref[Int]): Ref[Coll[A]];
def append(other: Ref[Coll[A]]): Ref[Coll[A]];
def reverse: Ref[Coll[A]]
def distinct: Ref[Coll[A]]
def startsWith(ys: Ref[Coll[A]]): Ref[Boolean];
def endsWith(ys: Ref[Coll[A]]): Ref[Boolean];
};
trait CollBuilder extends Def[CollBuilder] {
def fromItems[T](items: Ref[T]*)(implicit cT: Elem[T]): Ref[Coll[T]];
Expand Down
Loading
Loading