From 53ad033f21a2a89fa125f5af2016160a713b426c Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Wed, 20 Sep 2023 08:09:35 +1000 Subject: [PATCH 1/9] POC for RuntimeConstraint --- .../github/iltotore/iron/RefinedTypeOps.scala | 24 ++++++++----------- .../iltotore/iron/RuntimeConstraint.scala | 8 +++++++ 2 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 main/src/io/github/iltotore/iron/RuntimeConstraint.scala diff --git a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala index 63f5b195..10fe32af 100644 --- a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala +++ b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala @@ -48,7 +48,7 @@ object RefinedTypeOps: */ type FinalType = T -trait RefinedTypeOpsImpl[A, C, T]: +trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): /** * Implicitly refine at compile-time the given value. * @@ -76,8 +76,8 @@ trait RefinedTypeOpsImpl[A, C, T]: * @return a [[Right]] containing this value as [[T]] or a [[Left]] containing the constraint message. * @see [[fromIronType]], [[option]], [[applyUnsafe]]. */ - inline def either(value: A)(using constraint: Constraint[A, C]): Either[String, T] = - Either.cond(constraint.test(value), value.asInstanceOf[T], constraint.message) + inline def either(value: A): Either[String, T] = + Either.cond(rtc.test(value), value.asInstanceOf[T], rtc.message) /** * Refine the given value at runtime, resulting in an [[Option]]. @@ -86,8 +86,8 @@ trait RefinedTypeOpsImpl[A, C, T]: * @return an Option containing this value as [[T]] or [[None]]. * @see [[fromIronType]], [[either]], [[applyUnsafe]]. */ - inline def option(value: A)(using constraint: Constraint[A, C]): Option[T] = - Option.when(constraint.test(value))(value.asInstanceOf[T]) + inline def option(value: A): Option[T] = + Option.when(rtc.test(value))(value.asInstanceOf[T]) /** * Refine the given value at runtime. @@ -97,8 +97,8 @@ trait RefinedTypeOpsImpl[A, C, T]: * @throws an [[IllegalArgumentException]] if the constraint is not satisfied. * @see [[fromIronType]], [[either]], [[option]]. */ - inline def applyUnsafe(value: A)(using Constraint[A, C]): T = - value.refine[C].asInstanceOf[T] + inline def applyUnsafe(value: A): T = + if rtc.test(value) then value.asInstanceOf[T] else throw new IllegalArgumentException(rtc.message) inline def unapply(value: T): Option[A :| C] = Some(value.asInstanceOf[A :| C]) @@ -108,12 +108,8 @@ trait RefinedTypeOpsImpl[A, C, T]: inline given [R]: TypeTest[T, R] = summonInline[TypeTest[A :| C, R]].asInstanceOf[TypeTest[T, R]] - inline given [L](using inline constraint: Constraint[A, C]): TypeTest[L, T] = - val test = summonInline[TypeTest[L, A]] - - new TypeTest: - override def unapply(value: L): Option[value.type & T] = - test.unapply(value).filter(constraint.test(_)).asInstanceOf + given [L](using test: TypeTest[L, A]): TypeTest[L, T] with + override def unapply(value: L): Option[value.type & T] = test.unapply(value).filter(rtc.test(_)).asInstanceOf extension (wrapper: T) - inline def value: IronType[A, C] = wrapper.asInstanceOf[IronType[A, C]] \ No newline at end of file + inline def value: IronType[A, C] = wrapper.asInstanceOf[IronType[A, C]] diff --git a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala new file mode 100644 index 00000000..20fe58b1 --- /dev/null +++ b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala @@ -0,0 +1,8 @@ +package io.github.iltotore.iron + +final class RuntimeConstraint[A, B](_test: A => Boolean, val message: String): + inline def test(value: A): Boolean = _test(value) + +object RuntimeConstraint: + inline given derived[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] = + new RuntimeConstraint[A, B](c.test(_), c.message) From fd7e9196186147ac5edaa29be32000f65eb0f3e4 Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Thu, 21 Sep 2023 22:15:20 +1000 Subject: [PATCH 2/9] Use RuntimeConstraint in zio/cats newtypes --- cats/src/io/github/iltotore/iron/cats.scala | 24 +++++++++---------- .../github/iltotore/iron/RefinedTypeOps.scala | 2 ++ .../iltotore/iron/RuntimeConstraint.scala | 2 +- zio/src/io/github/iltotore/iron/zio.scala | 17 ++++++------- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/cats/src/io/github/iltotore/iron/cats.scala b/cats/src/io/github/iltotore/iron/cats.scala index 5f0be0ee..65ae23ed 100644 --- a/cats/src/io/github/iltotore/iron/cats.scala +++ b/cats/src/io/github/iltotore/iron/cats.scala @@ -127,7 +127,7 @@ object cats extends IronCatsInstances: * @return a [[Right]] containing this value as [[IronType]] or a [[Left]] containing the constraint message. * @see [[either]], [[eitherNel]]. */ - inline def eitherNec(value: A)(using inline c: Constraint[A, C]): EitherNec[String, T] = value.refineNec[C].map(_.asInstanceOf[T]) + inline def eitherNec(value: A): EitherNec[String, T] = ops.either(value).toEitherNec /** * Refine the given value at runtime, resulting in an [[EitherNel]]. @@ -136,7 +136,7 @@ object cats extends IronCatsInstances: * @return a [[Right]] containing this value as [[IronType]] or a [[Left]] containing the constraint message. * @see [[either]], [[eitherNec]]. */ - inline def eitherNel(value: A)(using inline c: Constraint[A, C]): EitherNel[String, T] = value.refineNel[C].map(_.asInstanceOf[T]) + inline def eitherNel(value: A): EitherNel[String, T] = ops.either(value).toEitherNel /** * Refine the given value at runtime, resulting in a [[Validated]]. @@ -145,7 +145,7 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing the constraint message. * @see [[validatedNec]], [[validatedNel]]. */ - inline def validated(value: A)(using inline c: Constraint[A, C]): Validated[String, T] = value.refineValidated[C].map(_.asInstanceOf[T]) + inline def validated(value: A): Validated[String, T] = ops.either(value).toValidated /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNec]]. @@ -154,8 +154,7 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyChain]] of error messages. * @see [[validated]], [[validatedNel]]. */ - inline def validatedNec(value: A)(using inline c: Constraint[A, C]): ValidatedNec[String, T] = - value.refineValidatedNec[C].map(_.asInstanceOf[T]) + inline def validatedNec(value: A): ValidatedNec[String, T] = ops.either(value).toValidatedNec /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNel]]. @@ -164,15 +163,14 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyList]] of error messages. * @see [[validated]], [[validatedNec]]. */ - inline def validatedNel(value: A)(using inline c: Constraint[A, C]): ValidatedNel[String, T] = - value.refineValidatedNel[C].map(_.asInstanceOf[T]) + inline def validatedNel(value: A): ValidatedNel[String, T] = ops.either(value).toValidatedNel /** * Represent all Cats' typeclass instances for Iron. */ private trait IronCatsInstances extends IronCatsLowPriority, RefinedTypeOpsCats: - //The `NotGiven` implicit parameter is mandatory to avoid ambiguous implicit error when both Eq[A] and Hash[A]/PartialOrder[A] exist + // The `NotGiven` implicit parameter is mandatory to avoid ambiguous implicit error when both Eq[A] and Hash[A]/PartialOrder[A] exist inline given [A, C](using inline ev: Eq[A], notHashOrOrder: NotGiven[Hash[A] | PartialOrder[A]]): Eq[A :| C] = ev.asInstanceOf[Eq[A :| C]] inline given [A, C](using inline ev: PartialOrder[A], notOrder: NotGiven[Order[A]]): PartialOrder[A :| C] = ev.asInstanceOf[PartialOrder[A :| C]] @@ -226,14 +224,14 @@ private trait IronCatsLowPriority: private trait RefinedTypeOpsCats extends RefinedTypeOpsCatsLowPriority: - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Eq[mirror.IronType]): Eq[T] = ev.asInstanceOf[Eq[T]] + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Eq[mirror.IronType]): Eq[T] = ev.asInstanceOf[Eq[T]] - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Order[mirror.IronType]): Order[T] = ev.asInstanceOf[Order[T]] + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Order[mirror.IronType]): Order[T] = ev.asInstanceOf[Order[T]] - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Show[mirror.IronType]): Show[T] = ev.asInstanceOf[Show[T]] + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Show[mirror.IronType]): Show[T] = ev.asInstanceOf[Show[T]] - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: PartialOrder[mirror.IronType]): PartialOrder[T] = ev.asInstanceOf[PartialOrder[T]] + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: PartialOrder[mirror.IronType]): PartialOrder[T] = ev.asInstanceOf[PartialOrder[T]] private trait RefinedTypeOpsCatsLowPriority: - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]] \ No newline at end of file + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]] diff --git a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala index 10fe32af..21f2f277 100644 --- a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala +++ b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala @@ -49,6 +49,8 @@ object RefinedTypeOps: type FinalType = T trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): + inline given RuntimeConstraint[A, C] = rtc + /** * Implicitly refine at compile-time the given value. * diff --git a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala index 20fe58b1..2aafeb23 100644 --- a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala +++ b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala @@ -1,7 +1,7 @@ package io.github.iltotore.iron final class RuntimeConstraint[A, B](_test: A => Boolean, val message: String): - inline def test(value: A): Boolean = _test(value) + inline def test(inline value: A): Boolean = _test(value) object RuntimeConstraint: inline given derived[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] = diff --git a/zio/src/io/github/iltotore/iron/zio.scala b/zio/src/io/github/iltotore/iron/zio.scala index 9011650a..d6997d52 100644 --- a/zio/src/io/github/iltotore/iron/zio.scala +++ b/zio/src/io/github/iltotore/iron/zio.scala @@ -1,12 +1,11 @@ package io.github.iltotore.iron import _root_.zio.NonEmptyChunk -import _root_.zio.prelude.{Debug, Equal, Hash, Ord, PartialOrd, Validation} +import _root_.zio.prelude.* object zio extends RefinedTypeOpsZio: extension [A](value: A) - /** * Refine the given value applicatively at runtime, resulting in a [[Validation]]. * @@ -16,9 +15,7 @@ object zio extends RefinedTypeOpsZio: inline def refineValidation[C](using inline constraint: Constraint[A, C]): Validation[String, A :| C] = Validation.fromPredicateWith(constraint.message)(value.asInstanceOf[A :| C])(constraint.test(_)) - extension [A, C1](value: A :| C1) - /** * Refine the given value again applicatively at runtime, resulting in a [[Validation]]. * @@ -35,17 +32,17 @@ object zio extends RefinedTypeOpsZio: * @param constraint the constraint to test with the value to refine. * @return a [[Valid]] containing this value as [[T]] or an [[Validation.Failure]] containing a [[NonEmptyChunk]] of error messages. */ - inline def validation(value: A)(using inline constraint: Constraint[A, C]): Validation[String, T] = - value.refineValidation[C].map(_.asInstanceOf[T]) + inline def validation(value: A): Validation[String, T] = + Validation.fromEither(ops.either(value)) private trait RefinedTypeOpsZio extends RefinedTypeOpsZioLowPriority: - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Debug[mirror.IronType]): Debug[T] = ev.asInstanceOf[Debug[T]] + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Debug[mirror.IronType]): Debug[T] = ev.asInstanceOf[Debug[T]] - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Equal[mirror.IronType]): Equal[T] = ev.asInstanceOf[Equal[T]] + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Equal[mirror.IronType]): Equal[T] = ev.asInstanceOf[Equal[T]] - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Ord[mirror.IronType]): Ord[T] = ev.asInstanceOf[Ord[T]] + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Ord[mirror.IronType]): Ord[T] = ev.asInstanceOf[Ord[T]] private trait RefinedTypeOpsZioLowPriority: - inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]] \ No newline at end of file + inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]] From e2d9ca8c154352a64bf0507017ffbe130ee05dc9 Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Fri, 22 Sep 2023 10:37:07 +1000 Subject: [PATCH 3/9] Remove inlining where not necessary --- cats/src/io/github/iltotore/iron/cats.scala | 10 +++++----- .../src/io/github/iltotore/iron/RefinedTypeOps.scala | 12 ++++++------ .../io/github/iltotore/iron/RuntimeConstraint.scala | 11 +++++++---- zio/src/io/github/iltotore/iron/zio.scala | 4 ++-- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/cats/src/io/github/iltotore/iron/cats.scala b/cats/src/io/github/iltotore/iron/cats.scala index 65ae23ed..042a5ac2 100644 --- a/cats/src/io/github/iltotore/iron/cats.scala +++ b/cats/src/io/github/iltotore/iron/cats.scala @@ -127,7 +127,7 @@ object cats extends IronCatsInstances: * @return a [[Right]] containing this value as [[IronType]] or a [[Left]] containing the constraint message. * @see [[either]], [[eitherNel]]. */ - inline def eitherNec(value: A): EitherNec[String, T] = ops.either(value).toEitherNec + def eitherNec(value: A): EitherNec[String, T] = ops.either(value).toEitherNec /** * Refine the given value at runtime, resulting in an [[EitherNel]]. @@ -136,7 +136,7 @@ object cats extends IronCatsInstances: * @return a [[Right]] containing this value as [[IronType]] or a [[Left]] containing the constraint message. * @see [[either]], [[eitherNec]]. */ - inline def eitherNel(value: A): EitherNel[String, T] = ops.either(value).toEitherNel + def eitherNel(value: A): EitherNel[String, T] = ops.either(value).toEitherNel /** * Refine the given value at runtime, resulting in a [[Validated]]. @@ -145,7 +145,7 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing the constraint message. * @see [[validatedNec]], [[validatedNel]]. */ - inline def validated(value: A): Validated[String, T] = ops.either(value).toValidated + def validated(value: A): Validated[String, T] = ops.either(value).toValidated /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNec]]. @@ -154,7 +154,7 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyChain]] of error messages. * @see [[validated]], [[validatedNel]]. */ - inline def validatedNec(value: A): ValidatedNec[String, T] = ops.either(value).toValidatedNec + def validatedNec(value: A): ValidatedNec[String, T] = ops.either(value).toValidatedNec /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNel]]. @@ -163,7 +163,7 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyList]] of error messages. * @see [[validated]], [[validatedNec]]. */ - inline def validatedNel(value: A): ValidatedNel[String, T] = ops.either(value).toValidatedNel + def validatedNel(value: A): ValidatedNel[String, T] = ops.either(value).toValidatedNel /** * Represent all Cats' typeclass instances for Iron. diff --git a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala index 21f2f277..0b09bd4e 100644 --- a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala +++ b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala @@ -48,8 +48,8 @@ object RefinedTypeOps: */ type FinalType = T -trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): - inline given RuntimeConstraint[A, C] = rtc +trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A :| C]): + inline given RuntimeConstraint[T] = rtc.asInstanceOf[RuntimeConstraint[T]] /** * Implicitly refine at compile-time the given value. @@ -69,7 +69,7 @@ trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): * @return a constrained value, without performing constraint checks. * @see [[apply]], [[applyUnsafe]]. */ - inline def assume(value: A): T = value.assume[C].asInstanceOf[T] + inline def assume(value: A): T = value.asInstanceOf[T] /** * Refine the given value at runtime, resulting in an [[Either]]. @@ -78,7 +78,7 @@ trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): * @return a [[Right]] containing this value as [[T]] or a [[Left]] containing the constraint message. * @see [[fromIronType]], [[option]], [[applyUnsafe]]. */ - inline def either(value: A): Either[String, T] = + def either(value: A): Either[String, T] = Either.cond(rtc.test(value), value.asInstanceOf[T], rtc.message) /** @@ -88,7 +88,7 @@ trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): * @return an Option containing this value as [[T]] or [[None]]. * @see [[fromIronType]], [[either]], [[applyUnsafe]]. */ - inline def option(value: A): Option[T] = + def option(value: A): Option[T] = Option.when(rtc.test(value))(value.asInstanceOf[T]) /** @@ -102,7 +102,7 @@ trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): inline def applyUnsafe(value: A): T = if rtc.test(value) then value.asInstanceOf[T] else throw new IllegalArgumentException(rtc.message) - inline def unapply(value: T): Option[A :| C] = Some(value.asInstanceOf[A :| C]) + def unapply(value: T): Option[A :| C] = Some(value.asInstanceOf[A :| C]) inline given RefinedTypeOps.Mirror[T] with override type BaseType = A diff --git a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala index 2aafeb23..9dbb938f 100644 --- a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala +++ b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala @@ -1,8 +1,11 @@ package io.github.iltotore.iron -final class RuntimeConstraint[A, B](_test: A => Boolean, val message: String): - inline def test(inline value: A): Boolean = _test(value) +type RuntimeConstraint[T] = T match + case IronType[a, c] => RuntimeConstraint.Impl[a, c, T] object RuntimeConstraint: - inline given derived[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] = - new RuntimeConstraint[A, B](c.test(_), c.message) + final class Impl[A, C, T](_test: A => Boolean, val message: String): + inline def test(value: A): Boolean = _test(value) + + inline given derived[A, C](using inline c: Constraint[A, C]): RuntimeConstraint[A :| C] = + new Impl[A, C, A :| C](c.test(_), c.message) diff --git a/zio/src/io/github/iltotore/iron/zio.scala b/zio/src/io/github/iltotore/iron/zio.scala index d6997d52..03c1d783 100644 --- a/zio/src/io/github/iltotore/iron/zio.scala +++ b/zio/src/io/github/iltotore/iron/zio.scala @@ -1,7 +1,7 @@ package io.github.iltotore.iron import _root_.zio.NonEmptyChunk -import _root_.zio.prelude.* +import _root_.zio.prelude.{Debug, Equal, Hash, Ord, Validation} object zio extends RefinedTypeOpsZio: @@ -32,7 +32,7 @@ object zio extends RefinedTypeOpsZio: * @param constraint the constraint to test with the value to refine. * @return a [[Valid]] containing this value as [[T]] or an [[Validation.Failure]] containing a [[NonEmptyChunk]] of error messages. */ - inline def validation(value: A): Validation[String, T] = + def validation(value: A): Validation[String, T] = Validation.fromEither(ops.either(value)) private trait RefinedTypeOpsZio extends RefinedTypeOpsZioLowPriority: From e0e0bdd2a30d2ffaf05be4a55fb8c0bad9286145 Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Sun, 24 Sep 2023 19:48:48 +1000 Subject: [PATCH 4/9] PR comments --- cats/src/io/github/iltotore/iron/cats.scala | 9 ++++++--- .../io/github/iltotore/iron/RefinedTypeOps.scala | 5 +++-- .../github/iltotore/iron/RuntimeConstraint.scala | 15 +++++++++------ zio/src/io/github/iltotore/iron/zio.scala | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cats/src/io/github/iltotore/iron/cats.scala b/cats/src/io/github/iltotore/iron/cats.scala index 042a5ac2..6816f638 100644 --- a/cats/src/io/github/iltotore/iron/cats.scala +++ b/cats/src/io/github/iltotore/iron/cats.scala @@ -145,7 +145,8 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing the constraint message. * @see [[validatedNec]], [[validatedNel]]. */ - def validated(value: A): Validated[String, T] = ops.either(value).toValidated + def validated(value: A): Validated[String, T] = + if ops.rtc.test(value) then Validated.valid(value.asInstanceOf[T]) else Validated.invalid(ops.rtc.message) /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNec]]. @@ -154,7 +155,8 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyChain]] of error messages. * @see [[validated]], [[validatedNel]]. */ - def validatedNec(value: A): ValidatedNec[String, T] = ops.either(value).toValidatedNec + def validatedNec(value: A): ValidatedNec[String, T] = + if ops.rtc.test(value) then Validated.validNec(value.asInstanceOf[T]) else Validated.invalidNec(ops.rtc.message) /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNel]]. @@ -163,7 +165,8 @@ object cats extends IronCatsInstances: * @return a [[Valid]] containing this value as [[IronType]] or an [[Invalid]] containing a [[NonEmptyList]] of error messages. * @see [[validated]], [[validatedNec]]. */ - def validatedNel(value: A): ValidatedNel[String, T] = ops.either(value).toValidatedNel + def validatedNel(value: A): ValidatedNel[String, T] = + if ops.rtc.test(value) then Validated.validNel(value.asInstanceOf[T]) else Validated.invalidNel(ops.rtc.message) /** * Represent all Cats' typeclass instances for Iron. diff --git a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala index 0b09bd4e..8326db0e 100644 --- a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala +++ b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala @@ -48,8 +48,9 @@ object RefinedTypeOps: */ type FinalType = T -trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A :| C]): - inline given RuntimeConstraint[T] = rtc.asInstanceOf[RuntimeConstraint[T]] + +trait RefinedTypeOpsImpl[A, C, T](using rtcAuto: RuntimeConstraint.AutoDerived[A, A :| C]): + given rtc: RuntimeConstraint[A, T] = rtcAuto.inner.asInstanceOf[RuntimeConstraint[A, T]] /** * Implicitly refine at compile-time the given value. diff --git a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala index 9dbb938f..35f6f087 100644 --- a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala +++ b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala @@ -1,11 +1,14 @@ package io.github.iltotore.iron -type RuntimeConstraint[T] = T match - case IronType[a, c] => RuntimeConstraint.Impl[a, c, T] +final class RuntimeConstraint[A, T](_test: A => Boolean, val message: String): + inline def test(value: A): Boolean = _test(value) object RuntimeConstraint: - final class Impl[A, C, T](_test: A => Boolean, val message: String): - inline def test(value: A): Boolean = _test(value) - inline given derived[A, C](using inline c: Constraint[A, C]): RuntimeConstraint[A :| C] = - new Impl[A, C, A :| C](c.test(_), c.message) + inline def derived[A, C](using inline c: Constraint[A, C]): RuntimeConstraint[A, A :| C] = + new RuntimeConstraint[A, A :| C](c.test(_), c.message) + + final class AutoDerived[A, T](val inner: RuntimeConstraint[A, T]) + object AutoDerived: + inline given derived[A, C](using inline c: Constraint[A, C]): AutoDerived[A, A :| C] = + new AutoDerived[A, A :| C](RuntimeConstraint.derived[A, C]) diff --git a/zio/src/io/github/iltotore/iron/zio.scala b/zio/src/io/github/iltotore/iron/zio.scala index 03c1d783..4e0e1d1a 100644 --- a/zio/src/io/github/iltotore/iron/zio.scala +++ b/zio/src/io/github/iltotore/iron/zio.scala @@ -33,7 +33,7 @@ object zio extends RefinedTypeOpsZio: * @return a [[Valid]] containing this value as [[T]] or an [[Validation.Failure]] containing a [[NonEmptyChunk]] of error messages. */ def validation(value: A): Validation[String, T] = - Validation.fromEither(ops.either(value)) + Validation.fromPredicateWith(ops.rtc.message)(value)(ops.rtc.test(_)).asInstanceOf[Validation[String, T]] private trait RefinedTypeOpsZio extends RefinedTypeOpsZioLowPriority: From ea590ff5b5776e914c1d157d3f387abbc2b56acc Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Sun, 24 Sep 2023 20:08:32 +1000 Subject: [PATCH 5/9] Avoid auto-deriving when implicit exists --- main/src/io/github/iltotore/iron/RuntimeConstraint.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala index 35f6f087..1c5cde8a 100644 --- a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala +++ b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala @@ -1,5 +1,7 @@ package io.github.iltotore.iron +import scala.util.NotGiven + final class RuntimeConstraint[A, T](_test: A => Boolean, val message: String): inline def test(value: A): Boolean = _test(value) @@ -10,5 +12,10 @@ object RuntimeConstraint: final class AutoDerived[A, T](val inner: RuntimeConstraint[A, T]) object AutoDerived: - inline given derived[A, C](using inline c: Constraint[A, C]): AutoDerived[A, A :| C] = + inline given [A, C](using rtc: RuntimeConstraint[A, A :| C]): AutoDerived[A, A :| C] = + new AutoDerived[A, A :| C](rtc) + + inline given [A, C](using inline c: Constraint[A, C], ng: NotGiven[RuntimeConstraint[A, A :| C]]): AutoDerived[A, A :| C] = new AutoDerived[A, A :| C](RuntimeConstraint.derived[A, C]) + +end RuntimeConstraint From 672834277b09ea20b6e167d77f908f5c82665440 Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Mon, 25 Sep 2023 08:26:05 +1000 Subject: [PATCH 6/9] PR comments --- cats/src/io/github/iltotore/iron/cats.scala | 6 +++--- .../github/iltotore/iron/RefinedTypeOps.scala | 8 ++------ .../iltotore/iron/RuntimeConstraint.scala | 17 +++-------------- zio/src/io/github/iltotore/iron/zio.scala | 2 +- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/cats/src/io/github/iltotore/iron/cats.scala b/cats/src/io/github/iltotore/iron/cats.scala index 6816f638..b26d2fa7 100644 --- a/cats/src/io/github/iltotore/iron/cats.scala +++ b/cats/src/io/github/iltotore/iron/cats.scala @@ -146,7 +146,7 @@ object cats extends IronCatsInstances: * @see [[validatedNec]], [[validatedNel]]. */ def validated(value: A): Validated[String, T] = - if ops.rtc.test(value) then Validated.valid(value.asInstanceOf[T]) else Validated.invalid(ops.rtc.message) + Validated.fromEither(ops.either(value)) /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNec]]. @@ -156,7 +156,7 @@ object cats extends IronCatsInstances: * @see [[validated]], [[validatedNel]]. */ def validatedNec(value: A): ValidatedNec[String, T] = - if ops.rtc.test(value) then Validated.validNec(value.asInstanceOf[T]) else Validated.invalidNec(ops.rtc.message) + Validated.fromEither(ops.either(value)).toValidatedNec /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNel]]. @@ -166,7 +166,7 @@ object cats extends IronCatsInstances: * @see [[validated]], [[validatedNec]]. */ def validatedNel(value: A): ValidatedNel[String, T] = - if ops.rtc.test(value) then Validated.validNel(value.asInstanceOf[T]) else Validated.invalidNel(ops.rtc.message) + Validated.fromEither(ops.either(value)).toValidatedNel /** * Represent all Cats' typeclass instances for Iron. diff --git a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala index 8326db0e..03691950 100644 --- a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala +++ b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala @@ -48,15 +48,13 @@ object RefinedTypeOps: */ type FinalType = T - -trait RefinedTypeOpsImpl[A, C, T](using rtcAuto: RuntimeConstraint.AutoDerived[A, A :| C]): - given rtc: RuntimeConstraint[A, T] = rtcAuto.inner.asInstanceOf[RuntimeConstraint[A, T]] +trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): + protected given RuntimeConstraint[A, C] = rtc /** * Implicitly refine at compile-time the given value. * * @param value the value to refine. - * @param constraint the implementation of `C` to check. * @tparam A the refined type. * @tparam C the constraint applied to the type. * @return the given value typed as [[IronType]] @@ -75,7 +73,6 @@ trait RefinedTypeOpsImpl[A, C, T](using rtcAuto: RuntimeConstraint.AutoDerived[A /** * Refine the given value at runtime, resulting in an [[Either]]. * - * @param constraint the constraint to test with the value to refine. * @return a [[Right]] containing this value as [[T]] or a [[Left]] containing the constraint message. * @see [[fromIronType]], [[option]], [[applyUnsafe]]. */ @@ -95,7 +92,6 @@ trait RefinedTypeOpsImpl[A, C, T](using rtcAuto: RuntimeConstraint.AutoDerived[A /** * Refine the given value at runtime. * - * @param constraint the constraint to test with the value to refine. * @return this value as [[T]]. * @throws an [[IllegalArgumentException]] if the constraint is not satisfied. * @see [[fromIronType]], [[either]], [[option]]. diff --git a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala index 1c5cde8a..430c1031 100644 --- a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala +++ b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala @@ -2,20 +2,9 @@ package io.github.iltotore.iron import scala.util.NotGiven -final class RuntimeConstraint[A, T](_test: A => Boolean, val message: String): +final class RuntimeConstraint[A, C](_test: A => Boolean, val message: String): inline def test(value: A): Boolean = _test(value) object RuntimeConstraint: - - inline def derived[A, C](using inline c: Constraint[A, C]): RuntimeConstraint[A, A :| C] = - new RuntimeConstraint[A, A :| C](c.test(_), c.message) - - final class AutoDerived[A, T](val inner: RuntimeConstraint[A, T]) - object AutoDerived: - inline given [A, C](using rtc: RuntimeConstraint[A, A :| C]): AutoDerived[A, A :| C] = - new AutoDerived[A, A :| C](rtc) - - inline given [A, C](using inline c: Constraint[A, C], ng: NotGiven[RuntimeConstraint[A, A :| C]]): AutoDerived[A, A :| C] = - new AutoDerived[A, A :| C](RuntimeConstraint.derived[A, C]) - -end RuntimeConstraint + inline given derived[A, C](using inline c: Constraint[A, C]): RuntimeConstraint[A, C] = + new RuntimeConstraint[A, C](c.test(_), c.message) diff --git a/zio/src/io/github/iltotore/iron/zio.scala b/zio/src/io/github/iltotore/iron/zio.scala index 4e0e1d1a..03c1d783 100644 --- a/zio/src/io/github/iltotore/iron/zio.scala +++ b/zio/src/io/github/iltotore/iron/zio.scala @@ -33,7 +33,7 @@ object zio extends RefinedTypeOpsZio: * @return a [[Valid]] containing this value as [[T]] or an [[Validation.Failure]] containing a [[NonEmptyChunk]] of error messages. */ def validation(value: A): Validation[String, T] = - Validation.fromPredicateWith(ops.rtc.message)(value)(ops.rtc.test(_)).asInstanceOf[Validation[String, T]] + Validation.fromEither(ops.either(value)) private trait RefinedTypeOpsZio extends RefinedTypeOpsZioLowPriority: From 76efae0afe97ebbaab091cad37b1dfde8f245cee Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Mon, 25 Sep 2023 08:32:28 +1000 Subject: [PATCH 7/9] Make `rtc` a val --- cats/src/io/github/iltotore/iron/cats.scala | 6 +++--- main/src/io/github/iltotore/iron/RefinedTypeOps.scala | 2 +- zio/src/io/github/iltotore/iron/zio.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cats/src/io/github/iltotore/iron/cats.scala b/cats/src/io/github/iltotore/iron/cats.scala index b26d2fa7..6816f638 100644 --- a/cats/src/io/github/iltotore/iron/cats.scala +++ b/cats/src/io/github/iltotore/iron/cats.scala @@ -146,7 +146,7 @@ object cats extends IronCatsInstances: * @see [[validatedNec]], [[validatedNel]]. */ def validated(value: A): Validated[String, T] = - Validated.fromEither(ops.either(value)) + if ops.rtc.test(value) then Validated.valid(value.asInstanceOf[T]) else Validated.invalid(ops.rtc.message) /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNec]]. @@ -156,7 +156,7 @@ object cats extends IronCatsInstances: * @see [[validated]], [[validatedNel]]. */ def validatedNec(value: A): ValidatedNec[String, T] = - Validated.fromEither(ops.either(value)).toValidatedNec + if ops.rtc.test(value) then Validated.validNec(value.asInstanceOf[T]) else Validated.invalidNec(ops.rtc.message) /** * Refine the given value applicatively at runtime, resulting in a [[ValidatedNel]]. @@ -166,7 +166,7 @@ object cats extends IronCatsInstances: * @see [[validated]], [[validatedNec]]. */ def validatedNel(value: A): ValidatedNel[String, T] = - Validated.fromEither(ops.either(value)).toValidatedNel + if ops.rtc.test(value) then Validated.validNel(value.asInstanceOf[T]) else Validated.invalidNel(ops.rtc.message) /** * Represent all Cats' typeclass instances for Iron. diff --git a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala index 03691950..a970439a 100644 --- a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala +++ b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala @@ -48,7 +48,7 @@ object RefinedTypeOps: */ type FinalType = T -trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]): +trait RefinedTypeOpsImpl[A, C, T](using val rtc: RuntimeConstraint[A, C]): protected given RuntimeConstraint[A, C] = rtc /** diff --git a/zio/src/io/github/iltotore/iron/zio.scala b/zio/src/io/github/iltotore/iron/zio.scala index 03c1d783..4e0e1d1a 100644 --- a/zio/src/io/github/iltotore/iron/zio.scala +++ b/zio/src/io/github/iltotore/iron/zio.scala @@ -33,7 +33,7 @@ object zio extends RefinedTypeOpsZio: * @return a [[Valid]] containing this value as [[T]] or an [[Validation.Failure]] containing a [[NonEmptyChunk]] of error messages. */ def validation(value: A): Validation[String, T] = - Validation.fromEither(ops.either(value)) + Validation.fromPredicateWith(ops.rtc.message)(value)(ops.rtc.test(_)).asInstanceOf[Validation[String, T]] private trait RefinedTypeOpsZio extends RefinedTypeOpsZioLowPriority: From b5d4ba9454b29e3b34eb48d7b254239d3ab97e4a Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Mon, 25 Sep 2023 13:23:03 +1000 Subject: [PATCH 8/9] Inline RuntimeConstraint given --- main/src/io/github/iltotore/iron/RefinedTypeOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala index a970439a..d5d401bf 100644 --- a/main/src/io/github/iltotore/iron/RefinedTypeOps.scala +++ b/main/src/io/github/iltotore/iron/RefinedTypeOps.scala @@ -49,7 +49,7 @@ object RefinedTypeOps: type FinalType = T trait RefinedTypeOpsImpl[A, C, T](using val rtc: RuntimeConstraint[A, C]): - protected given RuntimeConstraint[A, C] = rtc + inline protected given RuntimeConstraint[A, C] = rtc /** * Implicitly refine at compile-time the given value. From 1538ffac6e6e0a0416e27de286ee76d3d0547783 Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Thu, 28 Sep 2023 17:57:36 +1000 Subject: [PATCH 9/9] Add scaladoc for RuntimeConstraint --- .../github/iltotore/iron/RuntimeConstraint.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala index 430c1031..3236dc60 100644 --- a/main/src/io/github/iltotore/iron/RuntimeConstraint.scala +++ b/main/src/io/github/iltotore/iron/RuntimeConstraint.scala @@ -2,6 +2,20 @@ package io.github.iltotore.iron import scala.util.NotGiven +/** + * A [[RuntimeConstraint]] is similar to a [[Constraint]] with the difference that it can be used + * in non-inlined methods. + * + * This allows refinement of values in polymorphic methods / givens without the use of `inline`. + * e.g., the code below would fail to compile if [[Constraint]] was used instead. + * + * {{{ + * def foo[A, C](value: A)(using c: RuntimeConstraint[A, C]): Either[String, A :| C] = + * if c.test(value) then Right(value.assume[C]) else Left(c.message) + * }}} + * + * In cases that one does not exist in scope, one will be automatically derived from a [[Constraint]]. + */ final class RuntimeConstraint[A, C](_test: A => Boolean, val message: String): inline def test(value: A): Boolean = _test(value)