refined
refined copied to clipboard
Derive a custom Validate from an existing one
First let me apologize for creating an issue for a simple question, but I haven't found another channel.
So, let's say I would like to refine scala.concurrent.duration.FiniteDuration
, e.g. with Positive
. My first idea was to simply use Positive
and (contra)map it over a function extracting the length
from the FiniteDuration
which is a Long
. But I haven't found a way to do that, i.e. no public (contra)map method on Validate
.
Any help would be highly appreciated.
@hseeberger Asking questions here is fine. The Gitter channel would be another option.
You're right, currently there is no API to contramap
over an existing Validate
instance to create a new one. In your case, just defining the instance would be the easiest option:
scala> implicit val finiteDurationValidate = Validate.fromPredicate(
(d: FiniteDuration) => d.length > 0,
(d: FiniteDuration) => s"$d is positive",
Greater(shapeless.nat._0))
scala> refineV[Positive](1.minute)
res32: Either[String, Refined[FiniteDuration, Positive]] = Right(1 minute)
scala> refineV[Positive](-1.minute)
res33: Either[String, Refined[FiniteDuration, Positive]] = Left(Predicate failed: -1 minutes is positive.)
I think we should definitely make it easier to define this instance via the Validate[Long, Positive]
instance.
Thanks for the quick answer!
I have changed the title (removed "question") to better reflect the new nature of this issue (now a feature requirement).
I've been thinking about this too – we have Inference
for equivalent predicates, but we don't have a way to encode type-to-type predicate relationships. For instance, a Money(amount: Long, currency: String)
class can be refined based on the predicate of its amount
parameter. I suppose making the contramap
method on Validate
public would be a good first step?
Another (more verbose) alternative I just discovered a few days ago is to use RefTypeOps.coflatMapRefine
.
Making Validate.contramap
public is certainly an option but I think it needs a second parameter that also replaces the showExpr
implementation. In the current form a Validate[FiniteDuration, Positive]
derived from Validate[Long, Positive]
via contramap(_.length)
would reuse showExpr
from the Validate[Long, Positive]
instance to produce error messages like Predicate failed: -1 > 0.
In this situation it would be desireable if the error message would be more specific and related to FiniteDuration
.
Is there support for Refined[FiniteDuration, Positive]
?
# git clone refined
$cd refined
$ggrep -r FiniteDuration .
$ggrep -r Duration .
$
Thanks for your help and this great library!
@kevinmeredith refined does not (yet) provide support for refining Duration
or FiniteDuration
but you can use the instance in https://github.com/fthomas/refined/issues/397#issuecomment-354676072 for that.
For deriving a custom Validate
from existing one we might need an equivalence relation:
final class Equivalence[A, B]
private val equivalenceInstance = new Equivalence[Any, Any]
type `<==>`[A, B] = Equivalence[A, B]
implicit def inferEquivalence[T,A,B](implicit eab: A ==> B, eba: B ==> A) : A <==> B =
equivalenceInstance.asInstanceOf[A <==> B]
And then we want to have the following validation inference rule:
implicit def equivalenceValidation[T, A, B](implicit v: Validate[T, A], e : A <==> B): Validate[T, B] =
v.asInstanceOf[Validate[T, B]]