Skip to content

Commit c86cc07

Browse files
authored
Add 🌟 Scala 3 🌟 support (#5)
* Add Scala 3 to the build * Migrate Selector from shapeless-2 to Scala 3 * Migrate Replacer from shapeless-2 to Scala 3 * Migrate ForImpl * Fix UnzipDerivationSpec for Scala 3 * Apply DRY to ForImpl for Scala 2 sources * Add some notes for Replacer and Selector * Add rectification on the site about available Scala versions
1 parent 68b88a2 commit c86cc07

File tree

10 files changed

+139
-28
lines changed

10 files changed

+139
-28
lines changed

build.sbt

+24-11
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
1-
val commonScalacOptions =
2-
Seq(
3-
"-feature",
4-
"-deprecation",
5-
"-Xlint",
6-
"-Xfatal-warnings"
7-
)
8-
91
val Scala212 = "2.12.19"
102
val Scala213 = "2.13.14"
3+
val Scala3 = "3.3.3"
114

125
val commonSettings = Seq(
136
scalaVersion := Scala213,
14-
crossScalaVersions := Seq(Scala212, Scala213),
7+
crossScalaVersions := Seq(Scala212, Scala213, Scala3),
158
scalacOptions ++= {
9+
val commonScalacOptions =
10+
Seq(
11+
"-feature",
12+
"-deprecation",
13+
"-Xfatal-warnings"
14+
)
15+
val scala2Options =
16+
Seq(
17+
"-Xlint"
18+
)
19+
1620
scalaVersion.value match {
1721
case v if v.startsWith("2.12") =>
1822
Seq(
1923
"-Ypartial-unification",
2024
"-Ywarn-unused-import"
21-
) ++ commonScalacOptions
25+
) ++ commonScalacOptions ++ scala2Options
26+
case v if v.startsWith("2.13") =>
27+
commonScalacOptions ++ scala2Options
2228
case _ =>
2329
commonScalacOptions
2430
}
@@ -60,8 +66,15 @@ lazy val zipper = crossProject(JSPlatform, JVMPlatform).in(file("."))
6066
.settings(commonSettings)
6167
.settings(
6268
name := "zipper",
69+
libraryDependencies += {
70+
scalaVersion.value match {
71+
case v if v.startsWith("2") =>
72+
"com.chuusai" %%% "shapeless" % "2.3.10"
73+
case _ =>
74+
"org.typelevel" %% "shapeless3-deriving" % "3.4.1"
75+
}
76+
},
6377
libraryDependencies ++= Seq(
64-
"com.chuusai" %%% "shapeless" % "2.3.10",
6578
"org.scalatest" %%% "scalatest-flatspec" % "3.2.18" % Test,
6679
"org.scalatest" %%% "scalatest-shouldmatchers" % "3.2.18" % Test
6780
)

docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ the unchanged parts are shared:
7676

7777
### Usage
7878

79-
Include these lines in your `build.sbt`:
79+
`zipper` is available for Scala 2.12, 2.13 and 3.3+. Include these lines in your `build.sbt`:
8080

8181
```scala
8282
// for JVM

shared/src/main/scala-2.12/zipper/ForImpl.scala shared/src/main/scala-2.12/zipper/ForImplScalaVersionSpecific.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import shapeless.ops.hlist.{Selector, Replacer}
66
import scala.collection.generic.CanBuildFrom
77
import scala.language.higherKinds
88

9-
private[zipper] trait ForImpl {
9+
private[zipper] trait ForImplScalaVersionSpecific {
1010
class For[A, Coll[X] <: Seq[X]] {
1111
/** Derive an instance of `Unzip[A]` */
1212
def derive[L <: HList](

shared/src/main/scala-2.13/zipper/ForImpl.scala shared/src/main/scala-2.13/zipper/ForImplScalaVersionSpecific.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import shapeless.ops.hlist.{Replacer, Selector}
55

66
import scala.collection.Factory
77

8-
private[zipper] trait ForImpl {
8+
private[zipper] trait ForImplScalaVersionSpecific {
99
class For[A, Coll[X] <: Seq[X]] {
1010
/** Derive an instance of `Unzip[A]` */
1111
def derive[L <: HList](
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package zipper
2+
3+
import shapeless.{Generic, HList}
4+
import shapeless.ops.hlist.{Replacer, Selector}
5+
6+
private[zipper] trait ForImpl extends ForImplScalaVersionSpecific {
7+
implicit def `Unzip List-based`[A, L <: HList](
8+
implicit generic: Generic.Aux[A, L],
9+
select: Selector[L, List[A]],
10+
replace: Replacer.Aux[L, List[A], List[A], (List[A], L)]
11+
): Unzip[A] = new Unzip[A] {
12+
def unzip(node: A): List[A] = select(generic.to(node))
13+
14+
def zip(node: A, children: List[A]): A = generic.from(replace(generic.to(node), children)._2)
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package zipper
2+
3+
import contrib.shapeless3.{Replacer, Selector}
4+
import shapeless3.deriving.K0
5+
import shapeless3.deriving.K0.*
6+
7+
import scala.collection.Factory
8+
9+
private[zipper] trait ForImpl {
10+
given unzipListBased[A, L <: Tuple](using
11+
generic: K0.ProductGeneric[A] { type MirroredElemTypes = L },
12+
selector: Selector[L, List[A]],
13+
replacer: Replacer.Aux[L, List[A], List[A], (List[A], L)]
14+
): Unzip[A] with {
15+
def unzip(node: A): List[A] = selector(generic.toRepr(node))
16+
def zip(node: A, children: List[A]): A = {
17+
val repr = replacer(generic.toRepr(node), children)
18+
generic.fromRepr(repr._2)
19+
}
20+
}
21+
22+
class For[A, Coll[X] <: Seq[X]]:
23+
/** Derive an instance of `Unzip[A]` */
24+
inline given derive[L <: Tuple](using
25+
generic: K0.ProductGeneric[A] { type MirroredElemTypes = L },
26+
selector: Selector[L, Coll[A]],
27+
replacer: Replacer.Aux[L, Coll[A], Coll[A], (Coll[A], L)],
28+
factory: Factory[A, Coll[A]]
29+
): Unzip[A] with {
30+
def unzip(node: A): List[A] = selector(generic.toRepr(node)).toList
31+
def zip(node: A, children: List[A]): A = {
32+
val repr = replacer(generic.toRepr(node), children.to(factory))
33+
generic.fromRepr(repr._2)
34+
}
35+
}
36+
37+
object For:
38+
/**
39+
* @tparam A The type of the tree-like data structure
40+
* @tparam Coll The type of the collection used for recursion (e.g. Vector)
41+
*/
42+
def apply[A, Coll[X] <: Seq[X]]: For[A, Coll] = new For[A, Coll]
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package contrib.shapeless3
2+
3+
/**
4+
* This is ported from [[shapeless.ops.hlist.Replacer Replacer]] from shapeless-2.
5+
* At the moment of implementation, there is no direct support in shapeless-3.
6+
* We should give up on it once it arrives in the library.
7+
*/
8+
trait Replacer[L <: Tuple, U, V]:
9+
type Out <: Tuple
10+
def apply(t: L, v: V): Out
11+
12+
object Replacer:
13+
def apply[L <: Tuple, U, V](using r: Replacer[L, U, V]): Aux[L, U, V, r.Out] = r
14+
15+
type Aux[L <: Tuple, U, V, Out0] = Replacer[L, U, V] { type Out = Out0 }
16+
17+
given tupleReplacer1[T <: Tuple, U, V]: Aux[U *: T, U, V, (U, V *: T)] =
18+
new Replacer[U *: T, U, V] {
19+
type Out = (U, V *: T)
20+
21+
def apply(l: U *: T, v: V): Out = (l.head, v *: l.tail)
22+
}
23+
24+
given tupleReplacer2[H, T <: Tuple, U, V, OutT <: Tuple](using
25+
ut: Aux[T, U, V, (U, OutT)]): Aux[H *: T, U, V, (U, H *: OutT)] =
26+
new Replacer[H *: T, U, V] {
27+
type Out = (U, H *: OutT)
28+
29+
def apply(l: H *: T, v: V): Out = {
30+
val (u, outT) = ut(l.tail, v)
31+
(u, l.head *: outT)
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package contrib.shapeless3
2+
3+
/**
4+
* This is ported from [[shapeless.ops.hlist.Selector Selector]] from shapeless-2.
5+
* At the moment of implementation, there is no direct support in shapeless-3.
6+
* We should give up on it once it arrives in the library.
7+
*/
8+
trait Selector[L <: Tuple, U]:
9+
def apply(t: L): U
10+
11+
object Selector:
12+
given[H, T <: Tuple]: Selector[H *: T, H] with {
13+
def apply(t: H *: T): H = t.head
14+
}
15+
16+
given[H, T <: Tuple, U] (using s: Selector[T, U]): Selector[H *: T, U] with {
17+
def apply(t: H *: T): U = s (t.tail)
18+
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
11
package zipper
22

3-
import shapeless.{HList, Generic}
4-
import shapeless.ops.hlist.{Selector, Replacer}
5-
6-
private[zipper] trait GenericUnzipInstances extends ForImpl {
7-
implicit def `Unzip List-based`[A, L <: HList](
8-
implicit generic: Generic.Aux[A, L],
9-
select: Selector[L, List[A]],
10-
replace: Replacer.Aux[L, List[A], List[A], (List[A], L)]
11-
): Unzip[A] = new Unzip[A] {
12-
def unzip(node: A): List[A] = select(generic.to(node))
13-
def zip(node: A, children: List[A]): A = generic.from(replace(generic.to(node), children)._2)
14-
}
15-
}
3+
private[zipper] trait GenericUnzipInstances extends ForImpl

shared/src/test/scala/zipper/UnzipDerivationSpec.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class UnzipDerivationSpec extends AnyFlatSpec with Matchers {
1919
val before = Tree(1, Vector(Tree(2)))
2020
val after = Tree(1, Vector(Tree(2), Tree(3)))
2121

22-
implicit val unzip = Unzip.For[Tree, Vector].derive
22+
implicit val unzip: Unzip[Tree] = Unzip.For[Tree, Vector].derive
2323

2424
Zipper(before).moveDownRight.insertRight(Tree(3)).commit shouldEqual after
2525
}

0 commit comments

Comments
 (0)