Skip to content

Commit

Permalink
Added subtypes for Inference
Browse files Browse the repository at this point in the history
Fixes fthomas#454

Here I've tried to have Inference as a trait and added 2 subtypes
- InferAlways
- InferWhen

With that we can directly have a safe conversion whenever an InferAlways is presen
  • Loading branch information
umbreak committed Mar 29, 2018
1 parent 0f9d287 commit a6b353d
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 45 deletions.
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]
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

0 comments on commit a6b353d

Please sign in to comment.