Dependent type constraints (?) on trait in Scala - scala

I have the following model:
sealed trait MyRequest {
type Response <: MyResponse
}
sealed trait MyResponse {
type Request <: MyRequest
}
case class SayHelloRequest(name: String) extends MyRequest {
override type Response = SayHelloResponse
}
case class SayHelloResponse(greeting: String) extends MyResponse {
override type Request= SayHelloRequest
}
...
Is there some way, at the type level, I can enforce that the request/response pair match together? So if SayHelloRequest has a response type SayHelloResponse then SayHelloResponse must have a request type of SayHelloRequest.
Something like:
MyRequest#Response#Request =:= MyRequest
MyResponse#Request#Response =:= MyResponse

Try F-bounds
sealed trait MyRequest { self =>
type This >: self.type <: MyRequest { type This = self.This }
type Response <: MyResponse { type Request = self.This }
}
sealed trait MyResponse { self =>
type This >: self.type <: MyResponse { type This = self.This }
type Request <: MyRequest { type Response = self.This }
}
case class SayHelloRequest(name: String) extends MyRequest {
override type This = SayHelloRequest
override type Response = SayHelloResponse
}
case class SayHelloResponse(greeting: String) extends MyResponse {
override type This = SayHelloResponse
override type Request = SayHelloRequest
}
implicitly[SayHelloRequest#Response#Request =:= SayHelloRequest] // compiles
implicitly[SayHelloResponse#Request#Response =:= SayHelloResponse] // compiles
implicitly[MyRequest#Response#Request <:< MyRequest#This] // compiles
implicitly[MyResponse#Request#Response <:< MyResponse#This] // compiles
or a type class (and F-bounds)
// type class
trait RequestResponse {
type Request <: MyRequest[Request]
type Response <: MyResponse[Response]
}
object RequestResponse {
type Req[Rq <: MyRequest[Rq]] = RequestResponse { type Request = Rq }
type Resp[Rsp <: MyResponse[Rsp]] = RequestResponse { type Response = Rsp }
type Aux[Rq <: MyRequest[Rq], Rsp <: MyResponse[Rsp]] =
RequestResponse { type Request = Rq; type Response = Rsp }
// materializers
def req[Rq <: MyRequest[Rq]](implicit
reqResp: RequestResponse.Req[Rq]
): RequestResponse.Aux[Rq, reqResp.Response] = reqResp
def resp[Rsp <: MyResponse[Rsp]](implicit
reqResp: RequestResponse.Resp[Rsp]
): RequestResponse.Aux[reqResp.Request, Rsp] = reqResp
}
sealed abstract class MyRequest[This <: MyRequest[This]](
implicit val reqResp: RequestResponse.Req[This]
) { self: This =>
type Response = reqResp.Response
}
sealed abstract class MyResponse[This <: MyResponse[This]](
implicit val reqResp: RequestResponse.Resp[This]
) { self: This =>
type Request = reqResp.Request
}
case class SayHelloRequest(name: String) extends MyRequest[SayHelloRequest]
case class SayHelloResponse(greeting: String) extends MyResponse[SayHelloResponse]
implicit val sayHelloRequestResponse: RequestResponse.Aux[SayHelloRequest, SayHelloResponse] = null
val rr = RequestResponse.req[SayHelloRequest]
implicitly[rr.Response =:= SayHelloResponse] // compiles
val rr1 = RequestResponse.resp[SayHelloResponse]
implicitly[rr1.Request =:= SayHelloRequest] // compiles
https://tpolecat.github.io/2015/04/29/f-bounds.html

Related

Can I use type annotations of class to make decision about result type of method of the class?

I have some with two inheritors.
trait MyTrait[T <: MyTrait[T]] {
this: T =>
}
class MyTraitImpl1 extends MyTrait[MyTraitImpl1]
class MyTraitImpl2 extends MyTrait[MyTraitImpl2]
These inheritors are mutually exclusive and can be derived from each other so there is another class typed with MyTrait.
class TypedClass[T <: MyTrait](value: T) {
def anotherValue[O <: MyTrait]: O
}
Can I do something to make method anotherValue return exactly MyTraitImpl1 if TypedClass is typed with MyTraitImpl2 and vice versa?
If you can modify MyTrait, MyTraitImpl1, MyTraitImpl2 you can add type member OtherType
trait MyTrait[T <: MyTrait[T]] {
this: T =>
type OtherType <: MyTrait[_]
}
class MyTraitImpl1 extends MyTrait[MyTraitImpl1] {
override type OtherType = MyTraitImpl2
}
class MyTraitImpl2 extends MyTrait[MyTraitImpl2] {
override type OtherType = MyTraitImpl1
}
class TypedClass[T <: MyTrait[T]](value: T) {
def anotherValue: T#OtherType = ???
}
// OR
// class TypedClass[T <: MyTrait[T]](val value: T) {
//
// def anotherValue: value.OtherType = ???
// }
new TypedClass[MyTraitImpl1](new MyTraitImpl1).anotherValue : MyTraitImpl2
new TypedClass[MyTraitImpl2](new MyTraitImpl2).anotherValue : MyTraitImpl1
// new TypedClass[MyTraitImpl1](new MyTraitImpl1).anotherValue : MyTraitImpl1 // doesn't compile
// new TypedClass[MyTraitImpl2](new MyTraitImpl2).anotherValue : MyTraitImpl2 // doesn't compile
If you can't modify MyTrait, MyTraitImpl1, MyTraitImpl2 you can create type class OtherType
trait MyTrait[T <: MyTrait[T]] {
this: T =>
}
class MyTraitImpl1 extends MyTrait[MyTraitImpl1]
class MyTraitImpl2 extends MyTrait[MyTraitImpl2]
trait OtherType[T <: MyTrait[T]] {
type Out <: MyTrait[_]
}
object OtherType {
type Aux[T <: MyTrait[T], Out0 <: MyTrait[_]] = OtherType[T] { type Out = Out0 }
def instance[T <: MyTrait[T], Out0 <: MyTrait[_]]: Aux[T, Out0] = new OtherType[T] { type Out = Out0 }
implicit val otherType1: Aux[MyTraitImpl1, MyTraitImpl2] = instance
implicit val otherType2: Aux[MyTraitImpl2, MyTraitImpl1] = instance
}
class TypedClass[T <: MyTrait[T]](value: T) {
def anotherValue(implicit otherType: OtherType[T]): otherType.Out = ???
}

F-bounded type generic vs abstract type

I have a trait and a case class:
trait Foo[T <: Foo[T]] {
def self: T
}
case class Bar() extends Foo[Bar] {
def self = this
}
I would like to create a type that can take case classes that extend Foo. If I create a trait using generics:
trait TT[Key <: Foo[Key]] {
def f(key: Key) = {
key
}
}
val tt = new TT[Foo[_]]{}
tt.f(Bar())
It works fine, but I don't know how to express the same thing using abstract types:
trait T {
type Key <: Foo[Key]
def f(key: Key) = {
key
}
}
val t = new T{}
t.f(Bar()) //doesn't compile
Why the second version doesn't work?
EDIT:
As #Jasper-M pointed out, the first version doesn't work either, here is the updated version that works:
import scala.language.existentials
trait Foo[+T <: Foo[T]] {
def self: T
}
object Foo {
type Any = Foo[T] forSome { type T <: Foo[T] }
}
val tt = new TT[Foo.Any]{}
tt.f(Bar())
You have to initialize the Key type to Bar.
scala> val t = new T { type Key = Bar }
t: T{type Key = Bar} = $anon$1#2c34c819
scala> t.f(Bar())
res0: t.Key = Bar()

Scala: implicit lookup of type class instance for path-dependent type

trait Encoder[From, To] {
def encode(x: From): To
}
object Encoder {
implicit val thingToString: Encoder[Thing, String] = new Encoder[Thing, String] {
def encode(x: Thing): String = x.toString
}
}
trait Config {
type Repr
}
class MyConfig extends Config { type Repr = String }
//class ConcreteConfig { type Repr = String }
class Api[T](val config: Config) {
def doSomething(value: T)(implicit encoder: Encoder[T, config.Repr]): Unit = {}
}
case class Thing(a: Int)
object Test extends App {
import Encoder._
val api = new Api[Thing](new MyConfig)
api.doSomething(Thing(42))
}
The call to api.doSomething fails to compile:
could not find implicit value for parameter encoder: Encoder[Thing,Test.api.config.Repr]
If I change the signature of class Api[T]'s constructor so that it takes a ConcreteConfig, then the compiler can tell that config.Repr == String and the implicit lookup succeeds. But this wouldn't really work for my use case.
Is there any other way to guide the implicit lookup? Am I losing type info because I'm missing a type refinement or something?
This can't be achieved since config.Repr is not a stable path. The compiler can't determine that config.Repr = String, even though the runtime value of config is of type MyConfig.
This is going to work only if you can provide concrete config instance as a type parameter in Api, which will tell compiler what is the exact type of Repr:
class Api[T, C <: Config](val config: C) {
def doSomething(value: T)(implicit encoder: Encoder[T, config.Repr]): Unit = {}
}
object Test extends App {
import Encoder._
val api = new Api[Thing, MyConfig](new MyConfig)
api.doSomething(Thing(42))
}
Infer types one step at a time. Also, I don't see any reason for using value-dependent types here (or, for that matter, anywhere; that's a different question though).
trait Encoder[From, To] {
def encode(x: From): To
}
object Encoder {
implicit val thingToString: Encoder[Thing, String] = new Encoder[Thing, String] {
def encode(x: Thing): String = x.toString
}
}
trait Config {
type Repr
}
class MyConfig extends Config { type Repr = String }
// class ConcreteConfig { type Repr = String }
case class Api[T, C <: Config](val config: C) {
def doSomething(value: T)(implicit encoder: Encoder[T, C#Repr]): Unit = ()
}
case class ApiFor[T]() {
def withConfig[C <: Config](config: C): Api[T,C] = Api(config)
}
case class Thing(a: Int)
object Test extends App {
import Encoder._
val api = ApiFor[Thing] withConfig new MyConfig
// compiles!
api.doSomething(Thing(42))
}

Introduce type constraint for compile time safety

If we have a type class with some ADT as follow
trait MyRule {
type T
class ResourceIdType[A](val value: A)
case class StringResourceIdType(override val value: String) extends ResourceIdType(value)
case class ListResourceIdType(override val value: List[Int]) extends ResourceIdType(value)
def resourceType(rtm: T) : Any
private foo(rtm: T ) = {
resourceType(rtm) match{
case StringResourceIdType(s: String) =>
case ListResourceIdType(l:List[Int]) =>
...
}
type class impls override def resourceType as follow
object FirstClient{
implicit val clientRule = new MyRule{
type T = SomeType
def resourceType(rtm: T) = StringResourceIdType(rtm.name) //assume SomeType has a name property of type String
}
}
object SecondClient{
implicit val clientRule2 = New MyRule{
type T = SomeType2
def resourceType(rtm: T) = ListResourceIdType(rtm.ids) //assume SomeType2 has a ids property of type List[Int]
}
}
Want to make def resourceType(rtm: T) : Any compile time type safe/checked by removing Any and replace with a valid type as T <: ResourceIdType. What would be the right way to address this?
You can add a type parameter in MyRule trait and refine it in each type classes:
trait MyRule {
type T
class ResourceIdType[A](val value: A)
case class StringResourceIdType(override val value: String) extends ResourceIdType(value)
case class ListResourceIdType(override val value: List[Int]) extends ResourceIdType(value)
type F <: ResourceIdType[_]
def resourceType(rtm: T) : F
private def foo(rtm: T): Unit = {
resourceType(rtm) match {
case StringResourceIdType(s: String) =>
case ListResourceIdType(l:List[Int]) =>
}
}
}
object FirstClient{
implicit val clientRule = new MyRule{
case class SomeType(name: String)
type T = SomeType
type F = StringResourceIdType
def resourceType(rtm: T) = StringResourceIdType(rtm.name) //assume SomeType has a name property of type String
}
}
object SecondClient{
implicit val clientRule2 = new MyRule{
case class SomeType2(ids: List[Int])
type T = SomeType2
type F = ListResourceIdType
def resourceType(rtm: T) = ListResourceIdType(rtm.ids) //assume SomeType2 has a ids property of type List[Int]
}
}
The following compiles. Please let me know if it addresses your problem.
package net
trait SomeType {
def name: String
}
trait SomeType2 {
def ids: List[Int]
}
// Introduce an Algebraic Data Type
class ResourceValue
case class StringValue(x: String) extends ResourceValue
case class ListIntsValue(x: List[Int]) extends ResourceValue
trait MyRule {
type T
sealed trait ResourceIdType {
val value: ResourceValue
}
case class StringResourceIdType(override val value: StringValue) extends ResourceIdType
case class ListResourceIdType(override val value: ListIntsValue) extends ResourceIdType
def resourceType(rtm: T): ResourceIdType
private def foo(rtm: T): ResourceValue = {
resourceType(rtm) match {
case StringResourceIdType(s # StringValue(_)) => s
case ListResourceIdType(l # ListIntsValue(_)) => l
}
}
}
object FirstClient{
implicit val clientRule = new MyRule {
type T = SomeType
override def resourceType(rtm: T): ResourceIdType =
StringResourceIdType(StringValue(rtm.name))
}
}
object SecondClient{
implicit val clientRule2 = new MyRule {
type T = SomeType2
override def resourceType(rtm: T): ResourceIdType =
ListResourceIdType(ListIntsValue(rtm.ids))
}
}

Define a type constraint with abstract type

I try to define a type constraint with abstract type. but unfortunately, it doesn't compile.
sealed trait MatchableValue {
type A
def value: A
def asSingleItemValue : ItemValue
}
sealed trait ItemValue {
type A
def value: A
}
case class StringValue(value: String) extends ItemValue {type A = String}
case class StringMatchableValue(value: String) extends MatchableValue{
type A = String
override def asSingleItemValue = StringValue(value)
}
Unfortunately, this one doesn't work
def asSingleItemValue[B <: ItemValue](implicit ev: A =:= B#A) : B
The aim of the type constraint is to be warned at compile-time of such an error :
case class IntValue(value: Int) extends ItemValue {type A = Int}
case class IntMatchableValue(value: Int) extends MatchableValue{
type A = Int
def asSingleItemValue = StringValue("error")
}
You can accomplish this with a type refinement (note the method's return type):
sealed trait MatchableValue { self =>
type A
def value: A
def asSingleItemValue: ItemValue { type A = self.A }
}
sealed trait ItemValue {
type A
def value: A
}
case class StringValue(value: String) extends ItemValue { type A = String }
case class IntValue(value: Int) extends ItemValue { type A = Int }
Now this compiles:
case class StringMatchableValue(value: String) extends MatchableValue {
type A = String
def asSingleItemValue = StringValue(value)
}
But this doesn't:
case class StringMatchableValue(value: String) extends MatchableValue {
type A = String
def asSingleItemValue = IntValue(1)
}
Which I believe is what you want.
It's also worth noting that the following is a common pattern when dealing with type refinements:
sealed trait MatchableValue { self =>
type A
def value: A
def asSingleItemValue: ItemValue.Aux[A]
}
sealed trait ItemValue {
type A
def value: A
}
object ItemValue {
type Aux[A0] = ItemValue { type A = A0 }
}
This does exactly the same thing, but the syntax is a nice alternative if you find yourself needing to write out the type refinement a lot.
I think you meant to delete this line in your code?
def asSingleItemValue = StringValue(value)
and the override definition has some of the signature missing and should be like this:
override def asSingleItemValue[B <: ItemValue](implicit ev: =:=[String, B#A]): B = ev
Finally, the resulting type of ev needs to be B.