GithubHelp home page GithubHelp logo

refined's Introduction

refined: simple refinement types for Scala

GitHub Workflow Status codecov.io Gitter refined Scala version support Scaladoc

refined is a Scala library for refining types with type-level predicates which constrain the set of values described by the refined type. It started as a port of the refined Haskell library by Nikita Volkov (which also provides an excellent motivation why this kind of library is useful). The idea of expressing constraints at the type-level as Scala library was first explored by Flavio W. Brasil in bond.

A quick example:

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._

// This refines Int with the Positive predicate and checks via an
// implicit macro that the assigned value satisfies it:
scala> val i1: Int Refined Positive = 5
i1: Int Refined Positive = 5

// If the value does not satisfy the predicate, we get a meaningful
// compile error:
scala> val i2: Int Refined Positive = -5
<console>:22: error: Predicate failed: (-5 > 0).
       val i2: Int Refined Positive = -5
                                       ^

// There is also the explicit refineMV macro that can infer the base
// type from its parameter:
scala> refineMV[Positive](5)
res0: Int Refined Positive = 5

// Macros can only validate literals because their values are known at
// compile-time. To validate arbitrary (runtime) values we can use the
// refineV function:

scala> val x = 42 // suppose the value of x is not known at compile-time

scala> refineV[Positive](x)
res1: Either[String, Int Refined Positive] = Right(42)

scala> refineV[Positive](-x)
res2: Either[String, Int Refined Positive] = Left(Predicate failed: (-42 > 0).)

refined also contains inference rules for converting between different refined types. For example, Int Refined Greater[10] can be safely converted to Int Refined Positive because all integers greater than ten are also positive. The type conversion of refined types is a compile-time operation that is provided by the library:

scala> val a: Int Refined Greater[5] = 10
a: Int Refined Greater[Int(5)] = 10

// Since every value greater than 5 is also greater than 4, `a` can be
// ascribed the type Int Refined Greater[4]:
scala> val b: Int Refined Greater[4] = a
b: Int Refined Greater[Int(4)] = 10

// An unsound ascription leads to a compile error:
scala> val c: Int Refined Greater[6] = a
                                       ^
       error: type mismatch (invalid inference):
               eu.timepit.refined.numeric.Greater[5] does not imply
               eu.timepit.refined.numeric.Greater[6]

This mechanism allows to pass values of more specific types (e.g. Int Refined Greater[10]) to functions that take a more general type (e.g. Int Refined Positive) without manual intervention.

prior Scala 2.13 without literal types

Since there are no literal types prior to Scala 2.13 the literals must be created with shapeless:

scala> val a: Int Refined Greater[W.`5`.T] = 10
a: Int Refined Greater[Int(5)] = 10
scala> val b: Int Refined Greater[W.`4`.T] = a
b: Int Refined Greater[Int(4)] = 10

Note that W is a shortcut for shapeless.Witness which provides syntax for literal-based singleton types.

Table of contents

  1. More examples
  2. Using refined
  3. Community
  4. Documentation
  5. Provided predicates
  6. Contributors and participation
  7. Related projects
  8. License

More examples

import eu.timepit.refined._
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._
import eu.timepit.refined.api.{RefType, Refined}
import eu.timepit.refined.boolean._
import eu.timepit.refined.char._
import eu.timepit.refined.collection._
import eu.timepit.refined.generic._
import eu.timepit.refined.string._
import shapeless.{ ::, HNil }

scala> refineMV[NonEmpty]("Hello")
res2: String Refined NonEmpty = Hello

scala> refineMV[NonEmpty]("")
<console>:39: error: Predicate isEmpty() did not fail.
            refineMV[NonEmpty]("")
                              ^

scala> type ZeroToOne = Not[Less[0.0]] And Not[Greater[1.0]]
defined type alias ZeroToOne

scala> refineMV[ZeroToOne](1.8)
<console>:40: error: Right predicate of (!(1.8 < 0.0) && !(1.8 > 1.0)) failed: Predicate (1.8 > 1.0) did not fail.
       refineMV[ZeroToOne](1.8)
                          ^

scala> refineMV[AnyOf[Digit :: Letter :: Whitespace :: HNil]]('F')
res3: Char Refined AnyOf[Digit :: Letter :: Whitespace :: HNil] = F

scala> refineMV[MatchesRegex["[0-9]+"]]("123.")
<console>:39: error: Predicate failed: "123.".matches("[0-9]+").
              refineMV[MatchesRegex[W.`"[0-9]+"`.T]]("123.")
                                                    ^

scala> val d1: Char Refined Equal['3'] = '3'
d1: Char Refined Equal[Char('3')] = 3

scala> val d2: Char Refined Digit = d1
d2: Char Refined Digit = 3

scala> val d3: Char Refined Letter = d1
<console>:39: error: type mismatch (invalid inference):
 Equal[Char('3')] does not imply
 Letter
       val d3: Char Refined Letter = d1
                                     ^

scala> val r1: String Refined Regex = "(a|b)"
r1: String Refined Regex = (a|b)

scala> val r2: String Refined Regex = "(a|b"
<console>:38: error: Regex predicate failed: Unclosed group near index 4
(a|b
    ^
       val r2: String Refined Regex = "(a|b"
                                      ^

scala> val u1: String Refined Url = "htp://example.com"
<console>:38: error: Url predicate failed: unknown protocol: htp
       val u1: String Refined Url = "htp://example.com"
                                    ^

// Here we define a refined type "Int with the predicate (7 <= value < 77)".
scala> type Age = Int Refined Interval.ClosedOpen[7, 77]

scala> val userInput = 55

// We can refine values with this refined type by either using `refineV`
// with an explicit return type
scala> val ageEither1: Either[String, Age] = refineV(userInput)
ageEither1: Either[String,Age] = Right(55)

// or by using `RefType.applyRef` with the refined type as type parameter.
scala> val ageEither2 = RefType.applyRef[Age](userInput)
ageEither2: Either[String,Age] = Right(55)

Using refined

The latest version of the library is 0.11.2, which is available for Scala and Scala.js version 2.12 and 2.13.

If you're using sbt, add the following to your build:

libraryDependencies ++= Seq(
  "eu.timepit" %% "refined"                 % "0.11.2",
  "eu.timepit" %% "refined-cats"            % "0.11.2", // optional
  "eu.timepit" %% "refined-eval"            % "0.11.2", // optional, JVM-only
  "eu.timepit" %% "refined-jsonpath"        % "0.11.2", // optional, JVM-only
  "eu.timepit" %% "refined-pureconfig"      % "0.11.2", // optional, JVM-only
  "eu.timepit" %% "refined-scalacheck"      % "0.11.2", // optional
  "eu.timepit" %% "refined-scalaz"          % "0.11.2", // optional
  "eu.timepit" %% "refined-scodec"          % "0.11.2", // optional
  "eu.timepit" %% "refined-scopt"           % "0.11.2", // optional
  "eu.timepit" %% "refined-shapeless"       % "0.11.2"  // optional
)

For Scala.js just replace %% with %%% above.

Instructions for Maven and other build tools are available at search.maven.org.

Release notes for the latest version are here.

Community

Internal modules

The project provides these optional extensions and library integrations:

  • refined-cats provides Cats type class instances for refined types
  • refined-eval provides the Eval[S] predicate that checks if a value applied to the predicate S yields true
  • refined-jsonpath provides the JSONPath predicate that checks if a String is a valid JSONPath
  • refined-pureconfig allows to read configuration with refined types using PureConfig
  • refined-scalacheck allows to generate arbitrary values of refined types with ScalaCheck. Use refined-scalacheck_1.13 instead if your other dependencies use scalacheck version 1.13
  • refined-scalaz provides Scalaz type class instances for refined types and support for scalaz.@@
  • refined-scodec allows binary decoding and encoding of refined types with scodec and allows refining scodec.bits.ByteVector
  • refined-scopt allows to read command line options with refined types using scopt
  • refined-shapeless

External modules

Below is an incomplete list of third-party extensions and library integrations for refined. If your library is missing, please open a pull request to list it here:

Projects using refined

If your open source project is using refined, please consider opening a pull request to list it here:

Adopters

Are you using refined in your organization or company? Please consider opening a pull request to list it here:

Documentation

API documentation of the latest release is available at: https://static.javadoc.io/eu.timepit/refined_2.12/0.11.2/eu/timepit/refined/index.html

There are further (type-checked) examples in the docs directory including ones for defining custom predicates and working with type aliases. It also contains a description of refined's design and internals.

Talks and other external resources are listed on the Resources page in the wiki.

Provided predicates

The library comes with these predefined predicates:

boolean

  • True: constant predicate that is always true
  • False: constant predicate that is always false
  • Not[P]: negation of the predicate P
  • And[A, B]: conjunction of the predicates A and B
  • Or[A, B]: disjunction of the predicates A and B
  • Xor[A, B]: exclusive disjunction of the predicates A and B
  • Nand[A, B]: negated conjunction of the predicates A and B
  • Nor[A, B]: negated disjunction of the predicates A and B
  • AllOf[PS]: conjunction of all predicates in PS
  • AnyOf[PS]: disjunction of all predicates in PS
  • OneOf[PS]: exclusive disjunction of all predicates in PS

char

  • Digit: checks if a Char is a digit
  • Letter: checks if a Char is a letter
  • LetterOrDigit: checks if a Char is a letter or digit
  • LowerCase: checks if a Char is a lower case character
  • UpperCase: checks if a Char is an upper case character
  • Whitespace: checks if a Char is white space

collection

  • Contains[U]: checks if an Iterable contains a value equal to U
  • Count[PA, PC]: counts the number of elements in an Iterable which satisfy the predicate PA and passes the result to the predicate PC
  • Empty: checks if an Iterable is empty
  • NonEmpty: checks if an Iterable is not empty
  • Forall[P]: checks if the predicate P holds for all elements of an Iterable
  • Exists[P]: checks if the predicate P holds for some elements of an Iterable
  • Head[P]: checks if the predicate P holds for the first element of an Iterable
  • Index[N, P]: checks if the predicate P holds for the element at index N of a sequence
  • Init[P]: checks if the predicate P holds for all but the last element of an Iterable
  • Last[P]: checks if the predicate P holds for the last element of an Iterable
  • Tail[P]: checks if the predicate P holds for all but the first element of an Iterable
  • Size[P]: checks if the size of an Iterable satisfies the predicate P
  • MinSize[N]: checks if the size of an Iterable is greater than or equal to N
  • MaxSize[N]: checks if the size of an Iterable is less than or equal to N

generic

  • Equal[U]: checks if a value is equal to U

numeric

  • Less[N]: checks if a numeric value is less than N
  • LessEqual[N]: checks if a numeric value is less than or equal to N
  • Greater[N]: checks if a numeric value is greater than N
  • GreaterEqual[N]: checks if a numeric value is greater than or equal to N
  • Positive: checks if a numeric value is greater than zero
  • NonPositive: checks if a numeric value is zero or negative
  • Negative: checks if a numeric value is less than zero
  • NonNegative: checks if a numeric value is zero or positive
  • Interval.Open[L, H]: checks if a numeric value is in the interval (L, H)
  • Interval.OpenClosed[L, H]: checks if a numeric value is in the interval (L, H]
  • Interval.ClosedOpen[L, H]: checks if a numeric value is in the interval [L, H)
  • Interval.Closed[L, H]: checks if a numeric value is in the interval [L, H]
  • Modulo[N, O]: checks if an integral value modulo N is O
  • Divisible[N]: checks if an integral value is evenly divisible by N
  • NonDivisible[N]: checks if an integral value is not evenly divisible by N
  • Even: checks if an integral value is evenly divisible by 2
  • Odd: checks if an integral value is not evenly divisible by 2
  • NonNaN: checks if a floating-point number is not NaN

string

  • EndsWith[S]: checks if a String ends with the suffix S
  • IPv4: checks if a String is a valid IPv4
  • IPv6: checks if a String is a valid IPv6
  • MatchesRegex[S]: checks if a String matches the regular expression S
  • Regex: checks if a String is a valid regular expression
  • StartsWith[S]: checks if a String starts with the prefix S
  • Uri: checks if a String is a valid URI
  • Url: checks if a String is a valid URL
  • Uuid: checks if a String is a valid UUID
  • ValidByte: checks if a String is a parsable Byte
  • ValidShort: checks if a String is a parsable Short
  • ValidInt: checks if a String is a parsable Int
  • ValidLong: checks if a String is a parsable Long
  • ValidFloat: checks if a String is a parsable Float
  • ValidDouble: checks if a String is a parsable Double
  • ValidBigInt: checks if a String is a parsable BigInt
  • ValidBigDecimal: checks if a String is a parsable BigDecimal
  • Xml: checks if a String is well-formed XML
  • XPath: checks if a String is a valid XPath expression
  • Trimmed: checks if a String has no leading or trailing whitespace
  • HexStringSpec: checks if a String represents a hexadecimal number

Contributors and participation

The following people have helped making refined great:

refined is a Typelevel project. This means we embrace pure, typeful, functional programming, and provide a safe and friendly environment for teaching, learning, and contributing as described in the Scala Code of Conduct.

Related projects

License

refined is licensed under the MIT license, available at http://opensource.org/licenses/MIT and also in the LICENSE file.

refined's People

Contributors

armanbilge avatar balmungsan avatar busybyte avatar ceedubs avatar clydemachine avatar dcastro avatar derekmorr avatar dm-tran avatar err0r500 avatar evo-funfunfine avatar fommil avatar fthomas avatar howyp avatar ivan-klass avatar johnreedlol avatar kubukoz avatar kusamakura avatar liff avatar matthedude avatar matwojcik avatar mergify[bot] avatar michaelt293 avatar nequissimus avatar periklis avatar scala-steward avatar sh0hei avatar umbreak avatar xuwei-k avatar zainab-ali avatar zaneli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

refined's Issues

Make the Refined constructor private[refined]

I'd like to make the constructor of Refined private[refined] to ensure that any Refined[T, P] values only contains Ts 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?

Provide compile-time conversions for loosening constraints

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

Add update method to Refined

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

Represent validation errors as data

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)

RFE: implicit conversion from Refined values back to raw values

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

refining with type aliases does not work

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

Add ScalaCheck support

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 an Arbitrary[F[T, P]: RefType] automatically if there is an Arbitrary[T] and a Predicate[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.

refineLit does not work with Greater on the first try

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

refineLit does not work with predicates that take an implicit shapeless.Witness.Aux[T]

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

Add regex method?

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.

Lift the "only literals" constraint when the predicate is constant

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

Add implicit version of refineLit?

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.

Support 3rd-party types for refinement results

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

Release 0.2.0

TODO:

  • close #21
  • write an example using a type alias and add link to the release notes
  • close #27
  • extend fieldnames.md with ConstructorNames examples and add link to the release notes
  • write example using the util functions and add link to the release notes
  • replace refineLit and refine in the README.md
  • document Predicate.isConstant
  • update error message in RefineM
  • fix #38

Avoid using WeakWitness

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.

Various improvements

  • mention in README that there are also refineMV and refineV
  • remove deprecated refine and refineLit
  • write design document that explains the decision that led to the current design
  • rename point.md to custom_predicates.md
  • rename utils.md to util_string.md
  • add section to README explaining PostErasure*.scala
  • mention auto earlier in the README
  • check how much boxing a refined AnyVal 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 box

Run tests for Scala.js

They are currently disabled because ScalaCheck is a JVM-only dependency in build.sbt.

Add complete example how to use the library

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.