简体   繁体   中英

Scala typeclass for providing an instance either from derivation or from an existing implicit value

I'm getting started with generic programming in Scala and I'm trying to design a flexible buildable schema type (a generic description of ADTs) that can be translated into third party serialization (eg, circe, upickle) or schema (eg, tapir) type class instances. When building schemas for products and coproducts, it has to be able to resolve schemas for subcomponents.

The way I would like to achieve this is with a Provider type class, that will "provide" an instance of a given type either by resolving a Deriver or by resolving an existing implicit instance. By making the derivation provider a lower priority method, it should prefer instances to derivations.

Below is a version of my solution applied to a simpler case: generating a NamesFor[T] instance that contains the extracted field names of type T.

import shapeless._
import shapeless.labelled.FieldType

trait NamesFor[ T ] {
    type Names

    def names : Names
}

object NamesFor {
    type Aux[ T, Names0 ] = NamesFor[ T ] { type Names = Names0 }
}

trait Deriver[ From, To ] {
    def derive : To
}

object Deriver {
    implicit def deriveNameFromSymbol[ S <: Symbol ](
        implicit wit : Witness.Aux[ S ],
    ) : Deriver[ S, String ] =
        new Deriver[ S, String ] {
            override def derive : String = wit.value.name
        }

    implicit def deriveNameFromFieldType[ K <: Symbol, T ](
        implicit deriver : Deriver[ K, String ],
    ) : Deriver[ FieldType[ K, T ], String ] = new Deriver[ FieldType[ K, T ], String ] {
        override def derive : String = deriver.derive
    }

    // Deriving HList
    implicit val deriveFromHnil : Deriver[ HNil, HNil ] =
        new Deriver[ HNil, HNil ] {
            override def derive : HNil = HNil
        }

    implicit def deriveNameHListFromHList[ Head, Tail <: HList, Res <: HList ](
        implicit
        headDeriver : Lazy[ Deriver[ Head, String ] ],
        tailDeriver : Deriver[ Tail, Res ],
    ) : Deriver[ Head :: Tail, String :: Res ] = new Deriver[ Head :: Tail, String :: Res ] {
        override def derive : String :: Res = headDeriver.value.derive :: tailDeriver.derive
    }

    // Here's the deriver we're interested in
    implicit def deriveNamesFromLabelledGeneric[ T, R <: HList, Names0 <: HList ](
        implicit
        lGenEv : LabelledGeneric.Aux[ T, R ],
        rDer : Deriver[ R, Names0 ],
    ) : Deriver[ T, NamesFor[ T ] ] = new Deriver[ T, NamesFor[ T ] ] {
        override def derive : NamesFor.Aux[ T, Names0 ] = new NamesFor[ T ] {
            type Names = Names0

            override def names : Names = rDer.derive
        }
    }
}

trait Provider[ T ] {
    def provide : T
}

object Provider {
    implicit def provideInstance[ T ](
        implicit inst : T,
    ) : Provider[ T ] = new Provider[T] {
        override def provide : T = inst
    }
}

trait LowPriorityProvider {
    implicit def provideDerivation[ From, To ](
        implicit
        deriver : Deriver[ From, To ],
    ) : Provider[ To ] = new Provider[ To ] {
        override def provide : To = deriver.derive
    }
}

I can derive instances in the following two ways:

1: implicit provider of existing NamesFor[T] instance

case class Test( int : Int )

implicit val namesInst : NamesFor[ Test ] = new NamesFor[Test] {
    override type Names = String :: HNil

    override def names : String :: HNil = "INT_FIELD" :: HNil
}

val provider = implicitly[ Provider[ NamesFor[ Test ] ] ]

println( provider.provide.names ) // output: INT_FIELD :: HNil

2: implicit deriver of a NamesFor[T] from T

case class Test( int : Int )

val deriver = implicitly[ Deriver[ Test, NamesFor[ Test ] ] ]

println( deriver.derive.names ) // output: int :: HNil

but it fails when I try to resolve a provider of NamesFor[T] without an implicit NamesFor[T] instance in scope. That is, it fails to resolve an instance by way of provideDerivation :

case class Test( int : Int )

val provider = implicitly[ Provider[ NamesFor[ Test ] ] ]

// Err: could not find implicit value for parameter e: Provider[NamesFor[Test]]
println( provider.provide.names )

Any thoughts on how I could make this all work?

You seem to just forget to extend Provider from LowPriorityProvider

object Provider extends LowPriorityProvider {...}

Then

case class Test( int : Int )

val provider = implicitly[ Provider[ NamesFor[ Test ] ] ]

println( provider.provide.names )

compiles

https://scastie.scala-lang.org/DmytroMitin/S7LrOQocSSeMZHetCEifDA/1

Scala 2.13.8, Shapeless 2.3.9

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM