diff --git a/modules/core/src/main/scala/higherkindness/droste/basis.scala b/modules/core/src/main/scala/higherkindness/droste/basis.scala index 352736fd..34db0227 100644 --- a/modules/core/src/main/scala/higherkindness/droste/basis.scala +++ b/modules/core/src/main/scala/higherkindness/droste/basis.scala @@ -42,6 +42,9 @@ trait Project[F[_], R] { self => F: Foldable[F]): U = Project.collect[F, R, U, B](r)(pf) + def find[B](r: R)(pf: PartialFunction[R, B])(implicit F: Foldable[F]): Option[B] = + Project.find[F, R, B](r)(pf) + def contains(r: R, c: R)(implicit R: Eq[R], F: Foldable[F]): Boolean = Project.contains[F, R](r, c) @@ -73,6 +76,9 @@ object Project extends FloatingBasisInstances[Project] { pf.lift(_) .foldRight[U](U.algebra(NilF))((a, b) => U.algebra(ConsF(a, b)))) + def find[F[_], R, B](r: R)(pf: PartialFunction[R, B])(implicit P: Project[F, R], F: Foldable[F]): Option[B] = + foldMap[F, R, Option[B] @@ Tags.First](r)(pf.lift(_).first).unwrap + def contains[F[_], R]( r: R, c: R)(implicit P: Project[F, R], R: Eq[R], F: Foldable[F]): Boolean = diff --git a/modules/core/src/main/scala/higherkindness/droste/syntax/package.scala b/modules/core/src/main/scala/higherkindness/droste/syntax/package.scala index 6055f712..1a4e1053 100644 --- a/modules/core/src/main/scala/higherkindness/droste/syntax/package.scala +++ b/modules/core/src/main/scala/higherkindness/droste/syntax/package.scala @@ -174,6 +174,9 @@ object ProjectSyntax { implicit U: Basis[ListF[B, ?], U]): U = Project.collect[F, T, U, B](self)(pf) + def find[B](pf: PartialFunction[T, B]): Option[B] = + Project.find(self)(pf) + def contains(c: T)(implicit T: Eq[T]): Boolean = Project.contains(self, c) diff --git a/modules/core/src/main/scala/higherkindness/droste/util/newtypes.scala b/modules/core/src/main/scala/higherkindness/droste/util/newtypes.scala index 509458e0..607aae05 100644 --- a/modules/core/src/main/scala/higherkindness/droste/util/newtypes.scala +++ b/modules/core/src/main/scala/higherkindness/droste/util/newtypes.scala @@ -10,6 +10,7 @@ object newtypes { object Tags { sealed trait Conjunction sealed trait Disjunction + sealed trait First } implicit class BooleanOps(b: Boolean) { @@ -34,4 +35,14 @@ object newtypes { b: Boolean @@ Tags.Disjunction): Boolean @@ Tags.Disjunction = @@(a.unwrap || b.unwrap) } + + implicit class OptionOps[B](o: Option[B]) { + def first: Option[B] @@ Tags.First = @@(o) + } + + implicit def optionMonoid[B]: Monoid[Option[B] @@ Tags.First] = + new Monoid[Option[B] @@ Tags.First] { + def empty: Option[B] @@ Tags.First = @@(Option.empty[B]) + def combine(x: Option[B] @@ Tags.First, y: Option[B] @@ Tags.First): Option[B] @@ Tags.First = @@(x.unwrap.orElse(y.unwrap)) + } } diff --git a/modules/tests/src/test/scala/higherkindness/droste/examples/foldableOperationsOnProject.scala b/modules/tests/src/test/scala/higherkindness/droste/examples/foldableOperationsOnProject.scala index 04506fb8..be93dae4 100644 --- a/modules/tests/src/test/scala/higherkindness/droste/examples/foldableOperationsOnProject.scala +++ b/modules/tests/src/test/scala/higherkindness/droste/examples/foldableOperationsOnProject.scala @@ -3,6 +3,7 @@ package examples import cats.instances.list._ import cats.kernel.Eq +import cats.syntax.option._ import org.scalacheck.Properties import org.scalacheck.Prop._ @@ -44,6 +45,16 @@ final class FoldableOpsChecks case Lam(name, _) => name } ?= List("a", "b", "c", "d", "e", "f") + property("find none") = + tru[LExpr].find { + case v @ Var(name) if name.startsWith("d") => v + } ?= Option.empty[Var] + + property("find existing") = + tru[LExpr].find { + case Lam(name, _) if !name.startsWith("a") => name + } ?= "b".some + property("any") = tru[LExpr].any { case Var(name) => true