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

Add RuntimeConstraint #175

Merged
merged 9 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
24 changes: 11 additions & 13 deletions cats/src/io/github/iltotore/iron/cats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Iltotore marked this conversation as resolved.
Show resolved Hide resolved

/**
* Refine the given value at runtime, resulting in an [[EitherNel]].
Expand All @@ -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]].
Expand All @@ -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]].
Expand All @@ -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]].
Expand All @@ -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
Iltotore marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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]]
Expand Down Expand Up @@ -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]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]
26 changes: 12 additions & 14 deletions main/src/io/github/iltotore/iron/RefinedTypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ object RefinedTypeOps:
*/
type FinalType = T

trait RefinedTypeOpsImpl[A, C, T]:
trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]):
Iltotore marked this conversation as resolved.
Show resolved Hide resolved
inline given RuntimeConstraint[A, C] = rtc

/**
* Implicitly refine at compile-time the given value.
*
Expand Down Expand Up @@ -76,8 +78,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)
Iltotore marked this conversation as resolved.
Show resolved Hide resolved

/**
* Refine the given value at runtime, resulting in an [[Option]].
Expand All @@ -86,8 +88,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.
Expand All @@ -97,8 +99,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)
Iltotore marked this conversation as resolved.
Show resolved Hide resolved

inline def unapply(value: T): Option[A :| C] = Some(value.asInstanceOf[A :| C])

Expand All @@ -108,12 +110,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]]
inline def value: IronType[A, C] = wrapper.asInstanceOf[IronType[A, C]]
8 changes: 8 additions & 0 deletions main/src/io/github/iltotore/iron/RuntimeConstraint.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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)

object RuntimeConstraint:
inline given derived[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] =
new RuntimeConstraint[A, B](c.test(_), c.message)
17 changes: 7 additions & 10 deletions zio/src/io/github/iltotore/iron/zio.scala
Original file line number Diff line number Diff line change
@@ -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]].
*
Expand All @@ -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]].
*
Expand All @@ -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]]
inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]