Skip to content

Commit

Permalink
docs: Add documentation about RuntimeConstraint (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
Iltotore authored Oct 30, 2023
1 parent 6224b94 commit 6e69410
Showing 1 changed file with 55 additions and 3 deletions.
58 changes: 55 additions & 3 deletions docs/_docs/reference/constraint.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: "Constraint"

# Constraint

In Iron, a constraint consist of a type, called "dummy" or "proxy" type, associated with a given
In Iron, a constraint consist of a type, called "dummy" type, associated with a given
instance of [[Constraint|io.github.iltotore.iron.Constraint]].

See [Refinement](refinement.md) for usage.
Expand Down Expand Up @@ -113,7 +113,7 @@ final class Greater[V]

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

```scala sc-name:GreaterAndConstraint.scala sc-compile-with:Greater.scala
```scala sc-name:GreaterAndConstraint.scala sc-compile-with:Greater.scala
//{
import io.github.iltotore.iron.*
//}
Expand All @@ -132,7 +132,59 @@ This method is equivalent to `constValue[scala.compiletime.ops.any.ToString[V]]`

Now testing the constraint:

```scala sc-compile-with:GreaterAndConstraint.scala
```scala sc-compile-with:GreaterAndConstraint.scala
val x: Int :| Greater[5] = 6
val y: Int :| Greater[5] = 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`:

```scala
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
```

with two advantages:
- 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](newtypes.md#runtime-refinement)) methods use a
`RuntimeConstraint`.

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

```scala
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`:

```scala
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])
```

0 comments on commit 6e69410

Please sign in to comment.