diff --git a/docs/_docs/getting-started.md b/docs/_docs/getting-started.md index 92518944..5949fee7 100644 --- a/docs/_docs/getting-started.md +++ b/docs/_docs/getting-started.md @@ -22,8 +22,6 @@ ivy"io.github.iltotore::iron:version" ## Common imports -### Base - The following import is often used: ```scala @@ -53,42 +51,6 @@ import io.github.iltotore.iron.* import io.github.iltotore.iron.constraint.all.* ``` -### Companion object -`RefinedTypeOps` create convenient companion object. - -```scala -opaque type Temperature = Double :| Positive -object Temperature extends RefinedTypeOps[Temperature] -``` - -#### apply function for compile-time validation -```scala -val temperature = Temperature(100) //temperature is of Temperature type -``` -`Temperature(-100)` won't compile. - -#### fromIronType to wrap already refined values - -`fromIronType` helps to manage values which already were checked against constrains. - -Implication works here; hence, both `Double :| Greater[10]` and `Double :| Positive` could be `Temperature` -```scala -val x: Double :| Positive = 5.0 -val y: Double :| Greater[10] = 15.0 -val t1 = Temperature.fromIronType(x) -val t2 = Temperature.fromIronType(y) -``` - -#### Option/Either -`Temperature.either(-5.0)` and `Temperature.option(-5.0)` return `Either` and `Option`. - -#### applyUnsafe -`applyUnsafe` throws `IllegalArgumentException` exception if predicate fails. -`Temperature.applyUnsafe(-1)` is example of usage. - -#### assume -`Temperature.assume(x)` doesn't fail (and returns `Temperature`). No validations being performed. Consider it as unsafe cast. - ## Next steps You can find the list of all standard constraints in the [[constraint package summary|io.github.iltotore.iron.constraint]]. diff --git a/docs/_docs/reference/newtypes.md b/docs/_docs/reference/newtypes.md new file mode 100644 index 00000000..fc786651 --- /dev/null +++ b/docs/_docs/reference/newtypes.md @@ -0,0 +1,96 @@ +--- +title: "Creating New Types" +--- + +# Creating New Types + +You can create no-overhead new types like [scala-newtype](https://github.com/estatico/scala-newtype) in Scala 2 with Iron. + +## Opaque types + +The aliased type of an [opaque type](https://docs.scala-lang.org/scala3/book/types-opaque-types.html) is only known in its definition file. It is not considered like a type alias outside of it: + +```scala +opaque type Temperature = Double :| Positive +``` + +```scala +val x: Double :| Positive = 5 +val temperature: Temperature = x //Error: Temperature expected, got Double :| Positive +``` + +Such encapsulation is especially useful to avoid mixing different domain types with the same refinement: + +```scala +opaque type Temperature = Double :| Positive +opaque type Moisture = Double :| Positive +``` + +```scala +case class Info(temperature: Temperature, moisture: Moisture) + +val temperature: Temperature = ??? +val moisture: Moisture = ??? + +Info(moisture, temperature) //Compile-time error +``` + +But as is, you cannot create an "instance" of your opaque type outside of its definition file. You need to add methods yourself like: + +```scala +opaque type Temperature = Double :| Positive + +object Temperature: + + def apply(x: Double :| Positive): Temperature = x +``` + +## RefinedTypeOps + +Iron provides a convenient trait called `RefinedTypeOps` to easily add smart constructors to your type: + +```scala +opaque type Temperature = Double :| Positive +object Temperature extends RefinedTypeOps[Temperature] +``` + +```scala +val temperature = Temperature(15) //Compiles +println(temperature) //15 + +val positive: Int :| Positive = 15 +val tempFromIron = Temperature.fromIronType(positive) +``` + +### Runtime refinement + +`RefinedTypeOps` supports [all refinement methods](refinement.md) provided by Iron: + +```scala +val unsafeRuntime: Temperature = Temperature.applyUnsafe(15) +val option: Option[Temperature] = Temperature.option(15) +val either: Either[String, Temperature] = Temperature.either(15) +``` + +Constructors for other modules exist: + +```scala +val zioValidation: Temperature = Temperature.validation(15) +``` + +Note: all these constructors are inline. They don't bring any overhead: + +```scala +val temperature: Temperature = Temperature(15) +val unsafeRuntime: Temperature = Temperature.applyUnsafe(runtimeValue) +``` + +compiles to + +```scala +val temperature: Double = 15 +val unsafeRuntime: Double = + if runtimeValue > 0 then runtimeValue + else throw new IllegalArgumentException("...") +``` +