refined
refined copied to clipboard
Boolean refinements "weak"
The title sucks, I know :)
The following is an annoying thing refined
allows but should probably not be allowed.
I also think that some kind of type bound or self-typing may be able to solve this, although I have not looked into it at all.
Imagine this code:
type Foo = String Refined NonEmpty And MaxSize[W.`10`.T]
Looks fine until you realize it makes no sense at all (but compiles!).
typeOf[Foo].dealias.toString
res23: String = "eu.timepit.refined.boolean.And[eu.timepit.refined.api.Refined[String,eu.timepit.refined.collection.NonEmpty],eu.timepit.refined.collection.MaxSize[Int(10)]]"
So we have an And[Refined[String, NonEmpty], MaxSize[10]]
. Is there an actual use case for Refined
being a type parameter to any of the boolean operators? I can't think of one.
What I actually would want is this:
type Foo = String Refined (NonEmpty And MaxSize[W.`10`.T])
typeOf[Foo].dealias.toString
res21: String = "eu.timepit.refined.api.Refined[String,eu.timepit.refined.boolean.And[eu.timepit.refined.collection.NonEmpty,eu.timepit.refined.collection.MaxSize[Int(10)]]]"
To my understanding, there is no way we can make Refined
"weaker". But this should at least be a compile error without the parentheses, should it not?
We could introduce a marker trait Predicate
, let every predicate extends that, and then use a type bound in And
: class And[A <: Predicate, B <: Predicate]
. Refined
would not be a Predicate
and And[Refined, ...]
would be a compile error.
I like that, I will likely send a PR for it next week.
That would be great!
Here is another weird one. I am unsure it will be fixed with the marker trait. If not, I will open a new issue:
type Foo = String Refined MaxSize[W.`10`.T] //compiles, makes no sense
type Bar = String Refined MaxSize[Equal[W.`10`.T]] // what you actually want
Actually the first is correct and the second makes no sense. But you're right, for MaxSize
we can't add a type bound because we want to allow both Nat
and singleton types: MaxSize[_5]
and MaxSize[W.`5`.T]
.
What we can prevent via the marker trait is:
type Foo = String Refined Size[W.`10`.T] //compiles, makes no sense
type Bar = String Refined Size[Equal[W.`10`.T]] // what you actually want
By defining Size
as final case class Size[P <: Predicate](p: P)
.
So this marker trait business is a little more involved than I had thought :) If somebody want to take this on, go ahead. Otherwise it will have to wait until I have a bit more free time.
Oh, I see the issue. We also put Result
values in e.g. Not(Result(...))
and other predicates in Validate
instances and Result
is not a predicate. Hmm...
We are not only putting Result
values into predicates but sometimes also arbitrary types. See for example
scala> Validate[List[Int], Forall[Positive]].validate(List(1, 2))
res1: Result[Forall[List[Result[Greater[shapeless._0]]]]] = Passed(Forall(List(Passed(Greater(shapeless._0@5d15faf4)), Passed(Greater(shapeless._0@5d15faf4)))))
So if we change class Forall[P](p: P)
to class Forall[P <: Predicate](p: P)
the above would be invalid since List
is not a subtype of Predicate
.
Yeah, I don't think we can do this with what we currently have. I have sort of given up since I've run into lots of different issues whenever I tried... Not sure how to solve this...