Skip to content

Yet Another Lens Macro #3

@Odomontois

Description

@Odomontois

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)
  )
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions