refined icon indicating copy to clipboard operation
refined copied to clipboard

Draft macroless ==>

Open erikerlandson opened this issue 5 years ago • 8 comments

A candidate fix for #755 that refactors ==> to be macroless: if a valid inference cannot be proven, then P1 ==> P2 will fail to manifest. It no longer has a boolean field that is checked via macro.

Introduces a compile-time version of Refined called RefinedLT, which tests a literal type L's value against a corresponding predicate P. So the use of macros has been pushed from ==> to RefinedLT, where the macros are used to extract literal-type value and run the validator against it.

This PR is currently a bit rough - a few unit tests are failing, or failing to compile, which appear to work OK when I run them in the REPL. I also hacked around the old .isValid based unit-testing using a shim that made kittens cry.

erikerlandson avatar Apr 14 '20 15:04 erikerlandson

I have this passing[*] validateJVM locally, but it has to build against a version of singleton-ops with https://github.com/fthomas/singleton-ops/issues/134, I'm using a publishLocal against head of master.

[*] a few things are disabled

erikerlandson avatar Apr 14 '20 17:04 erikerlandson

I applause the macroless ==>!

One quick question: Is RefinedLT only required to implement equalValidateInference or are there other reasons or instances it is required for?

fthomas avatar Apr 14 '20 19:04 fthomas

@fthomas that's right, I only implemented RefinedLT to solve equalValidateInference. It seemed possibly useful as an implicit tool in its own right. For example, expressing refined-like constraints on type variables. But that's purely speculative.

erikerlandson avatar Apr 14 '20 19:04 erikerlandson

Probably obvious from the PR diff, and my comments, but worth calling out that this does add singleton-ops as a new dependency.

erikerlandson avatar Apr 15 '20 23:04 erikerlandson

My thoughts with regards to equalValidateInference: If we accept that the Inference#isValid field was a mistake, then equalValidateInference was a mistake too. The instance was added without explanation in https://github.com/fthomas/refined/commit/f1812d88042b143b826b8ce423d13501a6c1a197 and is in the library since version 0.1.0. I guess my reasoning for adding it was something like this: if Inference requires a Boolean we can go from any value T Refined Equal[U] (which has the value U) to T Refined P by applying P to U to obtain that Boolean. So that instance either requires the questionable isValid field or another macro (RefinedLT). I think if we want to put refined in a better position for Scala 3 we should not trade one macro for another and remove equalValidateInference altogether.

WDYT, @erikerlandson?

fthomas avatar Apr 16 '20 19:04 fthomas

I think if we want to put refined in a better position for Scala 3 we should not trade one macro for another and remove equalValidateInference altogether.

@fthomas I have no use cases that would need the equalValidateInference rule. If you are OK with removing it, it seems like a good trade-off for truly reducing the number of macros.

The role of expressing predicate constraints on literal types is arguably the domain of singleton-ops, using forms like OpAuxBoolean[P[T], true]

erikerlandson avatar Apr 16 '20 19:04 erikerlandson

just as a use-case reference, here is how I'm applying ==> to constrain operations in type sound ways: https://github.com/erikerlandson/coulomb/blob/refined/coulomb-refined/src/main/scala/coulomb/refined/package.scala#L154

erikerlandson avatar Apr 18 '20 21:04 erikerlandson

@fthomas I removed RefinedLT and equalValidateInference. There were two impacts.

  1. this inference is no longer possible:
  property("Exists[A] ==> Exists[B]") = secure {
    Inference[Contains[W.`'5'`.T], Exists[Digit]].isValid
  }
  1. this assignment no longer compiles
    val a: Char Refined Equal[W.`'0'`.T] = '0'
    val b: Char Refined Digit = a

erikerlandson avatar Apr 20 '20 19:04 erikerlandson