Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Remove redundant restriction at compile-time #123

Merged
merged 1 commit into from
Jun 3, 2023

Conversation

Iltotore
Copy link
Owner

@Iltotore Iltotore commented Jun 2, 2023

Should fix #122

This now compiles:

opaque type FirstName = String :| True
object FirstName extends RefinedTypeOps[FirstName]
val rawName = "Raf"
val name = FirstName(rawName)

@antonkw, does it solve your problem?

@antonkw
Copy link
Contributor

antonkw commented Jun 2, 2023

@Iltotore
Technically is what I addressed, looks great, thank you!

I still worry about targeting.
As someone who already familiar with approach and Iron itself, I will recognize that opaque type FirstName = String :| True is literally implementation of Value Object (in DDD terminology).

But anyone who quickly glanced can consider it as wierd validation but not tiny wrapper.

On my end, I will do following things:

  • Will add more notes to the site to emphasize that there is a way to use library with opaque types and True constraint to fully mimic newtypes behaviour.
  • Write a note for my https://antonkw.github.io/, not something popular but indexing will do the job :)
  • probably prepare a talk at something like Scalar, bacause opaque types are everywhere but all the time discussions end with "what a pity that we can't use the to implement Value Object"

And I still suggest to refine the naming.
I mean literally make an alias for True to have Newtype/ValueObject/Pure that saying loud "I am just business domain value without validation".

If you open to that suggestion, I would open the discussion in the issue and involve more contributors to derive best-sounding option. Or probably I'll be voted out and that is something redundant in general :)

@Iltotore
Copy link
Owner Author

Iltotore commented Jun 2, 2023

An alias for True looks legit to me. Sure you can open an issue!

@Iltotore Iltotore force-pushed the fix/redundant-restriction branch from 0d2182e to 0f42931 Compare June 2, 2023 16:00
@Iltotore
Copy link
Owner Author

Iltotore commented Jun 2, 2023

However, I am not sure that Pure is a good name. I think we have three possibilities:

  • Make an alias for the True constraint like Pure
  • Make a type alias for A :| True like NewType[A]
  • Make a type alias for RefinedTypeOps[A :| True] like RefinedTypeOps.NewType[A]

Note: A is the "raw" type like String

@antonkw
Copy link
Contributor

antonkw commented Jun 2, 2023

I think the two first options work together well.
Pure slightly withdraws the notion of "constraint", which is good. Plus, it is convenient to have String :| Pure when you create a "placeholder" with the intention to replace it with some sensible constraint. NewType[A] Is not as easy to refactor as A :| Pure.
And NewType[A] as alias for A :| Pure will be immediately understandable for original ne type users.
You wrap opaque type with NewType and receive what you had for @newtype

@Iltotore
Copy link
Owner Author

Iltotore commented Jun 2, 2023

Fair enough. I will merge this PR during the weekend and open (or let you do if you prefer) an issue for newcomers.

@antonkw
Copy link
Contributor

antonkw commented Jun 2, 2023

or let you do if you prefer

I am happy to help :)

@Iltotore Iltotore merged commit 69fb90d into main Jun 3, 2023
@Iltotore Iltotore deleted the fix/redundant-restriction branch June 3, 2023 09:09
@nkgm
Copy link

nkgm commented Feb 8, 2024

This doesn't compile for me. Am I reading this wrong?

opaque type MyUUID = UUID :| Pure
object MyUUID extends RefinedTypeOps.Transparent[MyUUID]

val myuuid = MyUUID(UUID.randomUUID())
[error] 31 |val myuuid = MyUUID(UUID.randomUUID())
[error]    |                    ^^^^^^^^^^^^^^^^^
[error]    |-- Constraint Error --------------------------------------------------------
[error]    |Cannot refine value at compile-time because the predicate cannot be evaluated.
[error]    |This is likely because the condition or the input value isn't fully inlined.
[error]    |
[error]    |To test a constraint at runtime, use one of the `refine...` extension methods.
[error]    |
[error]    |Inlined input: java.util.UUID.randomUUID()
[error]    |Inlined condition: {
[error]    |  val value$proxy2: java.util.UUID = java.util.UUID.randomUUID()
[error]    |
[error]    |  (true: scala.Boolean)
[error]    |}
[error]    |Message: Always valid
[error]    |----------------------------------------------------------------------------
[error]    |----------------------------------------------------------------------------
[error]    |Inline stack trace
[error]    |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]    |This location contains code that was inlined from types.scala:31
[error]     ----------------------------------------------------------------------------

@Iltotore
Copy link
Owner Author

Iltotore commented Feb 9, 2024

@nkgm non-AnyVal or String types are currently not checkable at compile-time but might be in the future. See #147. In your case, you can use assume or applyUnsafe instead since your constraint is Pure.

Also for opaque types you cannot use RefinedTypeOps.Transparent. You need to use RefinedTypeOps[UUID, Pure, MyUUID].

@nkgm
Copy link

nkgm commented Feb 9, 2024

Hi @Iltotore, and thanks for all your hard work in making this possible. I'm aware of the alternatives, just trying to be concise and improve my understanding where I can. I checked #147, but it's the same with Long:

opaque type MyLong = Long :| Pure
object MyLong extends RefinedTypeOps[Long, Pure, MyLong]

val myId = MyLong(System.currentTimeMillis())

As noted in RefinedTypeOps.apply:

if it isn't evaluable at compile-time, the compilation is aborted

So unless I'm missing something, the problem lies with the fact that the value can't be inlined. I can see how this is a requirement for actual constraints, but could this restriction not be relaxed for Pure?

@Iltotore
Copy link
Owner Author

Iltotore commented Feb 9, 2024

So unless I'm missing something, the problem lies with the fact that the value can't be inlined

The error here is because System.currentTimeMillis is a runtime value that might have a side effect thus the proxy preventing it to be inlined. However you're right: we could add another conversion or a condition in the compile-time refinement macro to avoid this problem for Pure. 👍

I suggest you to open a new issue instead of discussing in this already closed PR discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fake constraint for newtypes with no underlying validation
3 participants