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

Witness should provide the evidence of value.type =:= T #753

Open
Atry opened this issue Aug 23, 2017 · 7 comments
Open

Witness should provide the evidence of value.type =:= T #753

Atry opened this issue Aug 23, 2017 · 7 comments
Labels

Comments

@Atry
Copy link
Contributor

Atry commented Aug 23, 2017

Suppose we are creating a XML processing library, we want to ensure all the methods in a XML document only applies to elements that belong to the document.

We can define the document type like this:

trait Element[Owner <: Document with Singleton]

trait Document {
    def createElement(): Element[this.type]
    def appendChild(element: Element[this.type]): Unit
}

However, this approach is problematic when we summon the document for an element:

import shapeless._

def appendToDocument[Owner <: Document with Singleton](
    element: Element[Owner])(
    implicit witnessOwner: Witness.Aux[Owner]) = {
  witnessOwner.value.appendChild(element)
}
cmd2.sc:11: type mismatch;
 found   : Helper.this.Element[Owner]
 required: Helper.this.Element[shapeless.Witness.<refinement>.type]
Note: Owner >: shapeless.Witness.<refinement>.type, but trait Element is invariant in type Owner.
You may wish to define Owner as -Owner instead. (SLS 4.5)
    witnessOwner.value.appendChild(element)
                                   ^

The problem is that Scala compiler does not know Element[witnessOwner.value.type] and Element[Owner] are the same type.

This problem can be resolved if we add a value.type =:= T evidence in Witness, along with the ability of substitution in Scala 2.13.

@Atry
Copy link
Contributor Author

Atry commented Aug 23, 2017

Or we can define Witness as following:

trait Witness {
    type T = value.type
    val value: Any
}
object Witness {
      type Lt[+T0] = Witness {
          val value: T0
      }
      type Aux[T0] = Witness.Lt[T0] {
          type T = T0
      }
}

Then the evidence can be created as following:

def ev(w: Witness): w.value.type =:= w.T = {
    implicitly
}

@milessabin
Copy link
Owner

@Atry have you tried that alternative definition of Witness?

@milessabin
Copy link
Owner

As an aside, a more idiomatic approach to the motivating problem would be something along the lines of,

trait Document {
  trait Element
  def createElement(): Element
  def appendChild(element: Element): Unit
}

ie. use path dependent types to encode family polymorphism.

@Atry
Copy link
Contributor Author

Atry commented Aug 23, 2017

@milessabin At least the approach works for Dotty 😄

https://scastie.scala-lang.org/Atry/H5ltvYE9T9iD8IwRSdDESg/5

  trait Witness {
    type T = value.type
    val value: Any
  }
  object Witness {
    type Aux[T0] = Witness {
      type T = T0
      val value: T0
    }
  }

  trait Element[+Owner]

  trait Document {
    def createElement(): Element[this.type]
    def appendChild(element: Element[this.type]): Unit
  }

  def proveSameOwner(w: Witness) = {
    implicitly[Element[w.T] =:= Element[w.value.type]]
  }

  def appendToDocument[Owner <: Document with Singleton](
      element: Element[Owner])(
      implicit w: Witness.Aux[Owner]) = {
    val ev: Element[w.T] =:= Element[w.value.type] = proveSameOwner(w)
    w.value.appendChild(ev(element))
  }

@milessabin
Copy link
Owner

Could you try modifying the shapeless definition as you suggest and see what (if anything) breaks?

@Atry
Copy link
Contributor Author

Atry commented Aug 23, 2017

The new Witness encoding need some workaround for Scala 2:

https://scalafiddle.io/sf/c6YMHlX/0

trait Witness {
  type T = value.type
  val value: AnyRef
}

object Witness {
  type Aux[T0] = Witness {
    type T = T0
    val value: T0
  }
}

trait Element[+Owner]

trait Document {
    def createElement(): Element[this.type]
    def appendChild(element: Element[this.type]): Unit
}

trait Prover {
  val w: {
    type T
    val value: AnyRef
  }
  def proveSameOwner: Element[w.T] =:= Element[w.value.type]
}

trait WitnessProver extends Prover {
  val w: Witness
  def proveSameOwner = implicitly[Element[w.T] =:= Element[w.value.type]]
}

def appendToDocument[Owner <: Document with Singleton](
    element: Element[Owner])(
    implicit w0: Witness.Aux[Owner]) = {
  val prover: Prover { val w: Witness.Aux[Owner] } = new WitnessProver { val w = w0 }
  import prover._
  
  w.value.appendChild(prover.proveSameOwner(element))
}

@Atry
Copy link
Contributor Author

Atry commented Aug 23, 2017

The current Witness.Aux[Succ[P]] does not make sense, as Succ[P] is not a singleton type.
It should be Witness.Lt[Succ[P]].

@joroKr21 joroKr21 added this to the shapeless-2.5.0 milestone Mar 21, 2020
@joroKr21 joroKr21 removed this from the shapeless-2.5.0 milestone Sep 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants