.. include:: ../../roles.rst The Ada language - personal scratchpad ====================================== This is a mostly un-organized collection of notes I took while learning the language. Or rather, re-learning, because I've learned Ada many years ago, but the knowledge faded away over many years of not using it. Case insensitive language ------------------------- Everything, identifiers, package names, keywords is case-**insensitive** in Ada. This leads to a common practice of using long and often descriptive names for identifiers. While this makes the language often appear as verbose, it also means that Ada code is usually well readable and avoids cryptic names for identifiers or functions. Mixed snake case ---------------- Most style guides encourage to use mixed `snake case `_ for identifiers. Instead of ``move_point`` or ``movePoint`` (camel case) in Ada you'll very often see ``Move_Point`` for the same identifier. Since the language is case insensitive, the capitalized parts are purely for aesthetics. It is also good practice to avoid single character identifiers like ``i`` or ``f``, except for very short-lived identifiers (e.g. in loops). Identifiers starting or ending with an underscore are not allowed in Ada. Variable shadowing ------------------ Ada allows variable shadowing in most cases. Local variables shadow variables of the same name declared in higher level blocks. However, the compiler organizes variables in name spaces and it is possible to access variables using fully qualified names when blocks are labeled. The name of the current subprogram is always an implicit namespace. .. code-block:: ada :linenos: :caption: Shadowing of variable names procedure Shadow_Test is var : Integer := 0; begin Ada.Text_IO.Put_Line("The var is: " & Shadow_Test.var'Image); inner: declare -- this shadows the variable declared in line 2 var : Float := 10.0; begin -- but you can still access the outer variable by using its fully qualified name Ada.Text_IO.Put_Line("The outer var is: " & Shadow_Test.var'Image); Ada.Text_IO.Put_Line("The inner var is: " & var'Image); Shadow_Test.var := 20; end inner; Ada.Text_IO.Put_Line("The outer var is: " & Shadow_Test.var'Image); end Shadow_Test; By default, the GNAT compiler will warn you when a variable gets shadowed by a local declaration. Difference WITH vs USE ---------------------- In Ada, importing modules (informally known as `packages`) is a two step process. The `WITH` keyword makes a package available to the current module. `USE` integrates it into the top level namespace. USE is therefore optional and should not always be used, particularly to avoid name conflicts. Suppose, you have a package `Lib` that defines a method `Foo`. To use it you need to do `WITH Lib` and you can then use the method in your code by specifying the fully qualified name `Lib.Foo`. If you, however, `USE Lib` you can use `Foo` without prefixing it with its package name. An often seen construct in Ada are therefore statements like: :ada:`WITH Ada.Text_IO; USE Ada.Text_IO;`. * When you WITH and/or USE a package in the spec (.ads file), you do not need to do the same in the implementation (.adb) file of your module. * The compiler will warn you about unused packages when the `-gnatu` switch has been selected. You can silence this warnings (which are usually harmless) by using `-gnatU` (note the capital U). However, importing too man packages can lead to name conflicts which can result in compilation errors. Classes, methods, functions --------------------------- Ada allows object-oriented design and offers all the tools necessary. You can have classes (records in Ada) and methods working on instances of such records. The syntax is, however, different from known OO langauges like C++ or Java. Ada does not know the concept of member functions, but it does know the concept of dynamic dispatching, inheritance and interfaces. Member functions (method) are, however, defined in a different way and are not part of the class (record) itself. .. code-block:: ada :linenos: :caption: Example type Point is tagged record x, y, z : Coord; end Point; procedure Move_PointX(Self : in Point; delta : Coord) is begin Self.x := Self.x + delta; end Move_PointX; Here we have a record describing a point in 3d space with 3 coordinates (assume ``Coord`` is a custom floating point type that can only take values valid in our coordinate system). The method ``Move_PointX`` shall translate the x-coordinate of our point. Its signature has two arguments, the first is of type ``Point`` and THIS is what makes the procedure a quasi-member of ``Point``. Such methods are called **primitives** in Ada slang. The compiler then allows objects of type ``Point`` to use that procedure like a normal member method. You can use :ada:`p.Move_PointX(3.0);` (called dot-notation) to increase x by 3 and this would be exactly the same as calling :ada:`Move_PointX(p, 3.0);` in which case you must pass a value for the object to work on. When using the dot notation, you do NOT need to specify a value for ``Self``, the compiler does that for you and this works like the implicit this pointer in C++. The method definition must include it, but the compiler will automatically translate method calls. In C++ or Java, the method definition is part of the class definition and the compiler will call class methods with an implicit ``this``. Note also that ``Self`` is not a mandatory name, it's just convention to use names like ``Self`` or ``This`` but you can use whatever you want. In Ada, this is called the dispatching parameter and you can use dot notation ONLY if the dispatching parameter is the first in the list of arguments. Floating point arithmetics -------------------------- How to express NaN (not a number)? Use :ada:`T'Invalid_Value` where ``T`` is any floating point data type. How to check for NaN / infinity? Use :ada:`x /= x` where ``x`` is any floating point data type. How to express :java:`Double.MIN_NORMAL`? [#fe1]_ Use :ada:`T'Model_Small` for any floating point type ``T``. How to find `ULP `_? Use :ada:`T'Model_Epsilon` to find the number with the minimum precision for any floating point type ``T``. .. _aliased: What aliased means ------------------ You may have seen a compiler error stating ``main.adb:52:20: error: prefix of "Access" attribute must be aliased`` so what does this mean? It means you must tell the compiler explicitly by using the :ada:`aliased` keyword that a variable must have a memory location. The Ada compiler is free to decide whether a variable is stored at a memory address (like on the stack or the heap) or just kept in a CPU register. The latter is often better and more performant for short-lived variables, but has a significant limitation: Such a variable cannot be accessed with an :doc:`access type `, because an access type needs a memory address. Internally, access types are pointers and there is no way to have a pointer addressing a CPU register. So, declare your variable like so: :ada:`foo : aliased Long_Float := 0.0;` Terminology in Ada ------------------ When learning Ada, it's easy to get confused by terminology which can be quite a bit different from other languages. Methods or functions vs subprograms In Ada, the term *method* is rarely, if ever, used. We use the term *subprogram* which is either a procedure or a function. The difference is that a function **must** return a value and a procedure can not. A procedure is basically what a **void** function is in C++ or Java. Primitives Primitives is the term that is used for class methods, i.E. methods that work on objects instantiated from tagged records. Ada does not know the concept of implicit ``this`` and primitives must always specify the controlling parameter in explicit form. A typical declaration of a primitive would look like: :ada:`procedure Foo(Self : in out T; v : Integer);` It declares a subprogram that takes 2 parameters. The first (``Self``) is of type ``T`` which must be a tagged record. It's the controlling parameter and its presence in the first place of the formal arguments enables you to use the dot notation with primitives. Assume ``Bar`` is of type ``T`` then you can call either :ada:`Bar.Foo(10);` or :ada:`Foo(Bar, 10);`. Either way, the 10 is passed to the formal argument ``v``, the dot-notation automatically passes ``Bar`` to ``T``. Access Types This is the term we use for pointers which are really pointer-like data types in Ada. An access type can be used to access anything – a simple scalar type like :ada:`Integer`, a variable holding a ``record``, an ``array`` or even allows access to a function. Internally, access types are implemented as pointers and that's why the target object needs to be declared as :ref:`aliased `. -------------------------- .. [#fe1] Smallest possible positive normal number as defined in the IEEE 754 specifications.