fthomas / refined Goto Github PK
View Code? Open in Web Editor NEWRefinement types for Scala
License: MIT License
Refinement types for Scala
License: MIT License
Currently shapeless' @@
and refined's Refined
are treated specially in the library because theses are the only types for which there are refine
and refineM
functions. But what if somebody wants to use scalaz.@@
as a refinement result type? Currently one needs to write an instance of internal.Wrapper
for it and add their own refine
and refineM
variants (which is unnecessary boilerplatey). Ideally the internal.Wrapper
type class will be renamed and moved into the top-level package and gets refine
and refineM
as additional functions. This should also remove the necessity of the different refine
variants:
refineV -> TC[Refined].refine
refineMV -> TC[Refined].refineM
refineT -> TC[@@].refine
refineMT -> TC[@@].refineM
TC[scalaz.@@].refine
TC[scalaz.@@].refineM
This is related to #48 (comment).
TODO:
fieldnames.md
with ConstructorNames
examples and add link to the release notesutil
functions and add link to the release notesrefineLit
and refine
in the README.md
Predicate.isConstant
RefineM
Using MatchesRegex
works with refine
but currently fails with refineLit
. This test yields the compilation error given below:
val W = shapeless.Witness
property("refineLit success with MatchesRegex") = secure {
def ignore: String @@ MatchesRegex[W.`"[0-9]+"`.T] =
refineLit[MatchesRegex[W.`"[0-9]+"`.T], String]("123")
true
}
[error] refined/src/test/scala/eu/timepit/refined/RefinedSpec.scala:66: exception during macro expansion:
[error] scala.tools.reflect.ToolBoxError: reflective compilation has failed:
[error]
[error] overriding value value in trait Witness of type fresh$macro$5.this.T;
[error] value value has incompatible type
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:316)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.wrapInPackageAndCompile(ToolBoxFactory.scala:198)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:252)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$compile$2.apply(ToolBoxFactory.scala:429)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$compile$2.apply(ToolBoxFactory.scala:422)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.liftedTree2$1(ToolBoxFactory.scala:355)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.apply(ToolBoxFactory.scala:355)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:422)
[error] at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:444)
[error] at scala.reflect.macros.contexts.Evals$class.eval(Evals.scala:20)
[error] at scala.reflect.macros.contexts.Context.eval(Context.scala:6)
[error] at eu.timepit.refined.internal.package$.refineLitImpl(package.scala:13)
[error] refineLit[MatchesRegex[W.`"[0-9]+"`.T], String]("123")
The exception originates from this line in the refineLitImpl
macro:
val predicate: Predicate[P, T] = c.eval(c.Expr(c.untypecheck(p.tree)))
This REPL session should not produce errors:
scala> val x: Int @@ Positive = 5
x: shapeless.tag.@@[Int,eu.timepit.refined.numeric.Positive] = 5
scala> type PositiveInt = Int @@ Positive
defined type alias PositiveInt
scala> val y: PositiveInt = refineLit(5)
<console>:41: error: diverging implicit expansion for type eu.timepit.refined.Predicate[P,Int]
starting with method greaterPredicateNat in trait NumericPredicates
val y: PositiveInt = refineLit(5)
^
scala> val y: PositiveInt = 5
<console>:40: error: type mismatch;
found : Int(5)
required: PositiveInt
(which expands to) Int with shapeless.tag.Tagged[eu.timepit.refined.numeric.Greater[shapeless._0]]
val y: PositiveInt = 5
^
scala> val y: PositiveInt = refineLit[Positive](5)
y: PositiveInt = 5
scala> val y: PositiveInt = refineLit[Positive][Int](5)
y: PositiveInt = 5
Support implicit conversion from a Refined value to its raw value, as in:
implicit def toRaw[T, P](r: Refined[T, P]):T = r.get
Mostly just a question of where best to define it, so it is easy to include.
REPL Example:
scala> val x: Int Refined NonNegative = 3
x: eu.timepit.refined.Refined[Int,eu.timepit.refined.numeric.NonNegative] = Refined(3)
scala> x + 5
<console>:45: error: value + is not a member of eu.timepit.refined.Refined[Int,eu.timepit.refined.numeric.NonNegative]
scala> implicit def toRaw[T, P](r: Refined[T, P]):T = r.get
scala> x + 5
res2: Int = 8
From the Gitter channel
@koshelev Another thing is to add some scalacheck support, but it should not be so hard
@fthomas The ScalaCheck support sounds interesting. I guess you want to have anArbitrary[F[T, P]: RefType]
automatically if there is anArbitrary[T]
and aPredicate[P, T]
?
Filtering an existing Arbitrary[T]
with a Predicate[P, T]
will discard too many values in most cases, so that is probably too simplistic. We should nevertheless check how to make working with refined and ScalaCheck nicer.
refineMV
and refineV
refine
and refineLit
PostErasure*.scala
auto
earlier in the READMEAnyVal
causes compared to a value class wrapping an AnyVal
(see http://stackoverflow.com/questions/33136558/validations-in-value-classes/33180418#comment54214054_33180418) - Answer: monomorphic value classes (e.g. class VC(i: Int) extends AnyVal
) won't boxThe examples here http://fthomas.github.io/refined/latest/api/index.html#eu.timepit.refined.util.string$@regex%28s:shapeless.tag.@@[String,eu.timepit.refined.string.Regex]%29:scala.util.matching.Regex do not have any newlines although they are present in the source file (https://github.com/fthomas/refined/blob/master/shared/src/main/scala/eu/timepit/refined/util/string.scala#L15).
when milessabin/shapeless#440 is released.
Adding an implicit version of refineLit
like
implicit def refine[T, P](t: T)(implicit p: Predicate[P, T]): T @@ P = macro internal.RefineLit.macroImpl[P, T]
allows this
scala> val a: Char @@ Digit = '5'
a: shapeless.tag.@@[Char,eu.timepit.refined.char.Digit] = 5
scala> val a: Char @@ Digit = 'a'
<console>:36: error: Predicate failed: isDigit('a').
val a: Char @@ Digit = 'a'
^
I'm not sure yet if this implicit conversion should be added.
In order to work around #2, WeakWitness
was added. The underlying problem in #2 was that untypechecking and then typechecking a tree was not the identity. @milessabin suggested building a fresh tree instead of reusing the original so that we can use shapeless.Witness
instead of our WeakWitness
:
The fix, ultimately will be to traverse the typechecked tree and rebuild a fresh untypechecked one rather than trying to reuse the original tree.
I'll try this and post updates to this issue.
The collection predicates currently work only with types that are subtypes of TraversableOnce
but these should work with any Foldable
type.
Contains
is just a type alias (Contains[A] = Exists[Equal[A]]
), Index[N, P]
checks if the value at index N
satisfies the predicate P
, and Unique
checks if all elements in a collection are unique.
... using Predicate[T, P].isValid
They are currently disabled because ScalaCheck is a JVM-only dependency in build.sbt
.
similar to https://github.com/daandi/nice-uuid
/cc @daandi
refine
currently returns an Either[String, A]
. String
is an awful type for errors and should probably be replaced by a dedicated error type like RefinementError
.
case class RefinementError(msg: String)
The first option would be a value class case class Refined[T, P](get: T) extends AnyVal
as suggested by @alexarchambault here https://gitter.im/fthomas/refined/archives/2015/07/13.
scala> val x: List[Int] @@ True = List(1, 2, 3)
<console>:40: error: refineLit only supports literals
val x: List[Int] @@ True = List(1, 2, 3)
^
The rhs is not required for evaluation of the predicate (because it is constant), so this refinement should be possible at compile-time. The same is true for val x: List[Int] @@ Not[False] = List(1, 2, 3)
.
This is a reproducible REPL session after running sbt clean
and sbt console
:
Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_76).
Type in expressions to have them evaluated.
Type :help for more information.
scala> refineLit[Greater[_10], Int](15)
<console>:31: error: exception during macro expansion:
java.lang.IndexOutOfBoundsException: 0
at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:65)
at scala.collection.immutable.List.apply(List.scala:84)
at scala.reflect.internal.Importers$StandardImporter.recreateOrRelink$1(Importers.scala:170)
at scala.reflect.internal.Importers$StandardImporter.importSymbol(Importers.scala:210)
at scala.reflect.internal.Importers$StandardImporter.recreateType(Importers.scala:224)
at scala.reflect.internal.Importers$StandardImporter.importType(Importers.scala:284)
at scala.reflect.internal.Importers$StandardImporter$$anonfun$recreateType$1.apply(Importers.scala:224)
at scala.reflect.internal.Importers$StandardImporter$$anonfun$recreateType$1.apply(Importers.scala:224)
at scala.collection.immutable.List.map(List.scala:273)
at scala.reflect.internal.Importers$StandardImporter.recreateType(Importers.scala:224)
at scala.reflect.internal.Importers$StandardImporter.importType(Importers.scala:284)
at scala.reflect.internal.Importers$StandardImporter.recreateSymbol(Importers.scala:128)
at scala.reflect.internal.Importers$StandardImporter.scala$reflect$internal$Importers$StandardImporter$$cachedRecreateSymbol$1(Importers.scala:145)
at scala.reflect.internal.Importers$StandardImporter.recreateOrRelink$1(Importers.scala:193)
at scala.reflect.internal.Importers$StandardImporter.importSymbol(Importers.scala:210)
at scala.reflect.internal.Importers$StandardImporter$$anonfun$recreateType$4.apply(Importers.scala:248)
at scala.reflect.internal.Importers$StandardImporter$$anonfun$recreateType$4.apply(Importers.scala:248)
at scala.reflect.internal.Scopes$Scope.foreach(Scopes.scala:373)
at scala.reflect.internal.Importers$StandardImporter.recreateType(Importers.scala:248)
at scala.reflect.internal.Importers$StandardImporter.importType(Importers.scala:284)
at scala.reflect.internal.Importers$StandardImporter$$anon$1.complete(Importers.scala:75)
at scala.reflect.internal.Symbols$Symbol.info(Symbols.scala:1488)
at scala.reflect.runtime.SynchronizedSymbols$SynchronizedSymbol$$anon$1.scala$reflect$runtime$SynchronizedSymbols$SynchronizedSymbol$$super$info(SynchronizedSymbols.scala:174)
at scala.reflect.runtime.SynchronizedSymbols$SynchronizedSymbol$$anonfun$info$1.apply(SynchronizedSymbols.scala:127)
at scala.reflect.runtime.SynchronizedSymbols$SynchronizedSymbol$$anonfun$info$1.apply(SynchronizedSymbols.scala:127)
at scala.reflect.runtime.Gil$class.gilSynchronized(Gil.scala:19)
at scala.reflect.runtime.JavaUniverse.gilSynchronized(JavaUniverse.scala:16)
at scala.reflect.runtime.SynchronizedSymbols$SynchronizedSymbol$class.gilSynchronizedIfNotThreadsafe(SynchronizedSymbols.scala:123)
at scala.reflect.runtime.SynchronizedSymbols$SynchronizedSymbol$$anon$1.gilSynchronizedIfNotThreadsafe(SynchronizedSymbols.scala:174)
at scala.reflect.runtime.SynchronizedSymbols$SynchronizedSymbol$class.info(SynchronizedSymbols.scala:127)
at scala.reflect.runtime.SynchronizedSymbols$SynchronizedSymbol$$anon$1.info(SynchronizedSymbols.scala:174)
at scala.reflect.internal.Importers$StandardImporter.recreateOrRelink$1(Importers.scala:167)
at scala.reflect.internal.Importers$StandardImporter.importSymbol(Importers.scala:210)
at scala.reflect.internal.Importers$StandardImporter.recreateType(Importers.scala:228)
at scala.reflect.internal.Importers$StandardImporter.importType(Importers.scala:284)
at scala.reflect.internal.Importers$StandardImporter.recreateType(Importers.scala:228)
at scala.reflect.internal.Importers$StandardImporter.importType(Importers.scala:284)
at scala.reflect.internal.Importers$StandardImporter.recreateType(Importers.scala:224)
at scala.reflect.internal.Importers$StandardImporter.importType(Importers.scala:284)
at scala.reflect.internal.Importers$StandardImporter.recreatedTreeCompleter(Importers.scala:302)
at scala.reflect.internal.Importers$StandardImporter$$anonfun$importTree$1.apply$mcV$sp(Importers.scala:417)
at scala.reflect.internal.Importers$StandardImporter.tryFixup(Importers.scala:49)
at scala.reflect.internal.Importers$StandardImporter.importTree(Importers.scala:418)
at scala.reflect.internal.Importers$StandardImporter$$anonfun$recreateTree$17.apply(Importers.scala:367)
at scala.reflect.internal.Importers$StandardImporter$$anonfun$recreateTree$17.apply(Importers.scala:367)
at scala.collection.immutable.List.map(List.scala:273)
at scala.reflect.internal.Importers$StandardImporter.recreateTree(Importers.scala:367)
at scala.reflect.internal.Importers$StandardImporter.importTree(Importers.scala:415)
at scala.reflect.internal.Importers$StandardImporter.recreateTree(Importers.scala:370)
at scala.reflect.internal.Importers$StandardImporter.importTree(Importers.scala:415)
at scala.reflect.internal.Importers$StandardImporter.importTree(Importers.scala:29)
at scala.reflect.macros.contexts.Evals$class.eval(Evals.scala:19)
at scala.reflect.macros.contexts.Context.eval(Context.scala:6)
at eu.timepit.refined.internal.package$.refineLitImpl(package.scala:13)
refineLit[Greater[_10], Int](15)
^
scala> refineLit[Greater[_10], Int](15)
res1: shapeless.tag.@@[Int,eu.timepit.refined.numeric.Greater[shapeless.nat._10]] = 15
The exception does not happen on the second try. Seems to happen only with predicates that take Nat
s.
... when scala-js/scala-js#1802 is fixed and released.
... when scala-js/scala-js#1823 is released.
I'd like to make the constructor of Refined
private[refined]
to ensure that any Refined[T, P]
values only contains T
s that satisfy P
. But doing so currently fails a tut example:
[tut] *** Error reported at type_aliases.md:18
<console>:28: error: constructor Refined in class Refined cannot be accessed in object $iw
Square('a', 1)
^
The reason for this is that the autoRefineV
macro replaces 'a'
with Refined('a')
in this example and thus calling the constructor outside of the refined
package. Can this be done without more black magic?
I'd like to have a complete example that demonstrates how this library can or should be used. The example should be checked at build-time either as code in a separate example subproject or in a tut file.
... and use "test" instead of "refinedJVM/test;refinedJS/test" in the "validate" command alias.
https://repo1.maven.org/maven2/eu/timepit/refined_2.11/0.0.3/refined_2.11-0.0.3.pom
...
<dependency>
<groupId>org.tpolecat</groupId>
<artifactId>tut-core_2.11</artifactId>
<version>0.3.2</version>
</dependency>
...
This may be related to tpolecat/tut#36:
70e795a is a first step to more documentation. But there should also be examples which are verified by sbt-doctest.
This is just an idea. If someone else wants to implement this, feel free!
The purpose of Predicate
is to tell if a value conforms to type-level predicate. Refining the type is a separate concern.
Remove Predicate.show
and use Show
type class instead
This is a reminder for myself: https://gitter.im/milessabin/shapeless/archives/2015/07/11
Something like this:
final case class Refined[T, P](get: T) extends AnyVal {
def update(f: T => T)(implicit p: Predicate[P, T]): Either[String, Refined[T, P]]
}
scala> def foo(i: Int @@ Greater[_5]): Int = i
foo: (i: shapeless.tag.@@[Int,eu.timepit.refined.numeric.Greater[shapeless.nat._5]])Int
scala> foo(refineLit[Greater[_5]](10))
res11: Int = 10
scala> foo(refineLit[Greater[_6]](10))
<console>:35: error: type mismatch;
found : Int(10)
required: shapeless.tag.Tagged[eu.timepit.refined.numeric.Greater[shapeless.nat._5]] with Int
(which expands to) shapeless.tag.Tagged[eu.timepit.refined.numeric.Greater[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]]]] with Int
foo(refineLit[Greater[_6]](10))
^
The last call should typecheck because we should be able to prove statically that anything that is Greater[_6]
is also Greater[_5]
.
def regex(s: String @@ Regex): scala.util.matching.Regex = s.r
Usage could look like this:
import eu.timepit.refined.implicits._
val r = regex("(a|b)")
// r: scala.util.matching.Regex = "(a|b)"
// invalid regexes do not compile
The same could be done for URLs and URIs.
https://twitter.com/FouriersTrick/status/636539026208608257 and https://issues.scala-lang.org/browse/SI-7884
TL;DR case class copy method subverts the private constructor
REPL session:
scala> val r = refineMV[Positive](5)
r: eu.timepit.refined.Refined[Int,eu.timepit.refined.numeric.Positive] = Refined(5)
scala> r.copy[Int, Positive](-5)
res5: eu.timepit.refined.Refined[Int,eu.timepit.refined.numeric.Positive] = Refined(-5)
:-(
Thanks @ceedubs for the PSA.
For Predicate
s that override validated
we should ensure that validated
is consistent with isValid
.
It seems that releasePublishArtifactsAction := PgpKeys.publishSigned.value
currently has no effect.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.