{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "

Previous

\n", "

Next

\n", "

Tour of Scala

\n", "
\n", "\n", "# Annotations\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mobject\u001b[39m \u001b[36mDeprecationDemo\u001b[39m" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "object DeprecationDemo extends App {\n", " @deprecated(\"deprecation message\", \"release # which deprecates method\")\n", " def hello = \"hola\"\n", "\n", " hello \n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This will compile but the compiler will print a warning: \"there was one deprecation warning\".\n", "\n", "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.\n", "\n", "\n", "## Annotations that ensure correctness of encodings\n", "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:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "\u001b[32mimport \u001b[39m\u001b[36mscala.annotation.tailrec\n", "\n", "\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mfactorial\u001b[39m" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import scala.annotation.tailrec\n", "\n", "def factorial(x: Int): Int = {\n", "\n", " @tailrec\n", " def factorialHelper(x: Int, accumulator: Int): Int = {\n", " if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x)\n", " }\n", " factorialHelper(x, 1)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "cmd2.sc:6: could not optimize @tailrec annotated method factorialHelper: it contains a recursive call not in tail position\n", " if (x == 1) 1 else x * factorialHelper(x - 1)\n", " ^Compilation Failed" ] }, { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed" ] } ], "source": [ "import scala.annotation.tailrec\n", "\n", "def factorial(x: Int): Int = {\n", " @tailrec\n", " def factorialHelper(x: Int): Int = {\n", " if (x == 1) 1 else x * factorialHelper(x - 1)\n", " }\n", " factorialHelper(x)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We would get the message \"Recursive call not in tail position\".\n", "\n", "\n", "## Annotations affecting code generation\n", "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.\n", "\n", "### Java Annotations ###\n", "When writing Scala code which interoperates with Java, there are a few differences in annotation syntax to note.\n", "**Note:** Make sure you use the `-target:jvm-1.8` option with Java annotations.\n", "\n", "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" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@interface Source {\n", " public String URL();\n", " public String mail();\n", "}\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And then apply it as follows" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@Source(URL = \"http://coders.com/\",\n", " mail = \"support@coders.com\")\n", "public class MyClass extends HisClass ...\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An annotation application in Scala looks like a constructor invocation, for instantiating a Java annotation one has to use named arguments:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@Source(URL = \"http://coders.com/\",\n", " mail = \"support@coders.com\")\n", "class MyScalaClass ...\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@interface SourceURL {\n", " public String value();\n", " public String mail() default \"\";\n", "}\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And then apply it as follows" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@SourceURL(\"http://coders.com/\")\n", "public class MyClass extends HisClass ...\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, Scala provides the same possibility" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@SourceURL(\"http://coders.com/\")\n", "class MyScalaClass ...\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@SourceURL(value = \"http://coders.com/\",\n", " mail = \"support@coders.com\")\n", "public class MyClass extends HisClass ...\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scala provides more flexibility in this respect" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "@SourceURL(\"http://coders.com/\",\n", " mail = \"support@coders.com\")\n", " class MyScalaClass ...\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

Previous

\n", "

Next

\n", "

Tour of Scala

\n", "
" ] } ], "metadata": { "kernelspec": { "display_name": "Scala (2.13)", "language": "scala", "name": "scala213" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.13.1" } }, "nbformat": 4, "nbformat_minor": 4 }