Skip to content

Commit

Permalink
[Scala 3] Derive SemigroupK (#541)
Browse files Browse the repository at this point in the history
  • Loading branch information
joroKr21 committed May 22, 2024
1 parent 1fe25ce commit 5d90802
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 24 deletions.
5 changes: 3 additions & 2 deletions core/src/main/scala-3/cats/tagless/Derive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ object Derive:
inline def invariant[F[_]]: Invariant[F] = MacroInvariant.derive
inline def semigroupal[F[_]]: Semigroupal[F] = MacroSemigroupal.derive
inline def apply[F[_]]: Apply[F] = MacroApply.derive
inline def semigroupK[F[_]]: SemigroupK[F] = MacroSemigroupK.derive
inline def bifunctor[F[_, _]]: Bifunctor[F] = MacroBifunctor.derive
inline def profunctor[F[_, _]]: Profunctor[F] = MacroProfunctor.derive
inline def functorK[Alg[_[_]]]: FunctorK[Alg] = MacroFunctorK.derive
inline def semigroupalK[Alg[_[_]]]: SemigroupalK[Alg] = MacroSemigroupalK.derive
inline def contravariantK[Alg[_[_]]]: ContravariantK[Alg] = MacroContravariantK.derive
inline def invariantK[Alg[_[_]]]: InvariantK[Alg] = MacroInvariantK.derive
inline def semigroupalK[Alg[_[_]]]: SemigroupalK[Alg] = MacroSemigroupalK.derive
inline def applyK[Alg[_[_]]]: ApplyK[Alg] = MacroApplyK.derive
inline def contravariantK[Alg[_[_]]]: ContravariantK[Alg] = MacroContravariantK.derive
1 change: 1 addition & 0 deletions core/src/main/scala-3/cats/tagless/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ extension (x: Contravariant.type) @experimental inline def derived[F[_]]: Contra
extension (x: Invariant.type) @experimental inline def derived[F[_]]: Invariant[F] = Derive.invariant
extension (x: Semigroupal.type) @experimental inline def derived[F[_]]: Semigroupal[F] = Derive.semigroupal
extension (x: Apply.type) @experimental inline def derived[F[_]]: Apply[F] = Derive.apply
extension (x: SemigroupK.type) @experimental inline def derived[F[_]]: SemigroupK[F] = Derive.semigroupK
extension (x: Bifunctor.type) @experimental inline def derived[F[_, _]]: Bifunctor[F] = Derive.bifunctor
extension (x: Profunctor.type) @experimental inline def derived[F[_, _]]: Profunctor[F] = Derive.profunctor
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private class DeriveMacros[Q <: Quotes](using val q: Q):

extension (terms: Seq[Term])
def combineTo[A: Type](
args: Seq[Transform] = Nil,
args: Seq[Transform] = terms.map(_ => PartialFunction.empty),
body: Combine = PartialFunction.empty
): Expr[A] =
val name = Symbol.freshName("$anon")
Expand Down
55 changes: 55 additions & 0 deletions core/src/main/scala-3/cats/tagless/macros/MacroSemigroupK.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2019 cats-tagless maintainers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cats.tagless.macros

import cats.tagless.*
import cats.{Semigroup, SemigroupK}

import scala.annotation.experimental
import scala.quoted.*

@experimental
object MacroSemigroupK:
inline def derive[F[_]]: SemigroupK[F] = ${ semigroupK }

def semigroupK[F[_]: Type](using Quotes): Expr[SemigroupK[F]] = '{
new SemigroupK[F]:
def combineK[A](x: F[A], y: F[A]): F[A] =
${ deriveCombineK('{ x }, '{ y }) }
}

private[macros] def deriveCombineK[F[_]: Type, A: Type](
x: Expr[F[A]],
y: Expr[F[A]]
)(using q: Quotes): Expr[F[A]] =
import quotes.reflect.*
given DeriveMacros[q.type] = new DeriveMacros

val A = TypeRepr.of[A]
val a = A.typeSymbol

List(x.asTerm, y.asTerm).combineTo[F[A]](
body = {
case (tpe, x :: y :: Nil) if tpe.contains(a) =>
Select
.unique(tpe.summonLambda[SemigroupK](a), "combineK")
.appliedToTypes(List(A))
.appliedTo(x, y)
case (tpe, x :: y :: Nil) =>
tpe.summonOpt[Semigroup].fold(x)(Select.unique(_, "combine").appliedTo(x, y))
}
)
16 changes: 8 additions & 8 deletions tests/src/test/scala-3/cats/tagless/tests/autoApplyKTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,17 @@ object autoApplyKTests:
object AutoApplyKTestAlg:
import TestInstances.*

implicit def eqForAutoApplyKTestAlg[F[_]](implicit
eqFi: Eq[F[Int]],
eqFf: Eq[F[Float]],
eqEfd: Eq[EitherT[F, String, Double]]
given [F[_]](using
Eq[F[Int]],
Eq[F[Float]],
Eq[EitherT[F, String, Double]]
): Eq[AutoApplyKTestAlg[F]] = Eq.by: algebra =>
(algebra.parseInt, algebra.parseDouble, algebra.divide)

implicit def arbitraryAutoApplyKTestAlg[F[_]](implicit
arbFi: Arbitrary[F[Int]],
arbFf: Arbitrary[F[Float]],
arbEfd: Arbitrary[EitherT[F, String, Double]]
given [F[_]](using
Arbitrary[F[Int]],
Arbitrary[F[Float]],
Arbitrary[EitherT[F, String, Double]]
): Arbitrary[AutoApplyKTestAlg[F]] = Arbitrary(for
pInt <- Arbitrary.arbitrary[String => F[Int]]
pDouble <- Arbitrary.arbitrary[String => EitherT[F, String, Double]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ object autoApplyTests:
curry <- Arbitrary.arbitrary[String => Int => T]
yield new TestAlgebra[T]:
override def abstractEffect(i: String) = absEff(i)
override def concreteEffect(a: String) = conEff.getOrElse(super.concreteEffect(_))(a)
override def concreteEffect(a: String) = conEff.getOrElse(super.concreteEffect)(a)
override def abstractOther(a: String) = absOther(a)
override def concreteOther(a: String) = conOther.getOrElse(super.concreteOther(_))(a)
override def concreteOther(a: String) = conOther.getOrElse(super.concreteOther)(a)
override def withoutParams = withoutParameters
override def curried(a: String)(b: Int) = curry(a)(b)
)
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ object autoContravariantKTests:
def foldSpecialized(init: String)(f: (Int, String) => Int): Cokleisli[F, String, Int]

object TestAlgebra:
implicit def eqv[F[_]](implicit arbFi: Arbitrary[F[Int]], arbFs: Arbitrary[F[String]]): Eq[TestAlgebra[F]] =
given [F[_]](using Arbitrary[F[Int]], Arbitrary[F[String]]): Eq[TestAlgebra[F]] =
Eq.by(algebra => (algebra.sum, algebra.sumAll _, algebra.foldSpecialized))

implicit def arbitrary[F[_]](implicit
arbFs: Arbitrary[F[String]],
coFi: Cogen[F[Int]],
coFs: Cogen[F[String]]
given [F[_]](using
Arbitrary[F[String]],
Cogen[F[Int]],
Cogen[F[String]]
): Arbitrary[TestAlgebra[F]] = Arbitrary(for
s <- Arbitrary.arbitrary[F[Int] => Int]
sa <- Arbitrary.arbitrary[Seq[F[Int]] => Int]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class autoProfunctorTests extends CatsTaglessTestSuite:

checkAll("Profunctor[TestAlgebra]", ProfunctorTests[TestAlgebra].profunctor[Long, String, Int, Long, String, Int])
checkAll("Serializable Profunctor[TestAlgebra]", SerializableTests.serializable(Profunctor[TestAlgebra]))

test("extra type param correctly handled"):
val asStringAlg = AlgWithExtraTypeParamString.dimap((s: String) => s.length)(_ + 1)
assertEquals(asStringAlg.foo("base", "x2"), 9d)
Expand Down Expand Up @@ -78,13 +79,13 @@ object autoProfunctorTests:
list <- Arbitrary.arbitrary[List[A] => List[B]]
yield new TestAlgebra[A, B]:
override def abstractCovariant(str: String) = absCovariant(str)
override def concreteCovariant(str: String) = conCovariant.getOrElse(super.concreteCovariant(_))(str)
override def concreteCovariant(str: String) = conCovariant.getOrElse(super.concreteCovariant)(str)
override def abstractContravariant(a: A) = absContravariant(a)
override def concreteContravariant(a: A) = conContravariant.getOrElse(super.concreteContravariant(_))(a)
override def concreteContravariant(a: A) = conContravariant.getOrElse(super.concreteContravariant)(a)
override def abstractMixed(a: A) = absMixed(a)
override def concreteMixed(a: A) = conMixed.getOrElse(super.concreteMixed(_))(a)
override def concreteMixed(a: A) = conMixed.getOrElse(super.concreteMixed)(a)
override def abstractOther(str: String) = absOther(str)
override def concreteOther(str: String) = conOther.getOrElse(super.concreteOther(_))(str)
override def concreteOther(str: String) = conOther.getOrElse(super.concreteOther)(str)
override def withoutParams = noParams
override def fromList(as: List[A]) = list(as)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2019 cats-tagless maintainers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cats.tagless.tests

import cats.data.Validated
import cats.laws.discipline.arbitrary.*
import cats.laws.discipline.eq.*
import cats.laws.discipline.{SemigroupKTests, SerializableTests}
import cats.tagless.derived.*
import cats.{Comparison, Eq, SemigroupK}
import org.scalacheck.{Arbitrary, Cogen, Gen}

@experimental
class autoSemigroupKTests extends CatsTaglessTestSuite:
import autoSemigroupKTests.*

checkAll("SemigroupK[TestAlgebra]", SemigroupKTests[TestAlgebra].semigroupK[Int])
checkAll("SemigroupK[TestAlgebra]", SerializableTests.serializable(SemigroupK[TestAlgebra]))

object autoSemigroupKTests:
import TestInstances.*

trait TestAlgebra[T] derives SemigroupK:
def abstractEffect(a: String): Validated[Int, T]
def concreteEffect(a: String): Validated[Int, T] = abstractEffect(a + " concreteEffect")
def abstractOther(t: T): String
def concreteOther(a: String): String = a + " concreteOther"
def withoutParams: Comparison
def headOption(ts: List[T]): Option[T]

given [T: Arbitrary: Eq]: Eq[TestAlgebra[T]] =
Eq.by: algebra =>
(
algebra.abstractEffect,
algebra.concreteEffect,
algebra.abstractOther,
algebra.concreteOther,
algebra.withoutParams,
algebra.headOption
)

given [T: Arbitrary: Cogen]: Arbitrary[TestAlgebra[T]] =
Arbitrary(for
absEff <- Arbitrary.arbitrary[String => Validated[Int, T]]
conEff <- Arbitrary.arbitrary[Option[String => Validated[Int, T]]]
absOther <- Arbitrary.arbitrary[T => String]
conOther <- Arbitrary.arbitrary[Option[String => String]]
withoutParameters <- Gen.oneOf(Comparison.EqualTo, Comparison.GreaterThan, Comparison.LessThan)
hOpt <- Arbitrary.arbitrary[List[T] => Option[T]]
yield new TestAlgebra[T]:
override def abstractEffect(i: String) = absEff(i)
override def concreteEffect(a: String) = conEff.getOrElse(super.concreteEffect)(a)
override def abstractOther(t: T) = absOther(t)
override def concreteOther(a: String) = conOther.getOrElse(super.concreteOther)(a)
override def withoutParams = withoutParameters
override def headOption(ts: List[T]): Option[T] = hOpt(ts)
)
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ class autoSemigroupalKTests extends CatsTaglessTestSuite:
checkAll("SemigroupalK[CalculatorAlg]", SemigroupalKTests[CalculatorAlg].semigroupalK[Try, Option, List])
checkAll("SemigroupalK is Serializable", SerializableTests.serializable(SemigroupalK[SafeAlg]))

test("simple product") {
test("simple product"):
val prodInterpreter = Interpreters.tryInterpreter.productK(Interpreters.lazyInterpreter)
assertEquals(prodInterpreter.parseInt("3").first, Try(3))
assertEquals(prodInterpreter.parseInt("3").second.value, 3)
assertEquals(prodInterpreter.parseInt("sd").first.isSuccess, false)
assertEquals(prodInterpreter.divide(3f, 3f).second.value, 1f)
}

object autoSemigroupalKTests:

Expand Down

0 comments on commit 5d90802

Please sign in to comment.