From b87acab897998cd4f7eef9c3d2191fc0ac52bcdb Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Tue, 9 Nov 2021 17:24:26 -0500 Subject: [PATCH] foldLeft, foldRight, other Foldable specializations (#11592) * FoldableContravariant, a mapping for Foldable instances * use FoldableContravariant to specialize several ImmArraySeq, NonEmpty methods * folding specializations for ImmArray * a few docs for FoldableContravariant * specializations for FrontStack * no changelog CHANGELOG_BEGIN CHANGELOG_END --- .../daml/lf/data/FrontStack.scala | 19 +++++- .../digitalasset/daml/lf/data/ImmArray.scala | 18 ++++-- .../daml/lf/data/FrontStackSpec.scala | 4 ++ .../scalautil/FoldableContravariant.scala | 60 +++++++++++++++++++ .../daml/scalautil/nonempty/NonEmpty.scala | 13 ++-- 5 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 libs-scala/scala-utils/src/main/scala/com/daml/scalautil/FoldableContravariant.scala diff --git a/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/FrontStack.scala b/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/FrontStack.scala index 2612c9b404..eac0ab0852 100644 --- a/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/FrontStack.scala +++ b/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/FrontStack.scala @@ -4,8 +4,6 @@ package com.daml.lf.data import ScalazEqual.{orderBy, toIterableForScalazInstances} -import scalaz.syntax.applicative._ -import scalaz.syntax.traverse._ import scalaz.{Applicative, Order, Traverse} import scala.annotation.tailrec @@ -154,11 +152,26 @@ object FrontStack extends FrontStackInstances { implicit val `FrontStack covariant`: Traverse[FrontStack] = new Traverse[FrontStack] { override def traverseImpl[G[_]: Applicative, A, B]( fa: FrontStack[A] - )(f: A => G[B]): G[FrontStack[B]] = + )(f: A => G[B]): G[FrontStack[B]] = { + import scalaz.syntax.applicative._, scalaz.syntax.traverse._ fa.toBackStack.bqFoldRight(FrontStack.empty[B].pure[G])( (a, z) => ^(f(a), z)(_ +: _), (iaa, z) => ^(iaa traverse f, z)(_ ++: _), ) + } + + override def map[A, B](fa: FrontStack[A])(f: A => B) = fa map f + + override def foldLeft[A, B](fa: FrontStack[A], z: B)(f: (B, A) => B) = + fa.iterator.foldLeft(z)(f) + + override def foldRight[A, B](fa: FrontStack[A], z: => B)(f: (A, => B) => B) = + fa.toBackStack.bqFoldRight(z)( + (a, z) => f(a, z), + (iaa, z) => iaa.foldRight(z)(f(_, _)), + ) + + override def length[A](fa: FrontStack[A]) = fa.length } implicit def `FrontStack Order`[A: Order]: Order[FrontStack[A]] = { diff --git a/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/ImmArray.scala b/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/ImmArray.scala index 2f8abfcbe7..b22d244cb3 100644 --- a/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/ImmArray.scala +++ b/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/ImmArray.scala @@ -3,6 +3,7 @@ package com.daml.lf.data +import com.daml.scalautil.FoldableContravariant import com.daml.scalautil.Statement.discard import ScalazEqual.{equalBy, orderBy, toIterableForScalazInstances} @@ -393,6 +394,12 @@ object ImmArray extends ImmArrayInstances { } .map(_.toImmArray) } + + override def foldLeft[A, B](fa: ImmArray[A], z: B)(f: (B, A) => B) = + fa.foldLeft(z)(f) + + override def foldRight[A, B](fa: ImmArray[A], z: => B)(f: (A, => B) => B) = + fa.foldRight(z)(f(_, _)) } implicit def immArrayOrderInstance[A: Order]: Order[ImmArray[A]] = { @@ -429,12 +436,13 @@ object ImmArray extends ImmArrayInstances { object ImmArraySeq extends ImmArraySeqCompanion { val Empty: ImmArraySeq[Nothing] = ImmArray.Empty.toSeq implicit val `immArraySeq Traverse instance`: Traverse[ImmArraySeq] = new Traverse[ImmArraySeq] - with Foldable.FromFoldr[ImmArraySeq] { + with Foldable.FromFoldr[ImmArraySeq] + with FoldableContravariant[ImmArraySeq, ImmArray] { override def map[A, B](fa: ImmArraySeq[A])(f: A => B) = fa.toImmArray.map(f).toSeq - override def foldLeft[A, B](fa: ImmArraySeq[A], z: B)(f: (B, A) => B) = - fa.foldLeft(z)(f) - override def foldRight[A, B](fa: ImmArraySeq[A], z: => B)(f: (A, => B) => B) = - fa.foldRight(z)(f(_, _)) + + protected[this] override def Y = Foldable[ImmArray] + protected[this] override def ctmap[A](xa: ImmArraySeq[A]) = xa.toImmArray + override def traverseImpl[F[_], A, B]( immArr: ImmArraySeq[A] )(f: A => F[B])(implicit F: Applicative[F]): F[ImmArraySeq[B]] = { diff --git a/daml-lf/data/src/test/scala/com/digitalasset/daml/lf/data/FrontStackSpec.scala b/daml-lf/data/src/test/scala/com/digitalasset/daml/lf/data/FrontStackSpec.scala index 21304f273d..df56edd2a0 100644 --- a/daml-lf/data/src/test/scala/com/digitalasset/daml/lf/data/FrontStackSpec.scala +++ b/daml-lf/data/src/test/scala/com/digitalasset/daml/lf/data/FrontStackSpec.scala @@ -75,6 +75,10 @@ class FrontStackSpec "Traverse instance" should { checkLaws(ScalazProperties.traverse.laws[FrontStack]) + + "reconstruct itself with foldRight" in forAll { fs: FrontStack[Int] => + scalaz.Foldable[FrontStack].foldRight(fs, FrontStack.empty[Int])(_ +: _) should ===(fs) + } } "Equal instance" should { diff --git a/libs-scala/scala-utils/src/main/scala/com/daml/scalautil/FoldableContravariant.scala b/libs-scala/scala-utils/src/main/scala/com/daml/scalautil/FoldableContravariant.scala new file mode 100644 index 0000000000..bcc9013e73 --- /dev/null +++ b/libs-scala/scala-utils/src/main/scala/com/daml/scalautil/FoldableContravariant.scala @@ -0,0 +1,60 @@ +// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.scalautil + +import scalaz.{Monoid, Foldable, Semigroup} +import FoldableContravariant._ + +// Specialized overrides for when a Foldable wraps another Foldable. +// If you need to hand-write some of these, just mix in the traits you +// don't want to hand-write. +private[daml] trait FoldableContravariant[X[_], Y[_]] + extends CoreOps[X, Y] + with Semigroupoids[X, Y] + with Conversions[X, Y] + with Lookups[X, Y] + +private[daml] object FoldableContravariant { + trait CtMap[X[_], Y[_]] { + protected[this] def Y: Foldable[Y] + protected[this] def ctmap[A](ax: X[A]): Y[A] + } + + // non-derived combinators + trait CoreOps[X[_], Y[_]] extends Foldable[X] with CtMap[X, Y] { + override final def foldLeft[A, B](xa: X[A], z: B)(f: (B, A) => B) = Y.foldLeft(ctmap(xa), z)(f) + override final def foldRight[A, B](xa: X[A], z: => B)(f: (A, => B) => B) = + Y.foldRight(ctmap(xa), z)(f) + override final def foldMap[A, Z: Monoid](xa: X[A])(f: A => Z) = Y.foldMap(ctmap(xa))(f) + } + + // plays on functions from the semigroupoids library + trait Semigroupoids[X[_], Y[_]] extends Foldable[X] with CtMap[X, Y] { + override final def foldMapRight1Opt[A, B](xa: X[A])(z: A => B)(f: (A, => B) => B) = + Y.foldMapRight1Opt(ctmap(xa))(z)(f) + override final def foldMapLeft1Opt[A, B](xa: X[A])(z: A => B)(f: (B, A) => B) = + Y.foldMapLeft1Opt(ctmap(xa))(z)(f) + override final def foldMap1Opt[A, B: Semigroup](xa: X[A])(f: A => B) = + Y.foldMap1Opt(ctmap(xa))(f) + } + + // to (collection type) converters + trait Conversions[X[_], Y[_]] extends Foldable[X] with CtMap[X, Y] { + override final def toStream[A](xa: X[A]) = Y.toStream(ctmap(xa)) + override final def toSet[A](xa: X[A]) = Y.toSet(ctmap(xa)) + override final def toList[A](xa: X[A]) = Y.toList(ctmap(xa)) + override final def toVector[A](xa: X[A]) = Y.toVector(ctmap(xa)) + override final def toIList[A](xa: X[A]) = Y.toIList(ctmap(xa)) + override final def toEphemeralStream[A](xa: X[A]) = + Y.toEphemeralStream(ctmap(xa)) + } + + // list-like operations + trait Lookups[X[_], Y[_]] extends Foldable[X] with CtMap[X, Y] { + override final def index[A](xa: X[A], i: Int) = Y.index(ctmap(xa), i) + override final def length[A](xa: X[A]) = Y.length(ctmap(xa)) + override final def all[A](xa: X[A])(p: A => Boolean): Boolean = Y.all(ctmap(xa))(p) + override final def any[A](xa: X[A])(p: A => Boolean): Boolean = Y.any(ctmap(xa))(p) + } +} diff --git a/libs-scala/scala-utils/src/main/scala/com/daml/scalautil/nonempty/NonEmpty.scala b/libs-scala/scala-utils/src/main/scala/com/daml/scalautil/nonempty/NonEmpty.scala index fd67712be7..a2bd860d3a 100644 --- a/libs-scala/scala-utils/src/main/scala/com/daml/scalautil/nonempty/NonEmpty.scala +++ b/libs-scala/scala-utils/src/main/scala/com/daml/scalautil/nonempty/NonEmpty.scala @@ -6,10 +6,12 @@ package com.daml.scalautil.nonempty import scala.collection.compat._ import scala.collection.{immutable => imm}, imm.Map, imm.Set import scalaz.Id.Id -import scalaz.{Foldable, Foldable1, Monoid, OneAnd, Semigroup, Traverse} +import scalaz.{Foldable, Foldable1, OneAnd, Semigroup, Traverse} import scalaz.Leibniz, Leibniz.=== import scalaz.Liskov, Liskov.<~< import scalaz.syntax.std.option._ + +import com.daml.scalautil.FoldableContravariant import NonEmptyCollCompat._ /** The visible interface of [[NonEmpty]]; use that value to access @@ -169,7 +171,7 @@ sealed abstract class NonEmptyCollInstances extends NonEmptyCollInstances0 { sealed abstract class NonEmptyCollInstances0 { implicit def foldable1[F[_]](implicit F: Foldable[F]): Foldable1[NonEmptyF[F, *]] = - NonEmpty.substF(new Foldable1[F] { + NonEmpty.substF(new Foldable1[F] with FoldableContravariant[F, F] { private[this] def errEmpty(fa: F[_]) = throw new IllegalArgumentException( s"empty structure coerced to non-empty: $fa: ${fa.getClass.getSimpleName}" @@ -187,12 +189,9 @@ sealed abstract class NonEmptyCollInstances0 { override def foldMapLeft1[A, B](fa: F[A])(z: A => B)(f: (B, A) => B) = assertNE(fa, F.foldMapLeft1Opt(fa)(z)(f)) - override def foldMap[A, B: Monoid](fa: F[A])(f: A => B) = F.foldMap(fa)(f) + protected[this] override def Y = F - override def foldRight[A, B](fa: F[A], z: => B)(f: (A, => B) => B) = F.foldRight(fa, z)(f) - - override def foldLeft[A, B](fa: F[A], z: B)(f: (B, A) => B) = - F.foldLeft(fa, z)(f) + protected[this] override def ctmap[A](xa: F[A]) = xa }) import scala.language.implicitConversions