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

Yet Another Lens Macro #3

Open
Odomontois opened this issue Oct 7, 2020 · 0 comments
Open

Yet Another Lens Macro #3

Odomontois opened this issue Oct 7, 2020 · 0 comments

Comments

@Odomontois
Copy link
Member

Odomontois commented Oct 7, 2020

I'd like to have more Contains macro generators, covering common use cases.
Few come to mind:

1. A sealed family form for GenContains

example:

sealed trait User{
    def id: String
}

case class Registered(id: String, name: String, email: String) extends User
case class Anonymous(id: String, ip: String) extends User

Here GenContains[User](_.id) must go through all the direct subtypes of User and verify that GenContains(_.id) resolves to lens. Example of generated tree:

val registeredC = GenContains[Registered](_.id)
val anonymousC  = GenContains[Anonymous](_.id)

new Contains[User, String] {
  def set(s: User, b: String) = s match {
    case r: Registered => registeredC.set(r, b)
    case a: Anonymous  => anonymousC.set(a, b)
  }

  def extract(s: User): String = s match {
    case r: Registered => registeredC.extract(r)
    case a: Anonymous  => anonymousC.extract(a)
  }
}

The proposed solution should work even for the case when path is longer that one field, or one of the direct subtypes is a sealed family itself

2. Typeclass based sealed Contains derivation

Ability to derive Contains when each alternative has implicit Contains to the given type.
In the best form it should support recursion and GADTs

 
case class Path(str: String)

sealed trait ValidatedField[A] {
  def check[B](f: A => Either[String, B]): ValidatedField[B] = Check(this, f)
}

@ClassyOptics
case class Read(path: Path) extends ValidatedField[String]

@Optics
case class Check[A, B](inner: ValidatedField[A], verify: A => Either[String, B]) extends ValidatedField[B]

implicit def checkPath[A, B]: Contains[Check[A, B], Path] = Check.inner >> validatedPath

implicit def validatedPath[A]: Contains[ValidatedField[A], Path] = DeriveContains[ValidatedField[A], Path]

latter should be expanded to

implicit def validatedPath[A]: Contains[ValidatedField[A], Path] = {
  new Contains[ValidatedField[A], Path] {
    def set(s: ValidatedField[A], b: Path): ValidatedField[A] = s match {
      case r: Read        => Contains[Read, Path].set(r, b)
      case c: Check[x, A] => Contains[Check[x, A], Path].set(c, b)
    }

    def extract(s: ValidatedField[A]): Path = s match {
      case r: Read        => Contains[Read, Path].extract(r)
      case c: Check[x, A] => Contains[Check[x, A], Path].extract(c)
    }
  }
}

3. Typeclass based case class to case class derivation

When two case classes have fields related to each other, sometimes we can derive Contains for them. Such case could be a partial replacement for complex data transformation libraries such as https://github.com/scalalandio/chimney

We will derive Contains[A, B] when :

  1. Each field in B has a field with the same name in the A
  2. For any two fields with the same name of types AF and BF we have AF =:= BF or Contains[AF, BF]
    Latter could be relaxed providing implicit `Contains[A, A] during derivation
case class RichName(name: String, searchId: Long)
object RichName {
  implicit val nameString: Contains[RichName, String] = GenContains.apply(_.name)
}

case class UserData(
    firstName: String,
    lastName: String,
    age: Int
)

case class UserInfo(
    id: Long,
    firstName: RichName,
    lastName: RichName,
    age: Int,
    lastUpdated: Instant,
)

val infoData = DeriveContains[UserInfo, UserData]

last line should be expanded to the following

val infoData = new Contains[UserInfo, UserData] {
  implicit def sameConv[A]: A Contains A = Same.id

  def set(s: UserInfo, b: UserData): UserInfo =
    s.copy(
      firstName = Contains[RichName, String].set(s.firstName, b.firstName),
      lastName = Contains[RichName, String].set(s.lastName, b.lastName),
      age = Contains[Int, Int].set(s.age, b.age),
    )

  def extract(s: UserInfo): UserData = UserData(
    firstName = Contains[RichName, String].extract(s.firstName),
    lastName = Contains[RichName, String].extract(s.lastName),
    age = Contains[Int, Int].extract(s.age)
  )
}
@danslapman danslapman transferred this issue from tofu-tf/tofu Aug 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant