refined icon indicating copy to clipboard operation
refined copied to clipboard

Derive a custom Validate from an existing one

Open hseeberger opened this issue 7 years ago • 7 comments

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 avatar Jan 01 '18 16:01 hseeberger

@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.

fthomas avatar Jan 01 '18 20:01 fthomas

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).

hseeberger avatar Jan 01 '18 21:01 hseeberger

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.

kusamakura avatar Jan 06 '18 03:01 kusamakura

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.

fthomas avatar Jan 08 '18 20:01 fthomas

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 avatar Apr 30 '18 18:04 kevinmeredith

@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.

fthomas avatar May 02 '18 20:05 fthomas

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]]

Primetalk avatar Dec 12 '18 10:12 Primetalk