From 9bbe3743b0928808e074013ed92206da391d0af6 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 21 Feb 2022 10:57:04 -0800 Subject: [PATCH 1/2] Tweak Permissions example Add an example usage and a few more words to existing explanation. Also add a few commas. --- .../reference/other-new-features/opaques.md | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/_docs/reference/other-new-features/opaques.md b/docs/_docs/reference/other-new-features/opaques.md index 402440bad90c..fd13d3cee9db 100644 --- a/docs/_docs/reference/other-new-features/opaques.md +++ b/docs/_docs/reference/other-new-features/opaques.md @@ -33,9 +33,9 @@ end MyMath This introduces `Logarithm` as a new abstract type, which is implemented as `Double`. The fact that `Logarithm` is the same as `Double` is only known in the scope where -`Logarithm` is defined which in the above example corresponds to the object `MyMath`. -Or in other words, within the scope it is treated as type alias, but this is opaque to the outside world -where in consequence `Logarithm` is seen as an abstract type and has nothing to do with `Double`. +`Logarithm` is defined, which in the above example corresponds to the object `MyMath`. +Or in other words, within the scope, it is treated as a type alias, but this is opaque to the outside world +where, in consequence, `Logarithm` is seen as an abstract type that has nothing to do with `Double`. The public API of `Logarithm` consists of the `apply` and `safe` methods defined in the companion object. They convert from `Double`s to `Logarithm` values. Moreover, an operation `toDouble` that converts the other way, and operations `+` and `*` are defined as extension methods on `Logarithm` values. @@ -70,13 +70,12 @@ object Access: opaque type PermissionChoice = Int opaque type Permission <: Permissions & PermissionChoice = Int - extension (x: Permissions) - def & (y: Permissions): Permissions = x | y extension (x: PermissionChoice) def | (y: PermissionChoice): PermissionChoice = x | y + extension (x: Permissions) + def & (y: Permissions): Permissions = x | y extension (granted: Permissions) def is(required: Permissions) = (granted & required) == required - extension (granted: Permissions) def isOneOf(required: PermissionChoice) = (granted & required) != 0 val NoPermission: Permission = 0 @@ -101,9 +100,12 @@ where `x | y` means "a permission in `x` *or* in `y` granted". Note that inside the `Access` object, the `&` and `|` operators always resolve to the corresponding methods of `Int`, because members always take precedence over extension methods. -Because of that, the `|` extension method in `Access` does not cause infinite recursion. -Also, the definition of `ReadWrite` must use `|`, -even though an equivalent definition outside `Access` would use `&`. +For that reason, the `|` extension method in `Access` does not cause infinite recursion. + +In particular, the definition of `ReadWrite` must use `|`, the bitwise operator for `Int`, +even though client code outside `Access` would use `&`, the extension method on `Permissions`. +The internal representations of `ReadWrite` and `ReadOrWrite` are identical, but this is not visible to the client, +which is interested only in the semantics of `Permissions`, as in the example below. All three opaque type aliases have the same underlying representation type `Int`. The `Permission` type has an upper bound `Permissions & PermissionChoice`. This makes @@ -115,8 +117,11 @@ object User: import Access.* case class Item(rights: Permissions) + extension (item: Item) + def +(other: Item): Item = Item(item.rights & other.rights) val roItem = Item(Read) // OK, since Permission <: Permissions + val woItem = Item(Write) val rwItem = Item(ReadWrite) val noItem = Item(NoPermission) @@ -128,11 +133,18 @@ object User: assert(!noItem.rights.is(ReadWrite)) assert(!noItem.rights.isOneOf(ReadOrWrite)) + + assert((roItem + woItem).rights.is(ReadWrite)) end User ``` - -On the other hand, the call `roItem.rights.isOneOf(ReadWrite)` would give a type error -since `Permissions` and `PermissionChoice` are different, unrelated types outside `Access`. +On the other hand, the call `roItem.rights.isOneOf(ReadWrite)` would give a type error: +```scala + assert(roItem.rights.isOneOf(ReadWrite)) + ^^^^^^^^^ + Found: (Access.ReadWrite : Access.Permissions) + Required: Access.PermissionChoice +``` +`Permissions` and `PermissionChoice` are different, unrelated types outside `Access`. ### Opaque Type Members on Classes From 93a7a3a53b55ba36053626b1f1df0b956dc5b82c Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 4 Mar 2022 16:58:27 +0000 Subject: [PATCH 2/2] Align Permissions example with its test file --- tests/pos/reference/opaque.scala | 53 ++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/tests/pos/reference/opaque.scala b/tests/pos/reference/opaque.scala index 4dd91dc06fe4..2a1aa0610636 100644 --- a/tests/pos/reference/opaque.scala +++ b/tests/pos/reference/opaque.scala @@ -1,47 +1,49 @@ -object Logarithms { +object MyMath: opaque type Logarithm = Double - object Logarithm { + object Logarithm: + + // These are the two ways to lift to the Logarithm type - // These are the ways to lift to the logarithm type def apply(d: Double): Logarithm = math.log(d) def safe(d: Double): Option[Logarithm] = - if (d > 0.0) Some(math.log(d)) else None - } + if d > 0.0 then Some(math.log(d)) else None + + end Logarithm // Extension methods define opaque types' public APIs extension (x: Logarithm) def toDouble: Double = math.exp(x) def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y)) def * (y: Logarithm): Logarithm = x + y -} -object LogTest { - import Logarithms.* +end MyMath + +object LogTest: + import MyMath.Logarithm import Predef.{any2stringadd as _, *} val l = Logarithm(1.0) val l2 = Logarithm(2.0) val l3 = l * l2 val l4 = l + l2 -} -object Access { + +object Access: opaque type Permissions = Int opaque type PermissionChoice = Int opaque type Permission <: Permissions & PermissionChoice = Int - extension (x: Permissions) - def & (y: Permissions): Permissions = x | y extension (x: PermissionChoice) def | (y: PermissionChoice): PermissionChoice = x | y + extension (x: Permissions) + def & (y: Permissions): Permissions = x | y extension (granted: Permissions) def is(required: Permissions) = (granted & required) == required - extension (granted: Permissions) def isOneOf(required: PermissionChoice) = (granted & required) != 0 val NoPermission: Permission = 0 @@ -49,29 +51,34 @@ object Access { val Write: Permission = 2 val ReadWrite: Permissions = Read | Write val ReadOrWrite: PermissionChoice = Read | Write -} -object User { +end Access + +object User: import Access.* case class Item(rights: Permissions) + extension (item: Item) + def +(other: Item): Item = Item(item.rights & other.rights) val roItem = Item(Read) // OK, since Permission <: Permissions + val woItem = Item(Write) val rwItem = Item(ReadWrite) val noItem = Item(NoPermission) - assert( roItem.rights.is(ReadWrite) == false ) - assert( roItem.rights.isOneOf(ReadOrWrite) == true ) + assert(!roItem.rights.is(ReadWrite)) + assert(roItem.rights.isOneOf(ReadOrWrite)) - assert( rwItem.rights.is(ReadWrite) == true ) - assert( rwItem.rights.isOneOf(ReadOrWrite) == true ) + assert(rwItem.rights.is(ReadWrite)) + assert(rwItem.rights.isOneOf(ReadOrWrite)) - assert( noItem.rights.is(ReadWrite) == false ) - assert( noItem.rights.isOneOf(ReadOrWrite) == false ) + assert(!noItem.rights.is(ReadWrite)) + assert(!noItem.rights.isOneOf(ReadOrWrite)) + assert((roItem + woItem).rights.is(ReadWrite)) // Would be a type error: - // assert( roItem.rights.isOneOf(ReadWrite) == true ) -} + // assert(roItem.rights.isOneOf(ReadWrite)) +end User object o { opaque type T = Int