Skip to content

Commit 716b3a0

Browse files
committed
Docs and tests
1 parent c6c5bd1 commit 716b3a0

14 files changed

+508
-2
lines changed

compiler/test/dotc/pos-from-tasty.blacklist

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,14 @@ repeatedArgs213.scala
1717
default-super.scala
1818

1919
# Need to implement printing of match types
20-
matchtype.scala
20+
matchtype.scala
21+
22+
# Failure to print opaque types
23+
opaque.scala
24+
opaque-nullable.scala
25+
opaque-goups.scala
26+
opaque-groups.scala
27+
opaque-digits.scala
28+
opaque-immutable-array.scala
29+
opaque-propability.scala
30+
tagging.scala

docs/docs/internals/syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ LocalModifier ::= ‘abstract’
291291
| ‘sealed’
292292
| ‘implicit’
293293
| ‘lazy’
294-
| ‘transparent
294+
| ‘opaque
295295
| ‘inline’
296296
| ‘erased’
297297
AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier]

docs/docs/reference/opaques.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
layout: doc-page
3+
title: "Opaque Type Aliases"
4+
---
5+
6+
Opaque types aliases provide type abstraction without any overhead. Example:
7+
8+
```scala
9+
opaque type Logarithm = Double
10+
```
11+
12+
This introduces `Logarithm` as a new type, which is implemented as `Double` but is different from it. The fact that `Logarithm` is the same as `Double` is only known in the companion object of `Logarithm`. Here is a possible companion object:
13+
14+
```scala
15+
object Logarithm {
16+
17+
// These are the ways to lift to the logarithm type
18+
def apply(d: Double): Logarithm = math.log(d)
19+
20+
def safe(d: Double): Option[Logarithm] =
21+
if (d > 0.0) Some(math.log(d)) else None
22+
23+
// This is the first way to unlift the logarithm type
24+
def exponent(l: Logarithm): Double = l
25+
26+
// Extension methods define opaque types' public APIs
27+
implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal {
28+
// This is the second way to unlift the logarithm type
29+
def toDouble: Double = math.exp(`this`)
30+
def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that))
31+
def *(that: Logarithm): Logarithm = Logarithm(`this` + that)
32+
}
33+
}
34+
```
35+
36+
The companion object contains with the `apply` and `safe` methods ways to convert from doubles to `Logarithm` values. It also adds an `exponent` function and a decorator that implements `+` and `*` on logarithm values, as well as a conversion `toDouble`. All this is possible because within object `Logarithm`, the type `Logarithm` is just an alias of `Double`.
37+
38+
Outside the companion object, `Logarithm` is treated as a new abstract type. So the
39+
following operations would be valid because they use functionality implemented in the `Logarithm` object.
40+
41+
```scala
42+
val l = Logarithm(1.0)
43+
val l3 = l * l2
44+
val l4 = l + l2
45+
```
46+
47+
But the following operations would lead to type errors:
48+
49+
```scala
50+
val d: Double = l // error: found: Logarithm, required: Double
51+
val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm
52+
l * 2 // error: found: Int(2), required: Logarithm
53+
l / l2 // error: `/` is not a member fo Logarithm
54+
```
55+
56+
For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html).

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ sidebar:
4949
url: docs/reference/inline.html
5050
- title: Meta Programming
5151
url: docs/reference/principled-meta-programming.html
52+
- title: Opaque Type Aliases
53+
url: docs/reference/opaques.html
5254
- title: By-Name Implicits
5355
url: docs/reference/implicit-by-name-parameters.html
5456
- title: Auto Parameter Tupling

tests/neg/opaque.scala

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
object opaquetypes {
2+
3+
opaque val x: Int = 1 // error
4+
5+
opaque class Foo // error
6+
7+
opaque type T // error
8+
9+
opaque type U <: String // error
10+
11+
opaque type Fix[F[_]] = F[Fix[F]] // error: cyclic
12+
13+
opaque type O = String
14+
15+
val s: O = "" // error
16+
17+
object O {
18+
val s: O = "" // should be OK
19+
}
20+
21+
}
22+
23+
object logs {
24+
opaque type Logarithm = Double
25+
26+
object Logarithm {
27+
28+
// These are the ways to lift to the logarithm type
29+
def apply(d: Double): Logarithm = math.log(d)
30+
31+
def safe(d: Double): Option[Logarithm] =
32+
if (d > 0.0) Some(math.log(d)) else None
33+
34+
// This is the first way to unlift the logarithm type
35+
def exponent(l: Logarithm): Double = l
36+
37+
// Extension methods define opaque types' public APIs
38+
implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal {
39+
// This is the second way to unlift the logarithm type
40+
def toDouble: Double = math.exp(`this`)
41+
def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that))
42+
def *(that: Logarithm): Logarithm = Logarithm(`this` + that)
43+
}
44+
}
45+
46+
val l = Logarithm(2.0)
47+
val d: Double = l // error: found: Logarithm, required: Double
48+
val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm
49+
l * 2 // error: found: Int(2), required: Logarithm
50+
l / l2 // error: `/` is not a member fo Logarithm
51+
}

tests/neg/tagging.scala

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import scala.reflect.ClassTag
2+
object tagging {
3+
4+
// Tagged[S, T] means that S is tagged with T
5+
opaque type Tagged[X, Y] = X
6+
7+
object Tagged {
8+
def tag[S, T](s: S): Tagged[S, T] = (s: S)
9+
def untag[S, T](st: Tagged[S, T]): S = st
10+
11+
def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs
12+
def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst
13+
14+
implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] =
15+
ct
16+
}
17+
18+
type @@[S, T] = Tagged[S, T]
19+
20+
implicit class UntagOps[S, T](st: S @@ T) extends AnyVal {
21+
def untag: S = Tagged.untag(st)
22+
}
23+
24+
implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal {
25+
def untags: F[S] = Tagged.untags(fs)
26+
}
27+
28+
implicit class TagOps[S](s: S) extends AnyVal {
29+
def tag[T]: S @@ T = Tagged.tag(s)
30+
}
31+
32+
implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal {
33+
def tags[T]: F[S @@ T] = Tagged.tags(fs)
34+
}
35+
36+
trait Meter
37+
trait Foot
38+
trait Fathom
39+
40+
val x: Double @@ Meter = (1e7).tag[Meter]
41+
val y: Double @@ Foot = (123.0).tag[Foot]
42+
val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter]
43+
44+
val o: Ordering[Double] = implicitly
45+
val om: Ordering[Double @@ Meter] = o.tags[Meter]
46+
om.compare(x, x) // 0
47+
om.compare(x, y) // error
48+
xs.min(om) // 1.0
49+
xs.min(o) // error
50+
51+
// uses ClassTag[Double] via 'Tagged.taggedClassTag'.
52+
val ys = new Array[Double @@ Foot](20)
53+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
object opaquetypes {
2+
3+
opaque type Fix[F[_]] = F[Fix2[F]]
4+
5+
opaque type Fix2[F[_]] = Fix[F]
6+
7+
object Fix {
8+
def unfold[F[_]](x: Fix[F]): F[Fix]
9+
}
10+
11+
object Fix2 {
12+
def unfold[F[_]](x: Fix2[F]: Fix[F] = x
13+
def fold[F[_]](x: Fix[F]: Fix2[F] = x
14+
}
15+
16+
}

tests/pos/opaque-digits.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
object pkg {
2+
3+
import Character.{isAlphabetic, isDigit}
4+
5+
class Alphabetic private[pkg] (val value: String) extends AnyVal
6+
7+
object Alphabetic {
8+
def fromString(s: String): Option[Alphabetic] =
9+
if (s.forall(isAlphabetic(_))) Some(new Alphabetic(s))
10+
else None
11+
}
12+
13+
opaque type Digits = String
14+
15+
object Digits {
16+
def fromString(s: String): Option[Digits] =
17+
if (s.forall(isDigit(_))) Some(s)
18+
else None
19+
20+
def asString(d: Digits): String = d
21+
}
22+
}

tests/pos/opaque-groups.scala

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package object groups {
2+
trait Semigroup[A] {
3+
def combine(x: A, y: A): A
4+
}
5+
6+
object Semigroup {
7+
def instance[A](f: (A, A) => A): Semigroup[A] =
8+
new Semigroup[A] {
9+
def combine(x: A, y: A): A = f(x, y)
10+
}
11+
}
12+
13+
type Id[A] = A
14+
15+
trait Wrapping[F[_]] {
16+
def wraps[G[_], A](ga: G[A]): G[F[A]]
17+
def unwrap[G[_], A](ga: G[F[A]]): G[A]
18+
}
19+
20+
abstract class Wrapper[F[_]] { self =>
21+
def wraps[G[_], A](ga: G[A]): G[F[A]]
22+
def unwrap[G[_], A](gfa: G[F[A]]): G[A]
23+
24+
final def apply[A](a: A): F[A] = wraps[Id, A](a)
25+
26+
implicit object WrapperWrapping extends Wrapping[F] {
27+
def wraps[G[_], A](ga: G[A]): G[F[A]] = self.wraps(ga)
28+
def unwrap[G[_], A](ga: G[F[A]]): G[A] = self.unwrap(ga)
29+
}
30+
}
31+
32+
opaque type First[A] = A
33+
object First extends Wrapper[First] {
34+
def wraps[G[_], A](ga: G[A]): G[First[A]] = ga
35+
def unwrap[G[_], A](gfa: G[First[A]]): G[A] = gfa
36+
implicit def firstSemigroup[A]: Semigroup[First[A]] =
37+
Semigroup.instance((x, y) => x)
38+
}
39+
40+
opaque type Last[A] = A
41+
object Last extends Wrapper[Last] {
42+
def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga
43+
def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa
44+
implicit def lastSemigroup[A]: Semigroup[Last[A]] =
45+
Semigroup.instance((x, y) => y)
46+
}
47+
48+
opaque type Min[A] = A
49+
object Min extends Wrapper[Min] {
50+
def wraps[G[_], A](ga: G[A]): G[Min[A]] = ga
51+
def unwrap[G[_], A](gfa: G[Min[A]]): G[A] = gfa
52+
implicit def minSemigroup[A](implicit o: Ordering[A]): Semigroup[Min[A]] =
53+
Semigroup.instance(o.min)
54+
}
55+
56+
opaque type Max[A] = A
57+
object Max extends Wrapper[Max] {
58+
def wraps[G[_], A](ga: G[A]): G[Max[A]] = ga
59+
def unwrap[G[_], A](gfa: G[Max[A]]): G[A] = gfa
60+
implicit def maxSemigroup[A](implicit o: Ordering[A]): Semigroup[Max[A]] =
61+
Semigroup.instance(o.max)
62+
}
63+
64+
opaque type Plus[A] = A
65+
object Plus extends Wrapper[Plus] {
66+
def wraps[G[_], A](ga: G[A]): G[Plus[A]] = ga
67+
def unwrap[G[_], A](gfa: G[Plus[A]]): G[A] = gfa
68+
implicit def plusSemigroup[A](implicit n: Numeric[A]): Semigroup[Plus[A]] =
69+
Semigroup.instance(n.plus)
70+
}
71+
72+
opaque type Times[A] = A
73+
object Times extends Wrapper[Times] {
74+
def wraps[G[_], A](ga: G[A]): G[Times[A]] = ga
75+
def unwrap[G[_], A](gfa: G[Times[A]]): G[A] = gfa
76+
implicit def timesSemigroup[A](implicit n: Numeric[A]): Semigroup[Times[A]] =
77+
Semigroup.instance(n.times)
78+
}
79+
80+
opaque type Reversed[A] = A
81+
object Reversed extends Wrapper[Reversed] {
82+
def wraps[G[_], A](ga: G[A]): G[Reversed[A]] = ga
83+
def unwrap[G[_], A](gfa: G[Reversed[A]]): G[A] = gfa
84+
implicit def reversedOrdering[A](implicit o: Ordering[A]): Ordering[Reversed[A]] =
85+
o.reverse
86+
}
87+
88+
opaque type Unordered[A] = A
89+
object Unordered extends Wrapper[Unordered] {
90+
def wraps[G[_], A](ga: G[A]): G[Unordered[A]] = ga
91+
def unwrap[G[_], A](gfa: G[Unordered[A]]): G[A] = gfa
92+
implicit def unorderedOrdering[A]: Ordering[Unordered[A]] =
93+
Ordering.by(_ => ())
94+
}
95+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
object ia {
2+
3+
import java.util.Arrays
4+
5+
opaque type IArray[A1] = Array[A1]
6+
7+
object IArray {
8+
inline def initialize[A](body: => Array[A]): IArray[A] = body
9+
inline def size[A](ia: IArray[A]): Int = ia.length
10+
inline def get[A](ia: IArray[A], i: Int): A = ia(i)
11+
12+
// return a sorted copy of the array
13+
def sorted[A <: AnyRef : math.Ordering](ia: IArray[A]): IArray[A] = {
14+
val arr = Arrays.copyOf(ia, ia.length)
15+
scala.util.Sorting.quickSort(arr)
16+
arr
17+
}
18+
19+
// use a standard java method to search a sorted IArray.
20+
// (note that this doesn't mutate the array).
21+
def binarySearch(ia: IArray[Long], elem: Long): Int =
22+
Arrays.binarySearch(ia, elem)
23+
}
24+
25+
// same as IArray.binarySearch but implemented by-hand.
26+
//
27+
// given a sorted IArray, returns index of `elem`,
28+
// or a negative value if not found.
29+
def binaryIndexOf(ia: IArray[Long], elem: Long): Int = {
30+
var lower: Int = 0
31+
var upper: Int = IArray.size(ia)
32+
while (lower <= upper) {
33+
val middle = (lower + upper) >>> 1
34+
val n = IArray.get(ia, middle)
35+
36+
if (n == elem) return middle
37+
else if (n < elem) lower = middle + 1
38+
else upper = middle - 1
39+
}
40+
-lower - 1
41+
}
42+
}

0 commit comments

Comments
 (0)