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

Added subtypes for Inference #464

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,57 @@ package eu.timepit.refined.api
* Evidence that states if the conclusion `C` can be inferred from the
* premise `P` or not.
*
* This type class is used to implement refinement subtyping. If a valid
* `Inference[P, C]` exists, the type `F[T, P]` is considered a subtype
* This trait is used to implement refinement subtyping.
*/
trait Inference[P, C] {
type T[P2, C2] <: Inference[P2, C2]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of T? I see that it is used in adapt but can't we just use Inference there? Subtypes should be able to override adapt with a more concrete type (eg. override def adapt[P2, C2](adaptedShow: String): InferAlways[P2, C2] = ...).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept it there in case we wanted to use Dependent types for something like; def something[C,P](f: Inference[C,P]): f.T[C,P] = f...
However in the end I didn't find a way to use it for the implicit conversion. So if that's unnecessary any longer I can do it as suggested

def isValid: Boolean
final def notValid: Boolean = !isValid
def show: String
def adapt[P2, C2](adaptedShow: String): T[P2, C2]

}

/**
* This type class is used to implement compile-time refinement subtyping. If a
* `InferAlways[P, C]` exists, the type `F[T, P]` is considered a subtype
* of `F[T, C]`.
*/
case class Inference[P, C](isValid: Boolean, show: String) {
final case class InferAlways[P, C] private (show: String) extends Inference[P, C] {

final def adapt[P2, C2](adaptedShow: String): Inference[P2, C2] =
copy(show = adaptedShow.format(show))
type T[P2, C2] = InferAlways[P2, C2]
override def isValid: Boolean = true
override def adapt[P2, C2](adaptedShow: String): T[P2, C2] = copy(show = adaptedShow.format(show))
}

final def notValid: Boolean =
!isValid
/**
* This type class is used to define a runtime refinement subtyping. If an inference
* `InferWhen[P, C]` exists, and ''isValid'' is true, the type `F[T, P]` is considered a subtype of `F[T, C]`.
*/
final case class InferWhen[P, C] private (isValid: Boolean, show: String) extends Inference[P, C] {

type T[P2, C2] = InferWhen[P2, C2]
override def adapt[P2, C2](adaptedShow: String): T[P2, C2] = copy(show = adaptedShow.format(show))
}

object Inference {

type ==>[P, C] = Inference[P, C]
type ==>[P, C] = InferAlways[P, C]
type ?=>[P, C] = InferWhen[P, C]

def apply[P, C](implicit i: Inference[P, C]): Inference[P, C] = i

def alwaysValid[P, C](show: String): Inference[P, C] =
Inference(isValid = true, show)
def alwaysValid[P, C](show: String): P ==> C =
InferAlways(show)

def apply[P, C](isValid: Boolean, show: String): P ?=> C =
InferWhen(isValid, show)

def combine[P1, P2, P, C1, C2, C](i1: P1 ==> C1, i2: P2 ==> C2, show: String): P ==> C =
alwaysValid(show)

def combine[P1, P2, P, C1, C2, C](i1: Inference[P1, C1],
i2: Inference[P2, C2],
show: String): Inference[P, C] =
show: String): P ?=> C =
Inference(i1.isValid && i2.isValid, show.format(i1.show, i2.show))
}
14 changes: 12 additions & 2 deletions modules/core/shared/src/main/scala/eu/timepit/refined/auto.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined

import eu.timepit.refined.api.{Refined, RefType, Validate}
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.api.Inference.{==>, ?=>}
import eu.timepit.refined.macros.{InferMacro, RefineMacro}
import shapeless.tag.@@

Expand All @@ -11,6 +11,16 @@ import shapeless.tag.@@
*/
object auto {

/**
* Implicitly converts (at compile-time) a value of type `F[T, A]` to
* `F[T, B]`.
*
*/
implicit def autoInferAlways[F[_, _], T, A, B](ta: F[T, A])(
implicit rt: RefType[F],
ir: A ==> B
): F[T, B] = macro InferMacro.always[F, T, A, B]

/**
* Implicitly converts (at compile-time) a value of type `F[T, A]` to
* `F[T, B]` if there is a valid inference `A ==> B`. If the
Expand All @@ -29,7 +39,7 @@ object auto {
*/
implicit def autoInfer[F[_, _], T, A, B](ta: F[T, A])(
implicit rt: RefType[F],
ir: A ==> B
ir: A ?=> B
): F[T, B] = macro InferMacro.impl[F, T, A, B]

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined

import eu.timepit.refined.api._
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.api.Inference.{==>, ?=>}
import eu.timepit.refined.boolean._
import eu.timepit.refined.internal.Resources
import shapeless.{::, HList, HNil}
Expand Down Expand Up @@ -261,10 +261,10 @@ private[refined] trait BooleanInference0 extends BooleanInference1 {
implicit def minimalTautology[A]: A ==> A =
Inference.alwaysValid("minimalTautology")

implicit def doubleNegationElimination[A, B](implicit p1: A ==> B): Not[Not[A]] ==> B =
implicit def doubleNegationEliminationAlways[A, B](implicit p1: A ==> B): Not[Not[A]] ==> B =
p1.adapt("doubleNegationElimination(%s)")

implicit def doubleNegationIntroduction[A, B](implicit p1: A ==> B): A ==> Not[Not[B]] =
implicit def doubleNegationIntroductionAlways[A, B](implicit p1: A ==> B): A ==> Not[Not[B]] =
p1.adapt("doubleNegationIntroduction(%s)")

implicit def conjunctionAssociativity[A, B, C]: ((A And B) And C) ==> (A And (B And C)) =
Expand All @@ -273,7 +273,7 @@ private[refined] trait BooleanInference0 extends BooleanInference1 {
implicit def conjunctionCommutativity[A, B]: (A And B) ==> (B And A) =
Inference.alwaysValid("conjunctionCommutativity")

implicit def conjunctionEliminationR[A, B, C](implicit p1: B ==> C): (A And B) ==> C =
implicit def conjunctionEliminationRAlways[A, B, C](implicit p1: B ==> C): (A And B) ==> C =
p1.adapt("conjunctionEliminationR(%s)")

implicit def disjunctionAssociativity[A, B, C]: ((A Or B) Or C) ==> (A Or (B Or C)) =
Expand Down Expand Up @@ -306,15 +306,39 @@ private[refined] trait BooleanInference0 extends BooleanInference1 {

private[refined] trait BooleanInference1 extends BooleanInference2 {

implicit def modusTollens[A, B](implicit p1: A ==> B): Not[B] ==> Not[A] =
implicit def modusTollensAlways[A, B](implicit p1: A ==> B): Not[B] ==> Not[A] =
p1.adapt("modusTollens(%s)")
}

private[refined] trait BooleanInference2 {
private[refined] trait BooleanInference2 extends BooleanInference3 {

implicit def conjunctionEliminationL[A, B, C](implicit p1: A ==> C): (A And B) ==> C =
implicit def conjunctionEliminationLAlways[A, B, C](implicit p1: A ==> C): (A And B) ==> C =
p1.adapt("conjunctionEliminationL(%s)")

implicit def hypotheticalSyllogism[A, B, C](implicit p1: A ==> B, p2: B ==> C): A ==> C =
implicit def hypotheticalSyllogismAlways[A, B, C](implicit p1: A ==> B, p2: B ==> C): A ==> C =
Inference.combine(p1, p2, "hypotheticalSyllogism(%s, %s)")
}

private[refined] trait BooleanInference3 extends BooleanInference4 {
implicit def doubleNegationElimination[A, B](implicit p1: A ?=> B): Not[Not[A]] ?=> B =
p1.adapt("doubleNegationElimination(%s)")

implicit def doubleNegationIntroduction[A, B](implicit p1: A ?=> B): A ?=> Not[Not[B]] =
p1.adapt("doubleNegationIntroduction(%s)")

implicit def conjunctionEliminationR[A, B, C](implicit p1: B ?=> C): (A And B) ?=> C =
p1.adapt("conjunctionEliminationR(%s)")
}

private[refined] trait BooleanInference4 {

implicit def conjunctionEliminationL[A, B, C](implicit p1: A ?=> C): (A And B) ?=> C =
p1.adapt("conjunctionEliminationL(%s)")

implicit def modusTollens[A, B](implicit p1: A ?=> B): Not[B] ?=> Not[A] =
p1.adapt("modusTollens(%s)")

implicit def hypotheticalSyllogism[A, B, C](implicit p1: Inference[A, B],
p2: Inference[B, C]): A ?=> C =
Inference.combine(p1, p2, "hypotheticalSyllogism(%s, %s)")
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined

import eu.timepit.refined.api.{Inference, Result, Validate}
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.api.Inference.{==>, ?=>}
import eu.timepit.refined.boolean.Not
import eu.timepit.refined.collection._
import eu.timepit.refined.generic.Equal
Expand Down Expand Up @@ -309,32 +309,50 @@ object collection extends CollectionInference {
}
}

private[refined] trait CollectionInference {
private[refined] trait CollectionInference extends CollectionInference1 {

implicit def existsInference[A, B](implicit p1: A ==> B): Exists[A] ==> Exists[B] =
implicit def existsInferenceAlways[A, B](implicit p1: A ==> B): Exists[A] ==> Exists[B] =
p1.adapt("existsInference(%s)")

implicit def existsNonEmptyInference[P]: Exists[P] ==> NonEmpty =
Inference.alwaysValid("existsNonEmptyInference")

implicit def headInference[A, B](implicit p1: A ==> B): Head[A] ==> Head[B] =
implicit def headInferenceAlways[A, B](implicit p1: A ==> B): Head[A] ==> Head[B] =
p1.adapt("headInference(%s)")

implicit def headExistsInference[P]: Head[P] ==> Exists[P] =
Inference.alwaysValid("headExistsInference")

implicit def indexInference[N, A, B](implicit p1: A ==> B): Index[N, A] ==> Index[N, B] =
implicit def indexInferenceAlways[N, A, B](implicit p1: A ==> B): Index[N, A] ==> Index[N, B] =
p1.adapt("indexInference(%s)")

implicit def indexExistsInference[N, P]: Index[N, P] ==> Exists[P] =
Inference.alwaysValid("indexExistsInference")

implicit def lastInference[A, B](implicit p1: A ==> B): Last[A] ==> Last[B] =
implicit def lastInferenceAlways[A, B](implicit p1: A ==> B): Last[A] ==> Last[B] =
p1.adapt("lastInference(%s)")

implicit def lastExistsInference[P]: Last[P] ==> Exists[P] =
Inference.alwaysValid("lastExistsInference")

implicit def sizeInference[A, B](implicit p1: A ==> B): Size[A] ==> Size[B] =
implicit def sizeInferenceAlways[A, B](implicit p1: A ==> B): Size[A] ==> Size[B] =
p1.adapt("sizeInference(%s)")
}

private[refined] trait CollectionInference1 {

implicit def existsInference[A, B](implicit p1: A ?=> B): Exists[A] ?=> Exists[B] =
p1.adapt("existsInference(%s)")

implicit def headInference[A, B](implicit p1: A ?=> B): Head[A] ?=> Head[B] =
p1.adapt("headInference(%s)")

implicit def indexInference[N, A, B](implicit p1: A ?=> B): Index[N, A] ?=> Index[N, B] =
p1.adapt("indexInference(%s)")

implicit def lastInference[A, B](implicit p1: A ?=> B): Last[A] ?=> Last[B] =
p1.adapt("lastInference(%s)")

implicit def sizeInference[A, B](implicit p1: A ?=> B): Size[A] ?=> Size[B] =
p1.adapt("sizeInference(%s)")
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined

import eu.timepit.refined.api.{Inference, Validate}
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.api.Inference.?=>
import eu.timepit.refined.generic._
import shapeless._
import shapeless.ops.coproduct.ToHList
Expand Down Expand Up @@ -108,14 +108,14 @@ private[refined] trait GenericInference {
implicit def equalValidateInferenceWit[T, U <: T, P](
implicit v: Validate[T, P],
wu: Witness.Aux[U]
): Equal[U] ==> P =
): Equal[U] ?=> P =
Inference(v.isValid(wu.value), s"equalValidateInferenceWit(${v.showExpr(wu.value)})")

implicit def equalValidateInferenceNat[T, N <: Nat, P](
implicit v: Validate[T, P],
nt: Numeric[T],
tn: ToInt[N]
): Equal[N] ==> P =
): Equal[N] ?=> P =
Inference(v.isValid(nt.fromInt(tn())),
s"equalValidateInferenceNat(${v.showExpr(nt.fromInt(tn()))})")
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package eu.timepit.refined.macros

import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.api.Inference.{==>, ?=>}
import eu.timepit.refined.api.RefType
import eu.timepit.refined.internal.Resources
import macrocompat.bundle
Expand All @@ -10,9 +10,15 @@ import scala.reflect.macros.blackbox
class InferMacro(val c: blackbox.Context) extends MacroUtils {
import c.universe._

def impl[F[_, _], T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](ta: c.Expr[F[T, A]])(
def always[F[_, _], T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](ta: c.Expr[F[T, A]])(
rt: c.Expr[RefType[F]],
ir: c.Expr[A ==> B]
): c.Expr[F[T, B]] =
refTypeInstance(rt).unsafeRewrapM(c)(ta)

def impl[F[_, _], T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](ta: c.Expr[F[T, A]])(
rt: c.Expr[RefType[F]],
ir: c.Expr[A ?=> B]
): c.Expr[F[T, B]] = {

val inference = eval(ir)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined

import eu.timepit.refined.api.{Inference, Validate}
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.api.Inference.?=>
import eu.timepit.refined.boolean.{And, Not}
import eu.timepit.refined.numeric._
import shapeless.{Nat, Witness}
Expand Down Expand Up @@ -160,45 +160,45 @@ private[refined] trait NumericInference {
implicit wa: Witness.Aux[A],
wb: Witness.Aux[B],
nc: Numeric[C]
): Less[A] ==> Less[B] =
): Less[A] ?=> Less[B] =
Inference(nc.lt(wa.value, wb.value), s"lessInferenceWit(${wa.value}, ${wb.value})")

implicit def greaterInferenceWit[C, A <: C, B <: C](
implicit wa: Witness.Aux[A],
wb: Witness.Aux[B],
nc: Numeric[C]
): Greater[A] ==> Greater[B] =
): Greater[A] ?=> Greater[B] =
Inference(nc.gt(wa.value, wb.value), s"greaterInferenceWit(${wa.value}, ${wb.value})")

implicit def lessInferenceNat[A <: Nat, B <: Nat](
implicit ta: ToInt[A],
tb: ToInt[B]
): Less[A] ==> Less[B] =
): Less[A] ?=> Less[B] =
Inference(ta() < tb(), s"lessInferenceNat(${ta()}, ${tb()})")

implicit def greaterInferenceNat[A <: Nat, B <: Nat](
implicit ta: ToInt[A],
tb: ToInt[B]
): Greater[A] ==> Greater[B] =
): Greater[A] ?=> Greater[B] =
Inference(ta() > tb(), s"greaterInferenceNat(${ta()}, ${tb()})")

implicit def lessInferenceWitNat[C, A <: C, B <: Nat](
implicit wa: Witness.Aux[A],
tb: ToInt[B],
nc: Numeric[C]
): Less[A] ==> Less[B] =
): Less[A] ?=> Less[B] =
Inference(nc.lt(wa.value, nc.fromInt(tb())), s"lessInferenceWitNat(${wa.value}, ${tb()})")

implicit def greaterInferenceWitNat[C, A <: C, B <: Nat](
implicit wa: Witness.Aux[A],
tb: ToInt[B],
nc: Numeric[C]
): Greater[A] ==> Greater[B] =
): Greater[A] ?=> Greater[B] =
Inference(nc.gt(wa.value, nc.fromInt(tb())), s"greaterInferenceWitNat(${wa.value}, ${tb()})")

implicit def greaterEqualInference[A]: Greater[A] ==> GreaterEqual[A] =
implicit def greaterEqualInference[A]: Greater[A] ?=> GreaterEqual[A] =
Inference(true, "greaterEqualInference")

implicit def lessEqualInference[A]: Less[A] ==> LessEqual[A] =
implicit def lessEqualInference[A]: Less[A] ?=> LessEqual[A] =
Inference(true, "lessEqualInference")
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.timepit.refined

import eu.timepit.refined.api.{Inference, Validate}
import eu.timepit.refined.api.Inference.==>
import eu.timepit.refined.api.Inference.?=>
import eu.timepit.refined.string._
import shapeless.Witness

Expand Down Expand Up @@ -217,12 +217,12 @@ private[refined] trait StringInference {
implicit def endsWithInference[A <: String, B <: String](
implicit wa: Witness.Aux[A],
wb: Witness.Aux[B]
): EndsWith[A] ==> EndsWith[B] =
): EndsWith[A] ?=> EndsWith[B] =
Inference(wa.value.endsWith(wb.value), s"endsWithInference(${wa.value}, ${wb.value})")

implicit def startsWithInference[A <: String, B <: String](
implicit wa: Witness.Aux[A],
wb: Witness.Aux[B]
): StartsWith[A] ==> StartsWith[B] =
): StartsWith[A] ?=> StartsWith[B] =
Inference(wa.value.startsWith(wb.value), s"startsWithInference(${wa.value}, ${wb.value})")
}
Loading