Skip to content

Add code tabs for _tour/annotations #2566

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

Merged
merged 4 commits into from
Oct 5, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 117 additions & 23 deletions _tour/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,39 @@ redirect_from: "/tutorials/tour/annotations.html"
---

Annotations associate meta-information with definitions. For example, the annotation `@deprecated` before a method causes the compiler to print a warning if the method is used.
```

{% tabs annotations_1 class=tabs-scala-version %}
{% tab 'Scala 2' for=annotations_1 %}
```scala mdoc:fail
object DeprecationDemo extends App {
@deprecated("deprecation message", "release # which deprecates method")
def hello = "hola"

hello
hello
}
```
{% endtab %}
{% tab 'Scala 3' for=annotations_1 %}
```scala
object DeprecationDemo extends App:
@deprecated("deprecation message", "release # which deprecates method")
def hello = "hola"

hello
```
{% endtab %}
{% endtabs %}

This will compile but the compiler will print a warning: "there was one deprecation warning".

An annotation clause applies to the first definition or declaration following it. More than one annotation clause may precede a definition and declaration. The order in which these clauses are given does not matter.


## Annotations that ensure correctness of encodings
Certain annotations will actually cause compilation to fail if a condition(s) is not met. For example, the annotation `@tailrec` ensures that a method is [tail-recursive](https://en.wikipedia.org/wiki/Tail_call). Tail-recursion can keep memory requirements constant. Here's how it's used in a method which calculates the factorial:

{% tabs annotations_2 class=tabs-scala-version %}
{% tab 'Scala 2' for=annotations_2 %}
```scala mdoc
import scala.annotation.tailrec

Expand All @@ -38,8 +56,26 @@ def factorial(x: Int): Int = {
factorialHelper(x, 1)
}
```
The `factorialHelper` method has the `@tailrec` which ensures the method is indeed tail-recursive. If we were to change the implementation of `factorialHelper` to the following, it would fail:
{% endtab %}
{% tab 'Scala 3' for=annotations_2 %}
```scala
import scala.annotation.tailrec

def factorial(x: Int): Int =

@tailrec
def factorialHelper(x: Int, accumulator: Int): Int =
if x == 1 then accumulator else factorialHelper(x - 1, accumulator * x)
factorialHelper(x, 1)
```
{% endtab %}
{% endtabs %}

The `factorialHelper` method has the `@tailrec` which ensures the method is indeed tail-recursive. If we were to change the implementation of `factorialHelper` to the following, it would fail:

{% tabs annotations_3 class=tabs-scala-version %}
{% tab 'Scala 2' for=annotations_3 %}
```scala mdoc:fail
import scala.annotation.tailrec

def factorial(x: Int): Int = {
Expand All @@ -50,76 +86,134 @@ def factorial(x: Int): Int = {
factorialHelper(x)
}
```
We would get the message "Recursive call not in tail position".
{% endtab %}
{% tab 'Scala 3' for=annotations_3 %}
```scala
import scala.annotation.tailrec

def factorial(x: Int): Int =
@tailrec
def factorialHelper(x: Int): Int =
if x == 1 then 1 else x * factorialHelper(x - 1)
factorialHelper(x)
```
{% endtab %}
{% endtabs %}

We would get the message "Recursive call not in tail position".

## Annotations affecting code generation

{% tabs annotations_4 class=tabs-scala-version %}
{% tab 'Scala 2' for=annotations_4 %}

Some annotations like `@inline` affect the generated code (i.e. your jar file might have different bytes than if you hadn't used the annotation). Inlining means inserting the code in a method's body at the call site. The resulting bytecode is longer, but hopefully runs faster. Using the annotation `@inline` does not ensure that a method will be inlined, but it will cause the compiler to do it if and only if some heuristics about the size of the generated code are met.

{% endtab %}
{% tab 'Scala 3' for=annotations_4 %}

Some annotations like `@main` affect the generated code (i.e. your jar file might have different bytes than if you hadn't used the annotation). A `@main` annotation on a method generates an executable program that calls the method as an entry point.

{% endtab %}
{% endtabs %}

### Java Annotations ###
When writing Scala code which interoperates with Java, there are a few differences in annotation syntax to note.
**Note:** Make sure you use the `-target:jvm-1.8` option with Java annotations.

Java has user-defined metadata in the form of [annotations](https://docs.oracle.com/javase/tutorial/java/annotations/). A key feature of annotations is that they rely on specifying name-value pairs to initialize their elements. For instance, if we need an annotation to track the source of some class we might define it as

```
{% tabs annotations_5 %}
{% tab 'Java' for=annotations_5 %}
```java
@interface Source {
public String URL();
public String url();
public String mail();
}
```
{% endtab %}
{% endtabs %}

And then apply it as follows

```
@Source(URL = "https://coders.com/",
{% tabs annotations_6 %}
{% tab 'Java' for=annotations_6 %}
```java
@Source(url = "https://coders.com/",
mail = "[email protected]")
public class MyClass extends TheirClass ...
public class MyJavaClass extends TheirClass ...
```
{% endtab %}
{% endtabs %}

An annotation application in Scala looks like a constructor invocation, for instantiating a Java annotation one has to use named arguments:

```
@Source(URL = "https://coders.com/",
{% tabs annotations_7 %}
{% tab 'Scala 2 and 3' for=annotations_7 %}
```scala
@Source(url = "https://coders.com/",
mail = "[email protected]")
class MyScalaClass ...
```
{% endtab %}
{% endtabs %}

This syntax is quite tedious if the annotation contains only one element (without default value) so, by convention, if the name is specified as `value` it can be applied in Java using a constructor-like syntax:

```
{% tabs annotations_8 %}
{% tab 'Java' for=annotations_8 %}
```java
@interface SourceURL {
public String value();
public String mail() default "";
}
```
{% endtab %}
{% endtabs %}

And then apply it as follows
And then apply it as follows:

```
{% tabs annotations_9 %}
{% tab 'Java' for=annotations_9 %}
```java
@SourceURL("https://coders.com/")
public class MyClass extends TheirClass ...
public class MyJavaClass extends TheirClass ...
```
{% endtab %}
{% endtabs %}

In this case, Scala provides the same possibility
In this case, Scala provides the same possibility:

```
{% tabs annotations_10 %}
{% tab 'Scala 2 and 3' for=annotations_10 %}
```scala
@SourceURL("https://coders.com/")
class MyScalaClass ...
```
{% endtab %}
{% endtabs %}

The `mail` element was specified with a default value so we need not explicitly provide a value for it. However, if we need to do it we can not mix-and-match the two styles in Java:
The `mail` element was specified with a default value so we need not explicitly provide a value for it.
However, if we need to provide one then in Java we must also explicitly name the `value` parameter:

```
{% tabs annotations_11 %}
{% tab 'Java' for=annotations_11 %}
```java
@SourceURL(value = "https://coders.com/",
mail = "[email protected]")
public class MyClass extends TheirClass ...
public class MyJavaClass extends TheirClass ...
```
{% endtab %}
{% endtabs %}

Scala provides more flexibility in this respect
Scala provides more flexibility in this respect, so we can choose to only name the `mail` parameter:

```
{% tabs annotations_12 %}
{% tab 'Scala 2 and 3' for=annotations_12 %}
```scala
@SourceURL("https://coders.com/",
mail = "[email protected]")
class MyScalaClass ...
class MyScalaClass ...
```
{% endtab %}
{% endtabs %}