# Constraint

In Iron, a constraint consist of a type, called "dummy" type, associated with a given instance of Constraint.

See Refinement for usage.

## Operations

Usually, you can make your constraint out of existing ones. Iron provides several operators to help you to compose them.

### Union and intersection

Type union `C1 | C2` and intersection `C1 & C2` respectively act as a boolean OR/AND in Iron. For example, GreaterEqual is just a union of Greater and StrictEqual:

``````import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*

type GreaterEqual[V] = Greater[V] | StrictEqual[V]
``````
GreaterEqual.scala
``````import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*

type GreaterEqual[V] = Greater[V] | StrictEqual[V]

val x: Int :| GreaterEqual = 1 //OK
val y: Int :| GreaterEqual = -1 //Compile-time error: (Should be greater than 0 | Should strictly equal to 0)
``````

Same goes for intersection:

``````import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*

type GreaterEqual[V] = Greater[V] | StrictEqual[V]

type Between[Min, Max] = GreaterEqual[Min] & LessEqual[Max]
``````

### Other operations

Most constraint operators provided by Iron are "normal" constraints taking another constraint as parameter.

Here is a list of the most used operators:

• Not[C]: like a boolean "not". Negate the result of the `C` constraint.
• DescribedAs[C, V]: attach a custom description `V` to `C`.
• ForAll[C]: check if the `C` constraint passes for all elements of a collection/String
• Exists[C]: check if the `C` constraint passes for at least one element of a collection/String

## Dummy type

Usually, the dummy type is represented by a final class. Note that this class (or whatever entity you choose as a dummy) should not have constructor parameters.

``````final class Positive
``````
Positive.scala

The dummy type does nothing in itself. It is only used by the type system/implicit search to select the right Constraint.

## Constraint implementation

Each refined type `A :| C` need an implicit instance of `Constraint[A, C]` to be verified. For instance, `Int :| Positive` need a given instance of `Constraint[Int, Positive]`.

Here is how it looks:

``````final class Positive

import io.github.iltotore.iron.*

given Constraint[Int, Positive] with

override inline def test(value: Int): Boolean = value > 0

override inline def message: String = "Should be strictly positive"
``````
PositiveAndConstraint.scala

Note that you need to do this for each type. If your constraint supports multiple types (e.g numeric types), you can use a trait to reduce boilerplate:

``````final class Positive

import io.github.iltotore.iron.*

trait PositiveConstraint[A] extends Constraint[A, Positive]:
override inline def message: String = "Should be strictly positive"

given PositiveConstraint[Int] with
override inline def test(value: Int): Boolean = value > 0

given PositiveConstraint[Double] with
override inline def test(value: Double): Boolean = value > 0.0
``````

This constraint can now be used like any other:

``````final class Positive

import io.github.iltotore.iron.*

given Constraint[Int, Positive] with

override inline def test(value: Int): Boolean = value > 0

override inline def message: String = "Should be strictly positive"

val x: Int :| Positive = 1
val y: Int :| Positive = -1 //Compile-time error: Should be strictly positive
``````

## Constraint parameters

You can parameterize your constraints. Let's take the standard Greater constraint.

Constraint parameters are held by the dummy type as type parameters, not constructor parameters.

``````final class Greater[V]
``````
Greater.scala

Then, we can get the value of the passed type using `scala.compiletime.constValue`:

``````final class Greater[V]

import io.github.iltotore.iron.*
import scala.compiletime.constValue

given [V]: Constraint[Int, Greater[V]] with

override inline def test(value: Int): Boolean = value > constValue[V]

override inline def message: String = "Should be greater than " + stringValue[V]
``````
GreaterAndConstraint.scala

Note that we're using stringValue in the `message` method to get a fully inlined String value of the given type because `String#toString` is not inlined. This method is equivalent to `constValue[scala.compiletime.ops.any.ToString[V]]`.

Now testing the constraint:

``````final class Greater[V]

import io.github.iltotore.iron.*
import scala.compiletime.constValue

given [V]: Constraint[Int, Greater[V]] with

override inline def test(value: Int): Boolean = value > constValue[V]

override inline def message: String = "Should be greater than " + stringValue[V]

val x: Int :| Greater = 6
val y: Int :| Greater = 3 //Compile-time error: Should be greater than 5
``````

## Runtime proxy

Iron provides a proxy for `Constraint`, named `RuntimeConstraint`. It is used the same way as `Constraint`:

``````def refineRuntimeOption[A, C](value: A)(using constraint: RuntimeConstraint[A, C]): Option[A :| C] =
Option.when(constraint.test(value))(value.asInstanceOf[A :| C])

refineRuntimeOption[Int, Positive](5) //Some(5)
refineRuntimeOption[Int, Positive](-5) //None
``````

• It does not need the summoning method (here `refineOption`) to be `inline`
• It significantly lowers the generated bytecode and usually improves performances

Therefore, it is recommended to use `RuntimeConstraint` instead of `Constraint` when using the instance at runtime. For example, most of `RefinedTypeOps`'s (see New types) methods use a `RuntimeConstraint`.

It is also recommended to use `RuntimeConstraint` to derive typeclasses, especially when using a `given` with a function value.

``````trait FromString[A]:

def fromString(text: String): Either[String, A]

given [A, C](using constraint: RuntimeConstraint[A, C], instanceA: FromString[A]): FromString[A :| C] = text =>
instanceA
.fromString(text)
.filterOrElse(constraint.test(_), constraint.message)
.map(_.asInstanceOf[A :| C])
``````

Note that using a `Constraint` here (and having to our given instance `inline`) will produce a warning:

An inline given alias with a function value as right-hand side can significantly increase generated code size. You should either drop the `inline` or rewrite the given with an explicit `apply` method.

`RuntimeConstraint` is also useful when you need to reuse the same constraint. Here is an example from `RefinedTypeOps`:

``````trait RefinedTypeOps[A, C, T]:

inline def rtc: RuntimeConstraint[A, C] = ???

def option(value: A): Option[T] =
Option.when(rtc.test(value))(value.asInstanceOf[T])
``````