coulomb-refined
The coulomb-refined
package defines policies and utilities for integrating the
refined
typelevel libraries with coulomb
.
Quick Start
documentation
You can browse the coulomb-refined
policies
here.
packages
Include coulomb-refined
with your Scala project:
libraryDependencies += "com.manyangled" %% "coulomb-core" % "0.8.0"
libraryDependencies += "com.manyangled" %% "coulomb-refined" % "0.8.0"
import
To import the standard coulomb policy with the refined overlay:
// fundamental coulomb types and methods
import coulomb.*
import coulomb.syntax.*
// common refined definitions
import eu.timepit.refined.*
import eu.timepit.refined.api.*
import eu.timepit.refined.numeric.*
// algebraic definitions
import algebra.instances.all.given
import coulomb.ops.algebra.all.{*, given}
// standard policy for spire and scala types
import coulomb.policy.standard.given
import scala.language.implicitConversions
// overlay policy for refined integrations
import coulomb.policy.overlay.refined.algebraic.given
// coulomb syntax for refined integrations
import coulomb.syntax.refined.*
examples
Examples in this section will use the following workaround as a replacement for refineMV until it is ported forward to Scala 3.
// a workaround for refineMV not being available in scala3
// https://github.com/fthomas/refined/issues/932
object workaround:
extension [V](v: V)
def withRP[P](using Validate[V, P]): Refined[V, P] =
refineV[P].unsafeFrom(v)
import workaround.*
The coulomb-refined
package supports refined
predicates that are algebraically well-behaved for applicable operations.
Primarily this means the predicates Positive
and NonNegative
.
For example, the positive doubles are an additive semigroup and multiplicative group,
as the following code demonstrates.
The
table
below summarizes the full list of supported refined
predicates and associated algebras.
import coulomb.units.si.{*, given}
import coulomb.units.us.{*, given}
val pos1 = 1d.withRP[Positive].withUnit[Meter]
// pos1: Quantity[Refined[Double, Positive], Meter] = 1.0
val pos2 = 2d.withRP[Positive].withUnit[Meter]
// pos2: Quantity[Refined[Double, Positive], Meter] = 2.0
val pos3 = 3d.withRP[Positive].withUnit[Second]
// pos3: Quantity[Refined[Double, Positive], Second] = 3.0
// positive doubles are an additive semigroup
pos1 + pos2
// res0: Quantity[Refined[Double, Positive], Meter] = 3.0
// also a multiplicative semigroup
pos1 * pos2
// res1: Quantity[Refined[Double, Positive], ^[Meter, 2]] = 2.0
pos2.pow[2]
// res2: Quantity[Refined[Double, Positive], ^[Meter, 2]] = 4.0
// also a multiplicative group
pos2 / pos3
// res3: Quantity[Refined[Double, Positive], /[Meter, Second]] = 0.6666666666666666
pos2.pow[0]
// res4: Quantity[Refined[Double, Positive], 1] = 1.0
The standard refined
function for refining values with run-time checking is refineV
,
which returns an Either
.
The coulomb-refined
package supplies a similar variation refinedVU
.
These objects are also supported by algebras.
// This refinement succeeds, and returns a Right value
val pe1 = refineVU[Positive, Meter](1)
// pe1: Quantity[Either[String, Refined[Int, Positive]], Meter] = Right(
// value = 1
// )
// This refinement fails, and returns a Left value
val pe2 = refineVU[Positive, Meter](0)
// pe2: Quantity[Either[String, Refined[Int, Positive]], Meter] = Left(
// value = "Predicate failed: (0 > 0)."
// )
// positives are an additive semigroup
pe1 + pe1
// res5: Quantity[Either[String, Refined[Int, Positive]], Meter] = Right(
// value = 2
// )
// algebras operating on Left values result in a Left
pe1 + pe2
// res6: Quantity[Either[String, Refined[Int, Positive]], Meter] = Left(
// value = "Predicate failed: (0 > 0)."
// )
Policies
policy overlays
The coulomb-refined
package currently provides a single "overlay" policy.
An overlay policy is designed to work with any other policies currently in scope,
and lift them into another abstraction;
in this case, lifting policies for value type(s) V
into Refined[V, P]
.
The Refined
abstraction guarantees that a value of type V
satisfies some predicate P
,
and the semantics of V
remain otherwise unchanged.
For example, given any algebra in scope for a type V
that defines addition,
the coulomb-refined
overlay defines the corresponding Refined[V, P]
addition
like so:
plus(x: Refined[V, P], y: Refined[V, P]): Refined[V, P] =
// (x.value + y.value) refined by P
Because the refined algebraic policy is an overlay, you can use it with your choice of base policies, for example with core policies or spire policies.
algebraic policy table
The following table summarizes the "algebraic" overlay policy. Examples of Fractional value types include Double, Float, BigDecimal, spire Rational, etc. Integral value types include Int, Long, BigInt, etc.
Value Type | Predicate | Add Alg | Mult Alg | + |
* |
/ |
pow (exponent) |
---|---|---|---|---|---|---|---|
Fractional | Positive | semigroup | group | Y | Y | Y | Y (rational) |
Fractional | NonNegative | semigroup | semigroup | Y | Y | N | Y (pos int) |
Integral | Positive | semigroup | semigroup | Y | Y | N | Y (pos int) |
Integral | NonNegative | semigroup | semigroup | Y | Y | N | Y (pos int) |
The table above also applies to Either
objects returned by refineVU
as discussed
in the examples section above.