Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore Derivation Of Functional Abstractions #1079

Merged
merged 4 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package zio.prelude

import zio._
import zio.test._

object DerivationSpec extends ZIOSpecDefault {

final case class Person(name: String, age: Int)

sealed trait Color
Comment on lines +8 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come you don't need the derives Equal clauses? I though that you need to use them 🤔

https://docs.scala-lang.org/scala3/reference/contextual/derivation.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently not.


object Color {
case object Red extends Color
case object Green extends Color
case object Blue extends Color
}

def spec =
suite("DerivationSpec")(
suite("equal")(
test("case class") {
assertTrue(Person("Jane Doe", 42) === Person("Jane Doe", 42)) &&
assertTrue(Person("Jane Doe", 42) !== Person("John Doe", 42))
},
test("sealed trait") {
assertTrue(Color.Red === Color.Red) &&
assertTrue(Color.Red !== Color.Green)
}
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2023 John A. De Goes and the ZIO Contributors
*
* 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 zio.prelude

trait EqualVersionSpecific
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2023 John A. De Goes and the ZIO Contributors
*
* 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 zio.prelude

import scala.deriving.*
import scala.compiletime.{erasedValue, summonInline}

trait EqualVersionSpecific {

inline given derived[A](using mirror: Mirror.Of[A]): Equal[A] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inline given derived[A](using mirror: Mirror.Of[A]): Equal[A] =
inline def derived[A](using mirror: Mirror.Of[A]): Equal[A] =

Otherwise it will derive for all types with no control right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given is in the documentation of this feature and using def creates an infinite loop in the derivation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I've been alluding to in my comment above: #1079 (comment)

I also think that people would like to control it with derives Equal clauses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened an issue with the Scala 3 team regarding this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def creates an infinite loop in the derivation.

Erf that is ... weird. This code is equivalent to fully automatic derivation. With a def users would need to explicitly add the derives clause. I had many complaints with scanamo when instances were created implicitly.

I read the docs, the "recursively auto-derive" is an anti pattern imo, oh well I guess time will tell.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So also according to @bishabosha we shouldn't put the given on the derived method to let the derivations be driven by derives clauses.

scala/scala3#16916

using def creates an infinite loop in the derivation

Are you able replicate it? Maybe we should report that.

I'm trying my lack with changing the official documentation scala/scala3#16919 Let's see where this leads 😄

Copy link

@bishabosha bishabosha Feb 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given is in the documentation of this feature and using def creates an infinite loop in the derivation.

is the infinite loop with any type, or a specific example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The derivation for Color.

val instances = summonAll[mirror.MirroredElemTypes]
inline mirror match
case sum: Mirror.SumOf[A] => equalSum(sum, instances)
case product: Mirror.ProductOf[A] => equalProduct(product, instances)

private inline def summonAll[A <: Tuple]: List[Equal[_]] =
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Equal[t]] :: summonAll[ts]
}

private def equalSum[A](sum: Mirror.SumOf[A], elems: List[Equal[_]]): Equal[A] =
Equal.make { (left, right) =>
val leftOrdinal = sum.ordinal(left)
val rightOrdinal = sum.ordinal(right)
(leftOrdinal == rightOrdinal) && check(elems(leftOrdinal))(left, right)
}

private def equalProduct[A](product: Mirror.ProductOf[A], elems: List[Equal[_]]): Equal[A] =
Equal.make { (left, right) =>
iterator(left).zip(iterator(right)).zip(elems.iterator).forall {
case ((l, r), elem) => check(elem)(l, r)
}
}

private def check(equal: Equal[_])(left: Any, right: Any): Boolean =
equal.asInstanceOf[Equal[Any]].equal(left, right)

private def iterator[A](a: A) =
a.asInstanceOf[Product].productIterator
}
2 changes: 1 addition & 1 deletion core/shared/src/main/scala/zio/prelude/Equal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ trait Equal[-A] { self =>
def toScala[A1 <: A]: sm.Equiv[A1] = self.equal(_, _)
}

object Equal {
object Equal extends EqualVersionSpecific {

def fromScala[A](implicit equiv: sm.Equiv[A]): Equal[A] = equiv.equiv(_, _)

Expand Down
2 changes: 1 addition & 1 deletion project/BuildHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object BuildHelper {
val Scala211: String = "2.11.12"
val Scala212: String = "2.12.17"
val Scala213: String = "2.13.10"
val Scala3: String = "3.2.1"
val Scala3: String = "3.2.2"

val SilencerVersion = "1.7.12"

Expand Down