From 6dbec8ed65e0f7b210d6f9395f8e40fddc1d923a Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 7 Jan 2026 11:13:24 -0500 Subject: [PATCH 1/3] Replace remaining implicit classes with extension methods --- .../org/typelevel/twiddles/Twiddles.scala | 81 +++++++++---------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala b/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala index 965ee7f..6b0e78a 100644 --- a/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala +++ b/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala @@ -31,18 +31,43 @@ package org.typelevel.twiddles import cats.{Invariant, InvariantSemigroupal} -import cats.syntax.all._ +import cats.syntax.all.* import scala.compiletime.summonInline -trait TwiddleSyntax[F[_]]: +object Twiddles: + + type Prepend[A, B] = B match + case Tuple => A *: B + case _ => A *: B *: EmptyTuple + + inline def prepend[F[x]: InvariantSemigroupal, G[x] <: F[x], A, B](fa: F[A], gb: G[B]): F[Prepend[A, B]] = + inline gb match + case gbT: G[bh *: bt] => + val gbT0: G[bh *: bt] = gbT // Huh? + val res = fa.product(gbT0).imap[A *: bh *: bt] { case (hd, tl) => hd *: tl } { case hd *: tl => (hd, tl) } + res.asInstanceOf[F[Prepend[A, B]]] + case _ => + val res = fa.product(gb).imap[A *: B *: EmptyTuple] { case (a, b) => a *: b *: EmptyTuple } { + case a *: b *: EmptyTuple => (a, b) + } + res.asInstanceOf[F[Prepend[A, B]]] + + sealed trait PrependOps[F[_]]: + extension [A](self: F[A])(using InvariantSemigroupal[F]) + inline def *:[B](that: F[B]): F[Prepend[A, B]] = + prepend(self, that) + + extension [A, G[x] >: F[x]: InvariantSemigroupal](self: G[A]) + inline def *:[B](that: F[B]): G[Prepend[A, B]] = + prepend(self, that) - implicit def toTwiddleOpCons[B <: Tuple](fb: F[B]): TwiddleOpCons[F, B] = new TwiddleOpCons( - fb - ) - implicit def toTwiddleOpTwo[B](fb: F[B]): TwiddleOpTwo[F, B] = new TwiddleOpTwo(fb) +trait TwiddleSyntax[F[_]]: + + given twiddlesPrependOps: Twiddles.PrependOps[F] with {} extension [A](fa: F[A]) + inline def to[B]: F[B] = // Note: defining these as context params results in inference issues // See https://github.com/typelevel/twiddles/issues/19 and fix @@ -51,54 +76,20 @@ trait TwiddleSyntax[F[_]]: val F = summonInline[Invariant[F]] F.imap(fa)(iso.to)(iso.from) - @deprecated("1.0", "No longer needed") - def toTwiddleOpTo[A](fa: F[A]): TwiddleOpTo[F, A] = new TwiddleOpTo(fa) - extension [A <: Tuple](fa: F[A]) inline def dropUnits(using Invariant[F]): F[DropUnits[A]] = fa.imap(DropUnits.drop(_))(DropUnits.insert(_)) + object syntax: extension [F[_], A](fa: F[A]) - @annotation.targetName("cons") - def *:[G[x] >: F[x], B <: Tuple](gb: G[B])(using InvariantSemigroupal[G]): G[A *: B] = - fa.product(gb).imap[A *: B] { case (hd, tl) => hd *: tl } { case hd *: tl => (hd, tl) } - - @annotation.targetName("pair") - @annotation.nowarn - def *:[G[x] >: F[x], B](gb: G[B])(using InvariantSemigroupal[G]): G[A *: B *: EmptyTuple] = - fa.product(gb).imap[A *: B *: EmptyTuple] { case (a, b) => a *: b *: EmptyTuple } { - case a *: b *: EmptyTuple => (a, b) - } + inline def *:[G[x] >: F[x], B](gb: G[B])(using InvariantSemigroupal[G]): G[A *: B *: EmptyTuple] = + Twiddles.prepend(fa, gb).asInstanceOf[G[A *: B *: EmptyTuple]] - def to[B](using iso: Iso[A, B], F: Invariant[F]): F[B] = fa.imap(iso.to)(iso.from) + inline def to[B](using iso: Iso[A, B], F: Invariant[F]): F[B] = + fa.imap(iso.to)(iso.from) extension [F[_], A <: Tuple](fa: F[A]) inline def dropUnits(using Invariant[F]): F[DropUnits[A]] = fa.imap(DropUnits.drop(_))(DropUnits.insert(_)) -final class TwiddleOpCons[F[_], B <: Tuple](private val self: F[B]) extends AnyVal: - // Workaround for https://github.com/typelevel/twiddles/pull/2 - @annotation.targetName("consFixedF") - def *:[A](fa: F[A])(using F: InvariantSemigroupal[F]): F[A *: B] = - *:[F, A](fa)(using F) - - def *:[G[x] >: F[x], A](ga: G[A])(using InvariantSemigroupal[G]): G[A *: B] = - ga.product(self).imap[A *: B] { case (hd, tl) => hd *: tl } { case hd *: tl => (hd, tl) } - -final class TwiddleOpTwo[F[_], B](private val self: F[B]) extends AnyVal: - // Workaround for https://github.com/typelevel/twiddles/pull/2 - @annotation.targetName("twoFixedF") - def *:[A]( - fa: F[A] - )(using F: InvariantSemigroupal[F]): F[A *: B *: EmptyTuple] = - *:[F, A](fa)(using F) - - def *:[G[x] >: F[x], A](ga: G[A])(using InvariantSemigroupal[G]): G[A *: B *: EmptyTuple] = - ga.product(self).imap[A *: B *: EmptyTuple] { case (a, b) => a *: b *: EmptyTuple } { - case a *: b *: EmptyTuple => (a, b) - } - -@deprecated("1.0", "No longer needed") -final class TwiddleOpTo[F[_], A](private val self: F[A]) extends AnyVal: - def to[B](implicit iso: Iso[A, B], F: Invariant[F]): F[B] = self.imap(iso.to)(iso.from) From 791a79e5dae4755bcc0e42a98c2fcc515eb8da4b Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 7 Jan 2026 11:17:07 -0500 Subject: [PATCH 2/3] Scalafmt --- .../org/typelevel/twiddles/Twiddles.scala | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala b/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala index 6b0e78a..aa37ded 100644 --- a/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala +++ b/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala @@ -38,18 +38,24 @@ object Twiddles: type Prepend[A, B] = B match case Tuple => A *: B - case _ => A *: B *: EmptyTuple + case _ => A *: B *: EmptyTuple - inline def prepend[F[x]: InvariantSemigroupal, G[x] <: F[x], A, B](fa: F[A], gb: G[B]): F[Prepend[A, B]] = + inline def prepend[F[x]: InvariantSemigroupal, G[x] <: F[x], A, B]( + fa: F[A], + gb: G[B] + ): F[Prepend[A, B]] = inline gb match case gbT: G[bh *: bt] => val gbT0: G[bh *: bt] = gbT // Huh? - val res = fa.product(gbT0).imap[A *: bh *: bt] { case (hd, tl) => hd *: tl } { case hd *: tl => (hd, tl) } + val res = fa.product(gbT0).imap[A *: bh *: bt] { case (hd, tl) => hd *: tl } { + case hd *: tl => (hd, tl) + } res.asInstanceOf[F[Prepend[A, B]]] case _ => - val res = fa.product(gb).imap[A *: B *: EmptyTuple] { case (a, b) => a *: b *: EmptyTuple } { - case a *: b *: EmptyTuple => (a, b) - } + val res = + fa.product(gb).imap[A *: B *: EmptyTuple] { case (a, b) => a *: b *: EmptyTuple } { + case a *: b *: EmptyTuple => (a, b) + } res.asInstanceOf[F[Prepend[A, B]]] sealed trait PrependOps[F[_]]: @@ -61,13 +67,12 @@ object Twiddles: inline def *:[B](that: F[B]): G[Prepend[A, B]] = prepend(self, that) - trait TwiddleSyntax[F[_]]: given twiddlesPrependOps: Twiddles.PrependOps[F] with {} extension [A](fa: F[A]) - + inline def to[B]: F[B] = // Note: defining these as context params results in inference issues // See https://github.com/typelevel/twiddles/issues/19 and fix @@ -80,10 +85,11 @@ trait TwiddleSyntax[F[_]]: inline def dropUnits(using Invariant[F]): F[DropUnits[A]] = fa.imap(DropUnits.drop(_))(DropUnits.insert(_)) - object syntax: extension [F[_], A](fa: F[A]) - inline def *:[G[x] >: F[x], B](gb: G[B])(using InvariantSemigroupal[G]): G[A *: B *: EmptyTuple] = + inline def *:[G[x] >: F[x], B](gb: G[B])(using + InvariantSemigroupal[G] + ): G[A *: B *: EmptyTuple] = Twiddles.prepend(fa, gb).asInstanceOf[G[A *: B *: EmptyTuple]] inline def to[B](using iso: Iso[A, B], F: Invariant[F]): F[B] = @@ -92,4 +98,3 @@ object syntax: extension [F[_], A <: Tuple](fa: F[A]) inline def dropUnits(using Invariant[F]): F[DropUnits[A]] = fa.imap(DropUnits.drop(_))(DropUnits.insert(_)) - From d2323de87513a596e7598d6b226b56abdf1191ea Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 7 Jan 2026 15:21:18 -0500 Subject: [PATCH 3/3] Remove unneeded imap Co-authored-by: Niklas Klein --- .../src/main/scala-3/org/typelevel/twiddles/Twiddles.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala b/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala index aa37ded..d734117 100644 --- a/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala +++ b/core/shared/src/main/scala-3/org/typelevel/twiddles/Twiddles.scala @@ -52,10 +52,7 @@ object Twiddles: } res.asInstanceOf[F[Prepend[A, B]]] case _ => - val res = - fa.product(gb).imap[A *: B *: EmptyTuple] { case (a, b) => a *: b *: EmptyTuple } { - case a *: b *: EmptyTuple => (a, b) - } + val res = fa.product(gb) res.asInstanceOf[F[Prepend[A, B]]] sealed trait PrependOps[F[_]]: