The Outsider's Guide to Ada
Jarrett, Paul
Ada can be difficult to approach due to using a different vernacular to most other languages, and also having many unfamiliar structures and ways of doing things. This is an overview of the Ada language by someone who is new to the language, for programmers from other languages, kept as neutral and objective as possible. See how syntax falls into four categories and the language allows you to opt into features. Learn how Ada fits together at a high level, with an emphasis on the ways Ada differs, using code samples from open source Ada projects. About a year ago, I still thought Ada looked like COBOL. Since then, I've spent a long time trying to understand the language as a whole, since it's different from many others that I've used. This talk isn't to sell you on the language, but instead to provide a general overview of how the language works. This will complement Jean-Pierre Rosen's "Introduction to Ada for Beginning and Experienced Programmers" talk. If you haven't watched his talk yet, I recommend you do so after this one. If you have watched his talk, this should provide a different perspective to help you understand Ada better.
 Hello, and welcome to the Outsiders Guide to ADA. I'm Paul, a software engineer who normally writes C++. I've been using ADA for some of my hobby projects for almost a year now. I've written in quite a few programming languages, and not many people are familiar with ADA. So this is an overview of the language for programmers from other languages, from the perspective of someone who doesn't write ADA professionally and hasn't been working with ADA very long. This is not an advocacy talk. I can't teach you ADA in 30 minutes, so instead I'm going to show you its capabilities and also how its parts work together to provide a different experience from other languages. I think it's important to show real code, so most of the code samples I'll be showing is real ADA code from the projects I've written over the last year or from other open source projects. I am not a liar, so seek competent and licensed legal counsel before making any business or legal decisions based off of information in this talk. ADA is still in use today. It was released in the early 80s, followed by several revisions, making it a contemporary of C++, not a Fortran and Cobalt. Whereas the lineage of C++ can be traced roughly from ALGOL through BCPL and then C with similar influences, ADA comes from a parallel track from ALGOL through Pascal. The Pascal influence can be seen in the use of colon equals for assignment, which is a statement not an expression like in C++. It also uses single equals for equality, slash equals for not equal, and begin and end keywords instead of curly braces for blocks. In addition, due to the language not being built around object-oriented programming, there are some interesting consequences on program organization and structure. While several paid compilers exist, there is the ADA front-end to GCC, which is called NAT, which is free as in beer and as in freedom. The free software foundation's version of NAT provides a GPLv3 license with a runtime exception, so I've been writing and releasing my code under the Apache 2.0 license. For simplicity within this talk, I will be treating the NAT ecosystem and ADA as interchangeable, as NAT is the predominant open source variant of ADA. In addition to a compiler, NAT also includes a pretty printer for code formatting, a document generator, and other tools. ADA contains a subset called Spark, which provides the capability to formally verify subsets of your program enabled via the Spark mode aspect. There's also a relatively new package manager called AlliR. This enables you to install the NAT toolchain on multiple systems, create, publish, and download crates, which can be libraries or binaries. If you're familiar with Rust, this tool fulfills many of the roles of Rust-up and Cargo. There isn't a considerable open source presence for ADA. The Tiobi ranking has it at number 34. Language, which looks at the number of GitHub issues, pull requests, and stars has it at number 163. ADA doesn't show up on the RedMonk programming language rankings, and it does not appear on the Stack Overflow Developer Survey. The Black Duck open hub shows Rust as having 10 times the number of contributors of ADA and about the same amount of code. The popularity of programming languages metric shows ADA steadily gaining, though there's some question of if this is generated by ADA the language or by Cardano the cryptocurrency, which calls its token ADA. Most ADA tutorials describe the language, but not a possible environment for working in it. I developed an ADA on Windows, Mac, and Linux. On all of these platforms, I used the same method. I installed a free software foundation's NAT front end to GCC using Alier, and then used NAT Studio or Visual Studio Code. Both of these editors are good. My personal preference lately has been using Visual Studio Code. There's an ADA language server, which some people use with VIM, but it also has an extension for Visual Studio Code. For VIM users, there's also a group of plugins you can get called the ADA bundle. I use Alier to start my editor since it will launch it with the appropriate environment variable set. I use a JSON workspace file like shown, along with this Alier config command to start the Visual Studio Code directly. So when I write ADA, my editor looks something like this. A major hurdle in learning the language is that the vocabulary can be quite unfamiliar. I'll be using the vernacular of programmers familiar with C family languages, in particular C++. If you're having trouble with finding something or understanding an ADA-related term afterward, this chart should provide a starting place for looking for what you need. The way ADA is structured is a core which I call Type Safe C, and then three categories of features you can bring in after choosing. The core language includes primitives like integers and floating point types, arrays, functions, and packages for namespacing. This also includes a standard way of defining compilation units. One set of features includes object-oriented programming, compile time generics, and constructors and destructors. I call this C++98 in Pascal because it doesn't include some features we commonly use in more modern C++ versions like move semantics and variadic templates. The second set is built-in concurrency features. You can specify tasks for concurrent computation as well as protected objects to coordinate read and write access to resources used by multiple tasks. The third set relates to modeling features. This includes ranges and constraints you can apply to types, the Spark language subset used for formal verification, and also built-in designed by contract. When I write ADA, I typically use few or no object-oriented tools, the standard library container generics, and designed by contract with pre and post conditions. I've used the concurrency features for data processing in parallel and adding interactive features. The language provides the ability to opt into features. This falls into four categories, structure to delineate compilation units, provide namespaces for functions and types, and to separate code to execute concurrently. Types to define semantics for values by constraining their usage and contents, and provide the mechanism for function overloading. Statements to define computation within our structural elements, and finally controls to provide the compiler additional direction when the other elements are insufficient, which attach to the other elements without obscuring their intent. The physical structure of how ADA programs are written is a more formalized version than that used in C or C++. Specifications in.ADS files are sort of like header files, and bodies in.ADB files are sort of like source files. There's no preprocessor or include directive, and all elements must begin with what's called the context clause. This describes all of the immediate dependencies that this compilation unit depends upon, and can only be provided at the beginning. This means a compiler can see if it has what's required for trying to proceed. The program entry does not need to be called main, and can be specified as part of the build when making executables. The predominant compilation unit is the package. A common pattern ADA syntax is the use of a pair of declarative element, as well as a corresponding body element. Package names are significant, and the usage of dots between elements indicate a package being a child of another. In this example, if a package p.r existed, it would indicate that r subchild of the parent package p. ADA separates declarations from executable code. Sections following the reserved words is or declare indicate declarations, and those following the word begin indicate executable code. Data hiding, also known as encapsulation, happens at the structural level in packages and protected types by starting a section with the reserved word private, or by placing elements in the body only. This is different from many other languages, such as Java, which describe encapsulation at the class level. All of the structural elements exhibit these similar characteristics. This even includes the declare block, which can be used within statements describing execution, to create an additional scope for variables or set up an exception block to catch exceptions. The language permits the declaration of nearly anything in one of these sets of declarations. When writing new features, it's often convenient to split out new packages, functions, tasks, and protected types during refactoring into the declaration section of the function you're working on before moving them to their permanent homes. Structural elements fall upon two axes, whether they are active or passive, and whether they describe sequential behavior or concurrency features. Subprograms, which are either functions which return a value or a procedure which does not, describe reusable sequences of steps. Taskers provide namespacing for other elements and data hiding. They act like classes and subclasses in C++ or Java with regards to the rules for encapsulation. You can hide details from the outside while allowing children of the unit access. Tasks provide concurrent execution of statements, and the body in which they're declared won't exit until they're complete unless they're dynamically allocated. AIDA provides built-in capabilities to segment available CPUs into what's called dispatching domains and also to pin tasks to specific CPUs. Protective types synchronize read and write access to their contained data and can use complex guards to do so. One-off tasks and protected types can be created by just skipping the type reserved word in their declaration. I'm only going to briefly mention types here to facilitate further discussion. In general, types aren't that important to how AIDA works because they tend to work roughly the same all the time. Their purpose is to describe the value set or modified behaviors allowed on variables of a specific type. This takes many forms. The range for a percentage could be described as 0 to 100. For a reference count, a new type, not an alias, might be used to hide the underlying type. What this does is to create a new version of an existing type. Since AIDA does not allow implicit conversion, the type system prevents mixing these types without explicit casting. This allows the type checker to catch bad semantic usage, such as assigning an integer of microseconds to one of nanoseconds. Arrays provide groups of a given number of a type. The index type can be another type and have an arbitrary range. For example, you can create simple maps by using an enum as the index type or prevent the need to remap indices into and out of an array by declaring the array index range as that value range, such as 50 to 100. Limited types prohibit a value from being copied or reassigned. In my project septum, the search's package exposes a heavyweight, uncopyable search type in an opaque way to clients. Packages can encapsulate the detail of a type by marking it as private in the interface. Clients can store and copy the type around in a similar way to a class with private elements in another language. Tagged denotes types which might require dynamic dispatch, which is also called runtime polymorphism or virtual functions. You can also create abstract base classes by adding the abstract reserved word or create interfaces for types, tagged types, limited types, or for tasks and protected objects with the appropriate keywords along with the interface keyword. In addition to providing their behavior, limited and tagged types are passed by reference implicitly. Ada is a strongly typed language, but the important takeaway is that because types are not modules, they merely facilitate the creation of other behavior and are not the main focus of the language. Access types provide reference semantics to Ada. You can't do pointer arithmetic on an access type, it just keeps indirect access to a value elsewhere. Multiple access types can be created for a single type and these are incompatible with each other. These use separate storage pools and you can even specify different pools per access type and even use sub pools for increased granularity. They allow you to dynamically allocate a type and the access all flavor acts like a generic pointer to any value of that type even on the stack. Like in C++, you can also have variations of constant accesses and accesses to constants. Values which you take an access of must be marked as aliased. There's also accessibility checks which prevent you from returning an access to a variable which might leave scope, but you can subvert this with the unchecked access attribute. Since types are not modules, there needs to be a mechanism to describe behavior operating on types. A major way that types push design is through the use of function overloading, also known as ad hoc polymorphism as a primary design mechanism. Because of this, the namespaces of visible packages and the parameters and return types of the available options determine which sub-program gets chosen to be called. How do you define a function then? In other languages, the syntax changes depending upon whether you're attaching that function to a type or not, the visibility of the function, and if and how the input parameter, either implicit or explicit, can be modified, and if that value is copied, moved, or passed by reference or pointer. Especially in C++, there are quite a few ways to pass a parameter depending upon your technical and design intent. Parameters can be provided as inputs, outputs, or both inputs and outputs. When the mode of a parameter is not indicated, it defaults to an input parameter. Parameters are const by default unless they are marked as an output parameter. Anonymous and access types can also be provided, as well as a null exclusion to indicate that an access must be valid, which is like a reference in C++. For object-oriented programming, instead of providing a base class reference or pointer, the tick class attribute of the base class is used, which indicates what's called a class-wide type. Subprograms declared in the same package as the type, which have that type as the first parameter, are known as primitive operations. These get inherited when another type is created from this one. Primitive operations may perform dynamic binding, like a virtual function, and dispatch if the value passed is of a class-wide type. This means if the compiler knows the type of an object, it will be statically bound. If the compiler overloads, wrap the operator with double quotes, but otherwise works similarly to other functions. Since functions and procedures get declared outside of types, this means there isn't much of a distinction in AIDA between what other languages call member functions or methods, static functions, or free functions. Controlled types provide RAII, or resource acquisition is initialization. Types which inherit from AIDA.finalization.controlled can overwrite three procedures, initialized, which gets called after default initialization, adjust, which gets called after the value receives an assignment, and finalize, which cleans up at the end of the scope, like a C++ destructor, and also immediately before value receives an assignment. Limited types cannot be copied or assigned, so for those you inherit from AIDA.finalization.limited control, and there is no adjust in this case. Attributes take the place of additional operators and keywords to denote special properties and operations of types and values. Initially, they're super confusing, but they're just the name of a type or value followed by an apostrophe or what AIDA calls tick. For example, instead of size of and a line of, there's the attribute tick size and tick alignment. Instead of an operator to get AIDA's equivalent of a pointer to a value, you use tick access on the variable, which also works on sub-programs, like a C function pointer. If you want the raw machine address, you can use tick address. To parse a primitive value from a string, there's a function like tick value. Primitives also provide tick min and tick max attributes to return the min or max of a pair of values. For arrays, there's tick length, tick range to provide a range to iterate over, tick first and tick last to get the first and last indices. Some attributes like size and alignment can be controlled with aspects of what's called an attribute definition clause. Aspects allow you to attach additional metadata, options, and descriptions to types and structural elements without disturbing the main syntax. Being here, you can see the line in-type enforces an invariant. Local flags is a packed array of booleans of 32 bits, which is a fancy way of saying to use a 32-bit integer as the underline type. This was done to provide a nicer interface to set binary flags and then pass this value off to term iOS. Insert here will be inlined, but also has a precondition which gets checked prior to being called, and a post-condition checked afterwards. You can see the attribute tick old here being used, which is the associated value prior to the procedure call. TC get ATTR is just a C function being imported and can be used just like a normal AIDA functioning code. AIDAs generics are different from those in other languages, since you instantiate generics at the package and function level and not at the type level. This instantiation is also explicit, meaning you can't just use an ordered map type inline with angled brackets and have it work. This was a major conceptual block for me, as I don't recall ever working with a language which handles generic instantiation this way. Instead, the entire package gets instantiated and then used like any other normal package. Also demonstrated here is the usage of named parameters, which works also with sub-programs. For generics which can be shared, this instantiation can be done once at the compilation unit level, so each package doesn't need to individually instantiate it. Now that we've gone through and overview the language, let's look back at the high level at how these pieces fit together. Let's start with the structural side. You can see how sub-programs and packages can have generic versions, but tasks and threads cannot. You can, however, put tasks and protected objects inside of generic packages. You can see types split out into their various flavors. Implementations are technically not types in Ada, but I'm grouping them in that section since they're distinct from one another. Finally, you can see the controls we have over the other elements. There's quite a few pragmas which have corresponding aspects. There's also a set of pragmas, attributes, and aspects defined as part of the Ada standard, but implementation defined ones are permitted as well. With the available time, I'm going to highlight a few interesting features of Ada. Stringes are arrays of characters and are not null-terminated. Since they are arrays, they include the same bounce checks as other arrays. If needed, you can convert to and from null-terminated C-style strings using the built-in interfaces.c.strings package. The language allows returning variable length arrays from functions, so you can build and return strings from functions. String, like other arrays, is of a static size. If you need a resizable string type, you can use Ada.strings.unbounded.unbounded string. Since strings have a known size, creating arrays of plain strings is not possible unless you pad all of the strings to the same length or dynamically allocate each string. This also applies to keys of maps for which I use the variable-sized unbounded string type. The concurrency features are a complicated subject, so let's just look at an example of tasking in action. Assume we're loading many files from disk, possibly performing processing, and then storing the result in a cache. File loader task defines what's called an entry named wake. Entries are similar to procedures, they can't return any value, and they perform what's called a rendezvous between tasks or protected objects. One task calls the entry, and then the other task or protected object must accept it. I don't use any here, but you can define guards, which are conditions which must be true for a task or protected object to accept an entry. In the inner loop, this task calls an entry on the queue of files to process. This blocks until a file can be dequeued, or times out and exits the loop and hence the task after one second. If there was an element, then it's loaded, processed, and cached. FileCache here is a protected object preventing multiple file loaders from performing writes at the same time. This loop of the task ends by incrementing another protected object monitoring progress, which is used to display a progress report for the user. Note that the FileQ, FileCache, and Progress variables are not declared in this task. Normal rules regarding scope apply, and these variables had been declared in the same function scope as this task, so they are available. Note that all of this is done with built-in features of Ada. This is a sampling of low-level controls. You can import C functions, and then call them as if they were normal Ada functions. Compiler intrinsics work the same way, as in this example from the Atomic project. Inline assembly is allowed. I use this assembly to cause the debugger to break in the appropriate failing assertion when a test would fail in my test suite. When I did the popular ray tracing in One Week in an Ada, I adjusted binary layout to make a struct appropriate to write a BMP file header. Let's look back at the original layering of the language that I mentioned, and group these features. From here, you can see there is a good base to the language in starting out, and you can incrementally add features as you need them. When I built a septum code search tool, I started on the far left with just the basic primitives that I branched out by bringing in design by contract from the modeling group. When I wanted to expand the filtering mechanism, I converted filters to tag types to be able to use runtime polymorphism and dynamic dispatch, and they're still the only class-like type in the system. Most other types are struct-like records, with many being private types, which hide their contents within their containing packages. When I needed to parallelize search, I brought in tasks and converted the file cache to a protected type. My best way to describe Ada is that it's about intent. These examples show how some of the keywords relate to their goals. You describe what you want to have happen, and then you layer on meaning as needed with the additional controls available. The separation of structural elements and types from low-level controls puts the focus on solving the problem while providing customization where you need it. And we are live for questions and answers from Paul Gerrits. Before we start with the first question, let me remind people that they can always join the live chat after the talk ends. For this reason, they have to join the appropriate room in Matrix. So first question that we had, why does Ada need a move semantics, according to you, Paul? Move semantics are very important because they allow you to transfer resources without a copy or a reference. On the surface, it looks like why do we need that? But the important thing, a later question, is what does Ada need? There's a thing in C++ called perfect forwarding, where it's a variadic template, used to something called an arg pack, where you transfer arguments through a function call to call another function as if it had the exact same parameters. And move semantics would allow that, assuming that we had variadic templates and things like that in Ada. So it also expresses the semantic intent of an expiring object, or I'm not going to use these resources anymore, I'm handing an object off as a parameter. I think it's something that Ada actually definitely needs. But you know that the limited type in Ada is returned by, not by copy, but by address. In-out parameters are also passed by address. Tag types are also passed by address all the time. So move semantics already exists in Ada, simply not implemented through variadic templates. Simply not implemented through limited types and return blocks. Yes, it's a difference. It would be like adding a moved reserved word in addition to in and out. It's semantically different, is the point. Because in, it's transferring the resources. Just formalizes it, when you move an object, you cannot access that anymore. The borrower checker will get upset at you and say that the value has been moved out. Because it's a semantic definition of essentially destroying state. You use it for vectors, like copying resources. And there is a move function in Ada containers, in some of the Ada containers, but it's a semantic way of building it into the language. Right. Well, maybe that's something to look into. I hear that Spark has imported the borrower checker from that word. At least something similar to the borrower checker, first, into Spark. So now at compile time, there are some checks to verify the ownership of objects passed by address. So it's not in Ada, indeed. It's an extension to Ada. It's Spark. But yeah, maybe that might be something to look into. Second question, even though I read your website, what were the things you found really difficult for coming from the C++ world? What were the roadblocks you found? The biggest ones were attributes. I read Barnes's book and it was the primary source that I used. And a lot of the tutorials just assume that you know what attributes are. And it's like, I would expect there to be a keyword or something for that. It's not really a good explanation other than it's something that might, it's built in and built onto a value or an underwrite type. And that's not really explained very well in a lot of tutorials. That was a big trip up. And then generics. Normally instantiations are implicit. Like in C++ and Rust, you just use the type. The whole concept of having to instantiate an entire package, yes, it's what C++ does behind the scenes, but most of the time you don't concern yourself with it as much. But that was a huge one. But everything else, in many respects, Ada builds on the same conceptual foundations. Like I call it C++98 and Pascal because I honestly think someone with C++ could get productive in Ada in less than a month. Because there's a lot of conceptual similarity, even though it's a Pascal-based language and not a C-based language. A lot of the stuff is very compilation model system like the ADS, ADB files, very familiar with that. A lot of the stuff just kind of flows directly from C++ understanding. Does that make sense? Yeah. I didn't really catch what you said about the one month. You mean it takes at least a month to get become fluent in Ada? I think I was productive in Ada in less than a month. I was reading standard library code in less than a month. A lot of languages that you'd not normally at that point that fast. That matches my experience too. Because I was once an outsider too. I taught myself Ada, yeah, less than a month is sufficient. The important thing is that some people say we can't use Ada because there's no programmers or we have trouble finding programmers and I think that something that people don't understand is that you could hire C++ programmers and convert them into Ada programmers relatively quickly. Yeah. Awesome. If you were bound to Ada for some reason. That matches our experience too. Have you managed to convert anyone to Ada from C or C++? Conversion isn't really my goal. My goal was primarily to figure out the language, so to speak. Part of the reason I learned it was there wasn't a lot of information and I don't know anyone in person that actually uses Ada at all. This conference is my first experience actually talking to people who use the language outside of the text forms and things. My intent was to understand the language and all the material I've written has tried to be from the objective point of view of laying out the language because a lot of people don't understand how it fits together. I think there's a lot of conceptual ideas that would improve other languages if they would adopt them. Especially from a university point of view. Ada has a lot of concepts that are used and more difficult to learn languages like C++ that I think especially in a university setting it would be easier to get someone into C++ via Ada rather than just dropping them in there from Python or Java. Does that make sense? It does. It does a lot. You're preaching to the choir in fact. We are all in agreements. Maybe last question. What would you like that Ada did differently? I would like to see the, Nat already has a switch for it, the function call where you can treat basically the controlling parameter. You can always use the dot, the notation, the A dot B notation. I think that would be good because C++ is trying to move to that and I think REST is as well. That would be interesting to see because it would unify the ability to call everything the same way. Sometimes you create tag types just so you can get the A dot B notation. The other thing I would bring in would be I would like to see how control types, I would like the instantiation from aggregates. There's some non-obvious things about how those three initialize and adjust get called. It took a lot of experimenting to get that mental model correct in my head, longer than I would like. We'll know that initialization and finalization are difficult topics in any language. Ada makes them explicit so you have to really think about them. In other languages you might simply forget about them because they are implicit and you don't see them. But they exist nevertheless. I think that's all we have.
 Hello, and welcome to the Outsiders Guide to ADA.  I'm Paul, a software engineer who normally writes C++.  I've been using ADA for some of my hobby projects for almost a year now.  I've written in quite a few programming languages, and not many people are familiar with ADA.  So this is an overview of the language for programmers from other languages, from the  perspective of someone who doesn't write ADA professionally and hasn't been working with  ADA very long.  This is not an advocacy talk.  I can't teach you ADA in 30 minutes, so instead I'm going to show you its capabilities and  also how its parts work together to provide a different experience from other languages.  I think it's important to show real code, so most of the code samples I'll be showing  is real ADA code from the projects I've written over the last year or from other open source  projects.  I am not a liar, so seek competent and licensed legal counsel before making any business or  legal decisions based off of information in this talk.  ADA is still in use today.  It was released in the early 80s, followed by several revisions, making it a contemporary  of C++, not a Fortran and Cobalt.  Whereas the lineage of C++ can be traced roughly from ALGOL through BCPL and then C with similar  influences, ADA comes from a parallel track from ALGOL through Pascal.  The Pascal influence can be seen in the use of colon equals for assignment, which is a  statement not an expression like in C++.  It also uses single equals for equality, slash equals for not equal, and begin and end keywords  instead of curly braces for blocks.  In addition, due to the language not being built around object-oriented programming,  there are some interesting consequences on program organization and structure.  While several paid compilers exist, there is the ADA front-end to GCC, which is called  NAT, which is free as in beer and as in freedom.  The free software foundation's version of NAT provides a GPLv3 license with a runtime  exception, so I've been writing and releasing my code under the Apache 2.0 license.  For simplicity within this talk, I will be treating the NAT ecosystem and ADA as interchangeable,  as NAT is the predominant open source variant of ADA.  In addition to a compiler, NAT also includes a pretty printer for code formatting, a document  generator, and other tools.  ADA contains a subset called Spark, which provides the capability to formally verify  subsets of your program enabled via the Spark mode aspect.  There's also a relatively new package manager called AlliR.  This enables you to install the NAT toolchain on multiple systems, create, publish, and  download crates, which can be libraries or binaries.  If you're familiar with Rust, this tool fulfills many of the roles of Rust-up and Cargo.  There isn't a considerable open source presence for ADA.  The Tiobi ranking has it at number 34.  Language, which looks at the number of GitHub issues, pull requests, and stars has it at  number 163.  ADA doesn't show up on the RedMonk programming language rankings, and it does not appear  on the Stack Overflow Developer Survey.  The Black Duck open hub shows Rust as having 10 times the number of contributors of ADA  and about the same amount of code.  The popularity of programming languages metric shows ADA steadily gaining, though there's  some question of if this is generated by ADA the language or by Cardano the cryptocurrency,  which calls its token ADA.  Most ADA tutorials describe the language, but not a possible environment for working  in it.  I developed an ADA on Windows, Mac, and Linux.  On all of these platforms, I used the same method.  I installed a free software foundation's NAT front end to GCC using Alier, and then  used NAT Studio or Visual Studio Code.  Both of these editors are good.  My personal preference lately has been using Visual Studio Code.  There's an ADA language server, which some people use with VIM, but it also has an extension  for Visual Studio Code.  For VIM users, there's also a group of plugins you can get called the ADA bundle.  I use Alier to start my editor since it will launch it with the appropriate environment  variable set.  I use a JSON workspace file like shown, along with this Alier config command to start the  Visual Studio Code directly.  So when I write ADA, my editor looks something like this.  A major hurdle in learning the language is that the vocabulary can be quite unfamiliar.  I'll be using the vernacular of programmers familiar with C family languages, in particular  C++.  If you're having trouble with finding something or understanding an ADA-related term afterward,  this chart should provide a starting place for looking for what you need.  The way ADA is structured is a core which I call Type Safe C, and then three categories  of features you can bring in after choosing.  The core language includes primitives like integers and floating point types, arrays,  functions, and packages for namespacing.  This also includes a standard way of defining compilation units.  One set of features includes object-oriented programming, compile time generics, and constructors  and destructors.  I call this C++98 in Pascal because it doesn't include some features we commonly use in more  modern C++ versions like move semantics and variadic templates.  The second set is built-in concurrency features.  You can specify tasks for concurrent computation as well as protected objects to coordinate  read and write access to resources used by multiple tasks.  The third set relates to modeling features.  This includes ranges and constraints you can apply to types, the Spark language subset used  for formal verification, and also built-in designed by contract.  When I write ADA, I typically use few or no object-oriented tools, the standard library  container generics, and designed by contract with pre and post conditions.  I've used the concurrency features for data processing in parallel and adding interactive  features.  The language provides the ability to opt into features.  This falls into four categories, structure to delineate compilation units, provide namespaces  for functions and types, and to separate code to execute concurrently.  Types to define semantics for values by constraining their usage and contents, and provide the  mechanism for function overloading.  Statements to define computation within our structural elements, and finally controls  to provide the compiler additional direction when the other elements are insufficient, which  attach to the other elements without obscuring their intent.  The physical structure of how ADA programs are written is a more formalized version than  that used in C or C++.  Specifications in.ADS files are sort of like header files, and bodies in.ADB files are  sort of like source files.  There's no preprocessor or include directive, and all elements must begin with what's called  the context clause.  This describes all of the immediate dependencies that this compilation unit depends upon, and  can only be provided at the beginning.  This means a compiler can see if it has what's required for trying to proceed.  The program entry does not need to be called main, and can be specified as part of the  build when making executables.  The predominant compilation unit is the package.  A common pattern ADA syntax is the use of a pair of declarative element, as well as a  corresponding body element.  Package names are significant, and the usage of dots between elements indicate a package  being a child of another.  In this example, if a package p.r existed, it would indicate that r subchild of the parent  package p.  ADA separates declarations from executable code.  Sections following the reserved words is or declare indicate declarations, and those  following the word begin indicate executable code.  Data hiding, also known as encapsulation, happens at the structural level in packages  and protected types by starting a section with the reserved word private, or by placing  elements in the body only.  This is different from many other languages, such as Java, which describe encapsulation  at the class level.  All of the structural elements exhibit these similar characteristics.  This even includes the declare block, which can be used within statements describing execution,  to create an additional scope for variables or set up an exception block to catch exceptions.  The language permits the declaration of nearly anything in one of these sets of declarations.  When writing new features, it's often convenient to split out new packages, functions, tasks,  and protected types during refactoring into the declaration section of the function you're  working on before moving them to their permanent homes.  Structural elements fall upon two axes, whether they are active or passive, and whether they  describe sequential behavior or concurrency features.  Subprograms, which are either functions which return a value or a procedure which does not,  describe reusable sequences of steps.  Taskers provide namespacing for other elements and data hiding.  They act like classes and subclasses in C++ or Java with regards to the rules for encapsulation.  You can hide details from the outside while allowing children of the unit access.  Tasks provide concurrent execution of statements, and the body in which they're declared won't  exit until they're complete unless they're dynamically allocated.  AIDA provides built-in capabilities to segment available CPUs into what's called dispatching  domains and also to pin tasks to specific CPUs.  Protective types synchronize read and write access to their contained data and can use  complex guards to do so.  One-off tasks and protected types can be created by just skipping the type reserved word in  their declaration.  I'm only going to briefly mention types here to facilitate further discussion.  In general, types aren't that important to how AIDA works because they tend to work roughly  the same all the time.  Their purpose is to describe the value set or modified behaviors allowed on variables  of a specific type.  This takes many forms.  The range for a percentage could be described as 0 to 100.  For a reference count, a new type, not an alias, might be used to hide the underlying type.  What this does is to create a new version of an existing type.  Since AIDA does not allow implicit conversion, the type system prevents mixing these types  without explicit casting.  This allows the type checker to catch bad semantic usage, such as assigning an integer  of microseconds to one of nanoseconds.  Arrays provide groups of a given number of a type.  The index type can be another type and have an arbitrary range.  For example, you can create simple maps by using an enum as the index type or prevent  the need to remap indices into and out of an array by declaring the array index range  as that value range, such as 50 to 100.  Limited types prohibit a value from being copied or reassigned.  In my project septum, the search's package exposes a heavyweight, uncopyable search type  in an opaque way to clients.  Packages can encapsulate the detail of a type by marking it as private in the interface.  Clients can store and copy the type around in a similar way to a class with private elements  in another language.  Tagged denotes types which might require dynamic dispatch, which is also called runtime polymorphism  or virtual functions.  You can also create abstract base classes by adding the abstract reserved word or create  interfaces for types, tagged types, limited types, or for tasks and protected objects  with the appropriate keywords along with the interface keyword.  In addition to providing their behavior, limited and tagged types are passed by reference implicitly.  Ada is a strongly typed language, but the important takeaway is that because types are  not modules, they merely facilitate the creation of other behavior and are not the main focus  of the language.  Access types provide reference semantics to Ada.  You can't do pointer arithmetic on an access type, it just keeps indirect access to a value  elsewhere.  Multiple access types can be created for a single type and these are incompatible with  each other.  These use separate storage pools and you can even specify different pools per access type  and even use sub pools for increased granularity.  They allow you to dynamically allocate a type and the access all flavor acts like a generic  pointer to any value of that type even on the stack.  Like in C++, you can also have variations of constant accesses and accesses to constants.  Values which you take an access of must be marked as aliased.  There's also accessibility checks which prevent you from returning an access to a variable  which might leave scope, but you can subvert this with the unchecked access attribute.  Since types are not modules, there needs to be a mechanism to describe behavior operating  on types.  A major way that types push design is through the use of function overloading, also known  as ad hoc polymorphism as a primary design mechanism.  Because of this, the namespaces of visible packages and the parameters and return types  of the available options determine which sub-program gets chosen to be called.  How do you define a function then?  In other languages, the syntax changes depending upon whether you're attaching that function  to a type or not, the visibility of the function, and if and how the input parameter, either  implicit or explicit, can be modified, and if that value is copied, moved, or passed  by reference or pointer.  Especially in C++, there are quite a few ways to pass a parameter depending upon your technical  and design intent.  Parameters can be provided as inputs, outputs, or both inputs and outputs.  When the mode of a parameter is not indicated, it defaults to an input parameter.  Parameters are const by default unless they are marked as an output parameter.  Anonymous and access types can also be provided, as well as a null exclusion to indicate that  an access must be valid, which is like a reference in C++.  For object-oriented programming, instead of providing a base class reference or pointer,  the tick class attribute of the base class is used, which indicates what's called a  class-wide type.  Subprograms declared in the same package as the type, which have that type as the first  parameter, are known as primitive operations.  These get inherited when another type is created from this one.  Primitive operations may perform dynamic binding, like a virtual function, and dispatch  if the value passed is of a class-wide type.  This means if the compiler knows the type of an object, it will be statically bound.  If the compiler overloads, wrap the operator with double quotes, but otherwise works similarly  to other functions.  Since functions and procedures get declared outside of types, this means there isn't much  of a distinction in AIDA between what other languages call member functions or methods,  static functions, or free functions.  Controlled types provide RAII, or resource acquisition is initialization.  Types which inherit from AIDA.finalization.controlled can overwrite three procedures, initialized,  which gets called after default initialization, adjust, which gets called after the value receives  an assignment, and finalize, which cleans up at the end of the scope, like a C++ destructor,  and also immediately before value receives an assignment.  Limited types cannot be copied or assigned, so for those you inherit from AIDA.finalization.limited  control, and there is no adjust in this case.  Attributes take the place of additional operators and keywords to denote special properties  and operations of types and values.  Initially, they're super confusing, but they're just the name of a type or value followed  by an apostrophe or what AIDA calls tick.  For example, instead of size of and a line of, there's the attribute tick size and tick  alignment.  Instead of an operator to get AIDA's equivalent of a pointer to a value, you use tick access  on the variable, which also works on sub-programs, like a C function pointer.  If you want the raw machine address, you can use tick address.  To parse a primitive value from a string, there's a function like tick value.  Primitives also provide tick min and tick max attributes to return the min or max of a pair  of values.  For arrays, there's tick length, tick range to provide a range to iterate over, tick first  and tick last to get the first and last indices.  Some attributes like size and alignment can be controlled with aspects of what's called  an attribute definition clause.  Aspects allow you to attach additional metadata, options, and descriptions to types and structural  elements without disturbing the main syntax.  Being here, you can see the line in-type enforces an invariant.  Local flags is a packed array of booleans of 32 bits, which is a fancy way of saying  to use a 32-bit integer as the underline type.  This was done to provide a nicer interface to set binary flags and then pass this value  off to term iOS.  Insert here will be inlined, but also has a precondition which gets checked prior to  being called, and a post-condition checked afterwards.  You can see the attribute tick old here being used, which is the associated value prior  to the procedure call.  TC get ATTR is just a C function being imported and can be used just like a normal AIDA functioning  code.  AIDAs generics are different from those in other languages, since you instantiate generics  at the package and function level and not at the type level.  This instantiation is also explicit, meaning you can't just use an ordered map type inline  with angled brackets and have it work.  This was a major conceptual block for me, as I don't recall ever working with a language  which handles generic instantiation this way.  Instead, the entire package gets instantiated and then used like any other normal package.  Also demonstrated here is the usage of named parameters, which works also with sub-programs.  For generics which can be shared, this instantiation can be done once at the compilation unit level,  so each package doesn't need to individually instantiate it.  Now that we've gone through and overview the language, let's look back at the high  level at how these pieces fit together.  Let's start with the structural side.  You can see how sub-programs and packages can have generic versions, but tasks and  threads cannot.  You can, however, put tasks and protected objects inside of generic packages.  You can see types split out into their various flavors.  Implementations are technically not types in Ada, but I'm grouping them in that section  since they're distinct from one another.  Finally, you can see the controls we have over the other elements.  There's quite a few pragmas which have corresponding aspects.  There's also a set of pragmas, attributes, and aspects defined as part of the Ada standard,  but implementation defined ones are permitted as well.  With the available time, I'm going to highlight a few interesting features of Ada.  Stringes are arrays of characters and are not null-terminated.  Since they are arrays, they include the same bounce checks as other arrays.  If needed, you can convert to and from null-terminated C-style strings using the built-in interfaces.c.strings  package.  The language allows returning variable length arrays from functions, so you can build and  return strings from functions.  String, like other arrays, is of a static size.  If you need a resizable string type, you can use Ada.strings.unbounded.unbounded string.  Since strings have a known size, creating arrays of plain strings is not possible unless  you pad all of the strings to the same length or dynamically allocate each string.  This also applies to keys of maps for which I use the variable-sized unbounded string  type.  The concurrency features are a complicated subject, so let's just look at an example  of tasking in action.  Assume we're loading many files from disk, possibly performing processing, and then storing  the result in a cache.  File loader task defines what's called an entry named wake.  Entries are similar to procedures, they can't return any value, and they perform what's  called a rendezvous between tasks or protected objects.  One task calls the entry, and then the other task or protected object must accept it.  I don't use any here, but you can define guards, which are conditions which must be  true for a task or protected object to accept an entry.  In the inner loop, this task calls an entry on the queue of files to process.  This blocks until a file can be dequeued, or times out and exits the loop and hence  the task after one second.  If there was an element, then it's loaded, processed, and cached.  FileCache here is a protected object preventing multiple file loaders from performing writes  at the same time.  This loop of the task ends by incrementing another protected object monitoring progress,  which is used to display a progress report for the user.  Note that the FileQ, FileCache, and Progress variables are not declared in this task.  Normal rules regarding scope apply, and these variables had been declared in the same function  scope as this task, so they are available.  Note that all of this is done with built-in features of Ada.  This is a sampling of low-level controls.  You can import C functions, and then call them as if they were normal Ada functions.  Compiler intrinsics work the same way, as in this example from the Atomic project.  Inline assembly is allowed.  I use this assembly to cause the debugger to break in the appropriate failing assertion  when a test would fail in my test suite.  When I did the popular ray tracing in One Week in an Ada, I adjusted binary layout to  make a struct appropriate to write a BMP file header.  Let's look back at the original layering of the language that I mentioned, and group these  features.  From here, you can see there is a good base to the language in starting out, and you can  incrementally add features as you need them.  When I built a septum code search tool, I started on the far left with just the basic  primitives that I branched out by bringing in design by contract from the modeling group.  When I wanted to expand the filtering mechanism, I converted filters to tag types to be able  to use runtime polymorphism and dynamic dispatch, and they're still the only class-like type  in the system.  Most other types are struct-like records, with many being private types, which hide  their contents within their containing packages.  When I needed to parallelize search, I brought in tasks and converted the file cache to a  protected type.  My best way to describe Ada is that it's about intent.  These examples show how some of the keywords relate to their goals.  You describe what you want to have happen, and then you layer on meaning as needed with  the additional controls available.  The separation of structural elements and types from low-level controls puts the focus  on solving the problem while providing customization where you need it.  And we are live for questions and answers from Paul Gerrits.  Before we start with the first question, let me remind people that they can always join  the live chat after the talk ends.  For this reason, they have to join the appropriate room in Matrix.  So first question that we had, why does Ada need a move semantics, according to you, Paul?  Move semantics are very important because they allow you to transfer resources without  a copy or a reference.  On the surface, it looks like why do we need that?  But the important thing, a later question, is what does Ada need?  There's a thing in C++ called perfect forwarding, where it's a variadic template, used to something  called an arg pack, where you transfer arguments through a function call to call another function  as if it had the exact same parameters.  And move semantics would allow that, assuming that we had variadic templates and things like  that in Ada.  So it also expresses the semantic intent of an expiring object, or I'm not going to use  these resources anymore, I'm handing an object off as a parameter.  I think it's something that Ada actually definitely needs.  But you know that the limited type in Ada is returned by, not by copy, but by address.  In-out parameters are also passed by address.  Tag types are also passed by address all the time.  So move semantics already exists in Ada, simply not implemented through variadic templates.  Simply not implemented through limited types and return blocks.  Yes, it's a difference.  It would be like adding a moved reserved word in addition to in and out.  It's semantically different, is the point.  Because in, it's transferring the resources.  Just formalizes it, when you move an object, you cannot access that anymore.  The borrower checker will get upset at you and say that the value has been moved out.  Because it's a semantic definition of essentially destroying state.  You use it for vectors, like copying resources.  And there is a move function in Ada containers, in some of the Ada containers, but it's a  semantic way of building it into the language.  Right.  Well, maybe that's something to look into.  I hear that Spark has imported the borrower checker from that word.  At least something similar to the borrower checker, first, into Spark.  So now at compile time, there are some checks to verify the ownership of objects passed  by address.  So it's not in Ada, indeed.  It's an extension to Ada.  It's Spark.  But yeah, maybe that might be something to look into.  Second question, even though I read your website, what were the things you found really difficult  for coming from the C++ world?  What were the roadblocks you found?  The biggest ones were attributes.  I read Barnes's book and it was the primary source that I used.  And a lot of the tutorials just assume that you know what attributes are.  And it's like, I would expect there to be a keyword or something for that.  It's not really a good explanation other than it's something that might, it's built  in and built onto a value or an underwrite type.  And that's not really explained very well in a lot of tutorials.  That was a big trip up.  And then generics.  Normally instantiations are implicit.  Like in C++ and Rust, you just use the type.  The whole concept of having to instantiate an entire package, yes, it's what C++ does  behind the scenes, but most of the time you don't concern yourself with it as much.  But that was a huge one.  But everything else, in many respects, Ada builds on the same conceptual foundations.  Like I call it C++98 and Pascal because I honestly think someone with C++ could get  productive in Ada in less than a month.  Because there's a lot of conceptual similarity, even though it's a Pascal-based language and  not a C-based language.  A lot of the stuff is very compilation model system like the ADS, ADB files, very familiar  with that.  A lot of the stuff just kind of flows directly from C++ understanding.  Does that make sense?  Yeah.  I didn't really catch what you said about the one month.  You mean it takes at least a month to get become fluent in Ada?  I think I was productive in Ada in less than a month.  I was reading standard library code in less than a month.  A lot of languages that you'd not normally at that point that fast.  That matches my experience too.  Because I was once an outsider too.  I taught myself Ada, yeah, less than a month is sufficient.  The important thing is that some people say we can't use Ada because there's no programmers  or we have trouble finding programmers and I think that something that people don't understand  is that you could hire C++ programmers and convert them into Ada programmers relatively  quickly.  Yeah.  Awesome.  If you were bound to Ada for some reason.  That matches our experience too.  Have you managed to convert anyone to Ada from C or C++?  Conversion isn't really my goal.  My goal was primarily to figure out the language, so to speak.  Part of the reason I learned it was there wasn't a lot of information and I don't know  anyone in person that actually uses Ada at all.  This conference is my first experience actually talking to people who use the language outside  of the text forms and things.  My intent was to understand the language and all the material I've written has tried to  be from the objective point of view of laying out the language because a lot of people don't  understand how it fits together.  I think there's a lot of conceptual ideas that would improve other languages if they  would adopt them.  Especially from a university point of view.  Ada has a lot of concepts that are used and more difficult to learn languages like C++  that I think especially in a university setting it would be easier to get someone into C++  via Ada rather than just dropping them in there from Python or Java.  Does that make sense?  It does.  It does a lot.  You're preaching to the choir in fact.  We are all in agreements.  Maybe last question.  What would you like that Ada did differently?  I would like to see the, Nat already has a switch for it, the function call where you  can treat basically the controlling parameter.  You can always use the dot, the notation, the A dot B notation.  I think that would be good because C++ is trying to move to that and I think REST is  as well.  That would be interesting to see because it would unify the ability to call everything  the same way.  Sometimes you create tag types just so you can get the A dot B notation.  The other thing I would bring in would be I would like to see how control types, I  would like the instantiation from aggregates.  There's some non-obvious things about how those three initialize and adjust get called.  It took a lot of experimenting to get that mental model correct in my head, longer than  I would like.  We'll know that initialization and finalization are difficult topics in any language.  Ada makes them explicit so you have to really think about them.  In other languages you might simply forget about them because they are implicit and you  don't see them.  But they exist nevertheless.  I think that's all we have.
ada_outsiders_guide_0_143.png ada_outsiders_guide_144_5039.png ada_outsiders_guide_5040_5399.png ada_outsiders_guide_5400_5603.png ada_outsiders_guide_5604_6179.png ada_outsiders_guide_6180_7559.png ada_outsiders_guide_7560_7715.png ada_outsiders_guide_7716_8267.png ada_outsiders_guide_8268_10271.png ada_outsiders_guide_10272_14651.png ada_outsiders_guide_14652_16319.png ada_outsiders_guide_16320_21227.png ada_outsiders_guide_21228_21923.png ada_outsiders_guide_21924_22631.png ada_outsiders_guide_22632_24395.png ada_outsiders_guide_24396_25703.png ada_outsiders_guide_25704_27419.png ada_outsiders_guide_27420_28739.png ada_outsiders_guide_28740_29915.png ada_outsiders_guide_29916_31079.png ada_outsiders_guide_31080_32495.png ada_outsiders_guide_32496_34775.png ada_outsiders_guide_34776_35543.png ada_outsiders_guide_35544_36743.png ada_outsiders_guide_36744_37799.png ada_outsiders_guide_37800_38063.png ada_outsiders_guide_38064_40043.png ada_outsiders_guide_40044_40583.png ada_outsiders_guide_40584_43235.png ada_outsiders_guide_43236_45683.png ada_outsiders_guide_45684_45755.png ada_outsiders_guide_45756_45875.png ada_outsiders_guide_45876_45971.png ada_outsiders_guide_45972_46907.png ada_outsiders_guide_46908_48779.png ada_outsiders_guide_48780_49055.png ada_outsiders_guide_49056_49127.png ada_outsiders_guide_49128_50675.png ada_outsiders_guide_50676_50723.png ada_outsiders_guide_50724_50807.png ada_outsiders_guide_50808_50915.png ada_outsiders_guide_50916_51131.png ada_outsiders_guide_51132_51263.png ada_outsiders_guide_51264_51379.png
Hello, and welcome to the Outsiders Guide to ADA. I'm Paul, a software engineer who normally writes C++. I've been using ADA for some of my hobby projects for almost a year now. I've written in quite a few programming languages, and not many people are familiar with ADA. So this is an overview of the language for programmers from other languages, from the perspective of someone who doesn't write ADA professionally and hasn't been working with ADA very long. This is not an advocacy talk. I can't teach you ADA in 30 minutes, so instead I'm going to show you its capabilities and also how its parts work together to provide a different experience from other languages. I think it's important to show real code, so most of the code samples I'll be showing is real ADA code from the projects I've written over the last year or from other open source projects. I am not a liar, so seek competent and licensed legal counsel before making any business or legal decisions based off of information in this talk. ADA is still in use today. It was released in the early 80s, followed by several revisions, making it a contemporary of C++, not a Fortran and Cobalt. Whereas the lineage of C++ can be traced roughly from ALGOL through BCPL and then C with similar influences, ADA comes from a parallel track from ALGOL through Pascal. The Pascal influence can be seen in the use of colon equals for assignment, which is a statement not an expression like in C++. It also uses single equals for equality, slash equals for not equal, and begin and end keywords instead of curly braces for blocks. In addition, due to the language not being built around object-oriented programming, there are some interesting consequences on program organization and structure. While several paid compilers exist, there is the ADA front-end to GCC, which is called NAT, which is free as in beer and as in freedom. The free software foundation's version of NAT provides a GPLv3 license with a runtime exception, so I've been writing and releasing my code under the Apache 2.0 license. For simplicity within this talk, I will be treating the NAT ecosystem and ADA as interchangeable, as NAT is the predominant open source variant of ADA. In addition to a compiler, NAT also includes a pretty printer for code formatting, a document generator, and other tools. ADA contains a subset called Spark, which provides the capability to formally verify subsets of your program enabled via the Spark mode aspect. There's also a relatively new package manager called AlliR. This enables you to install the NAT toolchain on multiple systems, create, publish, and download crates, which can be libraries or binaries. If you're familiar with Rust, this tool fulfills many of the roles of Rust-up and Cargo. There isn't a considerable open source presence for ADA. The Tiobi ranking has it at number 34. Language, which looks at the number of GitHub issues, pull requests, and stars has it at number 163. ADA doesn't show up on the RedMonk programming language rankings, and it does not appear on the Stack Overflow Developer Survey. The Black Duck open hub shows Rust as having 10 times the number of contributors of ADA and about the same amount of code. The popularity of programming languages metric shows ADA steadily gaining, though there's some question of if this is generated by ADA the language or by Cardano the cryptocurrency, which calls its token ADA. Most ADA tutorials describe the language, but not a possible environment for working in it. I developed an ADA on Windows, Mac, and Linux. On all of these platforms, I used the same method. I installed a free software foundation's NAT front end to GCC using Alier, and then used NAT Studio or Visual Studio Code. Both of these editors are good. My personal preference lately has been using Visual Studio Code. There's an ADA language server, which some people use with VIM, but it also has an extension for Visual Studio Code. For VIM users, there's also a group of plugins you can get called the ADA bundle. I use Alier to start my editor since it will launch it with the appropriate environment variable set. I use a JSON workspace file like shown, along with this Alier config command to start the Visual Studio Code directly. So when I write ADA, my editor looks something like this. A major hurdle in learning the language is that the vocabulary can be quite unfamiliar. I'll be using the vernacular of programmers familiar with C family languages, in particular C++. If you're having trouble with finding something or understanding an ADA-related term afterward, this chart should provide a starting place for looking for what you need. The way ADA is structured is a core which I call Type Safe C, and then three categories of features you can bring in after choosing. The core language includes primitives like integers and floating point types, arrays, functions, and packages for namespacing. This also includes a standard way of defining compilation units. One set of features includes object-oriented programming, compile time generics, and constructors and destructors. I call this C++98 in Pascal because it doesn't include some features we commonly use in more modern C++ versions like move semantics and variadic templates. The second set is built-in concurrency features. You can specify tasks for concurrent computation as well as protected objects to coordinate read and write access to resources used by multiple tasks. The third set relates to modeling features. This includes ranges and constraints you can apply to types, the Spark language subset used for formal verification, and also built-in designed by contract. When I write ADA, I typically use few or no object-oriented tools, the standard library container generics, and designed by contract with pre and post conditions. I've used the concurrency features for data processing in parallel and adding interactive features. The language provides the ability to opt into features. This falls into four categories, structure to delineate compilation units, provide namespaces for functions and types, and to separate code to execute concurrently. Types to define semantics for values by constraining their usage and contents, and provide the mechanism for function overloading. Statements to define computation within our structural elements, and finally controls to provide the compiler additional direction when the other elements are insufficient, which attach to the other elements without obscuring their intent. The physical structure of how ADA programs are written is a more formalized version than that used in C or C++. Specifications in.ADS files are sort of like header files, and bodies in.ADB files are sort of like source files. There's no preprocessor or include directive, and all elements must begin with what's called the context clause. This describes all of the immediate dependencies that this compilation unit depends upon, and can only be provided at the beginning. This means a compiler can see if it has what's required for trying to proceed. The program entry does not need to be called main, and can be specified as part of the build when making executables. The predominant compilation unit is the package. A common pattern ADA syntax is the use of a pair of declarative element, as well as a corresponding body element. Package names are significant, and the usage of dots between elements indicate a package being a child of another. In this example, if a package p.r existed, it would indicate that r subchild of the parent package p. ADA separates declarations from executable code. Sections following the reserved words is or declare indicate declarations, and those following the word begin indicate executable code. Data hiding, also known as encapsulation, happens at the structural level in packages and protected types by starting a section with the reserved word private, or by placing elements in the body only. This is different from many other languages, such as Java, which describe encapsulation at the class level. All of the structural elements exhibit these similar characteristics. This even includes the declare block, which can be used within statements describing execution, to create an additional scope for variables or set up an exception block to catch exceptions. The language permits the declaration of nearly anything in one of these sets of declarations. When writing new features, it's often convenient to split out new packages, functions, tasks, and protected types during refactoring into the declaration section of the function you're working on before moving them to their permanent homes. Structural elements fall upon two axes, whether they are active or passive, and whether they describe sequential behavior or concurrency features. Subprograms, which are either functions which return a value or a procedure which does not, describe reusable sequences of steps. Taskers provide namespacing for other elements and data hiding. They act like classes and subclasses in C++ or Java with regards to the rules for encapsulation. You can hide details from the outside while allowing children of the unit access. Tasks provide concurrent execution of statements, and the body in which they're declared won't exit until they're complete unless they're dynamically allocated. AIDA provides built-in capabilities to segment available CPUs into what's called dispatching domains and also to pin tasks to specific CPUs. Protective types synchronize read and write access to their contained data and can use complex guards to do so. One-off tasks and protected types can be created by just skipping the type reserved word in their declaration. I'm only going to briefly mention types here to facilitate further discussion. In general, types aren't that important to how AIDA works because they tend to work roughly the same all the time. Their purpose is to describe the value set or modified behaviors allowed on variables of a specific type. This takes many forms. The range for a percentage could be described as 0 to 100. For a reference count, a new type, not an alias, might be used to hide the underlying type. What this does is to create a new version of an existing type. Since AIDA does not allow implicit conversion, the type system prevents mixing these types without explicit casting. This allows the type checker to catch bad semantic usage, such as assigning an integer of microseconds to one of nanoseconds. Arrays provide groups of a given number of a type. The index type can be another type and have an arbitrary range. For example, you can create simple maps by using an enum as the index type or prevent the need to remap indices into and out of an array by declaring the array index range as that value range, such as 50 to 100. Limited types prohibit a value from being copied or reassigned. In my project septum, the search's package exposes a heavyweight, uncopyable search type in an opaque way to clients. Packages can encapsulate the detail of a type by marking it as private in the interface. Clients can store and copy the type around in a similar way to a class with private elements in another language. Tagged denotes types which might require dynamic dispatch, which is also called runtime polymorphism or virtual functions. You can also create abstract base classes by adding the abstract reserved word or create interfaces for types, tagged types, limited types, or for tasks and protected objects with the appropriate keywords along with the interface keyword. In addition to providing their behavior, limited and tagged types are passed by reference implicitly. Ada is a strongly typed language, but the important takeaway is that because types are not modules, they merely facilitate the creation of other behavior and are not the main focus of the language. Access types provide reference semantics to Ada. You can't do pointer arithmetic on an access type, it just keeps indirect access to a value elsewhere. Multiple access types can be created for a single type and these are incompatible with each other. These use separate storage pools and you can even specify different pools per access type and even use sub pools for increased granularity. They allow you to dynamically allocate a type and the access all flavor acts like a generic pointer to any value of that type even on the stack. Like in C++, you can also have variations of constant accesses and accesses to constants. Values which you take an access of must be marked as aliased. There's also accessibility checks which prevent you from returning an access to a variable which might leave scope, but you can subvert this with the unchecked access attribute. Since types are not modules, there needs to be a mechanism to describe behavior operating on types. A major way that types push design is through the use of function overloading, also known as ad hoc polymorphism as a primary design mechanism. Because of this, the namespaces of visible packages and the parameters and return types of the available options determine which sub-program gets chosen to be called. How do you define a function then? In other languages, the syntax changes depending upon whether you're attaching that function to a type or not, the visibility of the function, and if and how the input parameter, either implicit or explicit, can be modified, and if that value is copied, moved, or passed by reference or pointer. Especially in C++, there are quite a few ways to pass a parameter depending upon your technical and design intent. Parameters can be provided as inputs, outputs, or both inputs and outputs. When the mode of a parameter is not indicated, it defaults to an input parameter. Parameters are const by default unless they are marked as an output parameter. Anonymous and access types can also be provided, as well as a null exclusion to indicate that an access must be valid, which is like a reference in C++. For object-oriented programming, instead of providing a base class reference or pointer, the tick class attribute of the base class is used, which indicates what's called a class-wide type. Subprograms declared in the same package as the type, which have that type as the first parameter, are known as primitive operations. These get inherited when another type is created from this one. Primitive operations may perform dynamic binding, like a virtual function, and dispatch if the value passed is of a class-wide type. This means if the compiler knows the type of an object, it will be statically bound. If the compiler overloads, wrap the operator with double quotes, but otherwise works similarly to other functions. Since functions and procedures get declared outside of types, this means there isn't much of a distinction in AIDA between what other languages call member functions or methods, static functions, or free functions. Controlled types provide RAII, or resource acquisition is initialization. Types which inherit from AIDA.finalization.controlled can overwrite three procedures, initialized, which gets called after default initialization, adjust, which gets called after the value receives an assignment, and finalize, which cleans up at the end of the scope, like a C++ destructor, and also immediately before value receives an assignment. Limited types cannot be copied or assigned, so for those you inherit from AIDA.finalization.limited control, and there is no adjust in this case. Attributes take the place of additional operators and keywords to denote special properties and operations of types and values. Initially, they're super confusing, but they're just the name of a type or value followed by an apostrophe or what AIDA calls tick. For example, instead of size of and a line of, there's the attribute tick size and tick alignment. Instead of an operator to get AIDA's equivalent of a pointer to a value, you use tick access on the variable, which also works on sub-programs, like a C function pointer. If you want the raw machine address, you can use tick address. To parse a primitive value from a string, there's a function like tick value. Primitives also provide tick min and tick max attributes to return the min or max of a pair of values. For arrays, there's tick length, tick range to provide a range to iterate over, tick first and tick last to get the first and last indices. Some attributes like size and alignment can be controlled with aspects of what's called an attribute definition clause. Aspects allow you to attach additional metadata, options, and descriptions to types and structural elements without disturbing the main syntax. Being here, you can see the line in-type enforces an invariant. Local flags is a packed array of booleans of 32 bits, which is a fancy way of saying to use a 32-bit integer as the underline type. This was done to provide a nicer interface to set binary flags and then pass this value off to term iOS. Insert here will be inlined, but also has a precondition which gets checked prior to being called, and a post-condition checked afterwards. You can see the attribute tick old here being used, which is the associated value prior to the procedure call. TC get ATTR is just a C function being imported and can be used just like a normal AIDA functioning code. AIDAs generics are different from those in other languages, since you instantiate generics at the package and function level and not at the type level. This instantiation is also explicit, meaning you can't just use an ordered map type inline with angled brackets and have it work. This was a major conceptual block for me, as I don't recall ever working with a language which handles generic instantiation this way. Instead, the entire package gets instantiated and then used like any other normal package. Also demonstrated here is the usage of named parameters, which works also with sub-programs. For generics which can be shared, this instantiation can be done once at the compilation unit level, so each package doesn't need to individually instantiate it. Now that we've gone through and overview the language, let's look back at the high level at how these pieces fit together. Let's start with the structural side. You can see how sub-programs and packages can have generic versions, but tasks and threads cannot. You can, however, put tasks and protected objects inside of generic packages. You can see types split out into their various flavors. Implementations are technically not types in Ada, but I'm grouping them in that section since they're distinct from one another. Finally, you can see the controls we have over the other elements. There's quite a few pragmas which have corresponding aspects. There's also a set of pragmas, attributes, and aspects defined as part of the Ada standard, but implementation defined ones are permitted as well. With the available time, I'm going to highlight a few interesting features of Ada. Stringes are arrays of characters and are not null-terminated. Since they are arrays, they include the same bounce checks as other arrays. If needed, you can convert to and from null-terminated C-style strings using the built-in interfaces.c.strings package. The language allows returning variable length arrays from functions, so you can build and return strings from functions. String, like other arrays, is of a static size. If you need a resizable string type, you can use Ada.strings.unbounded.unbounded string. Since strings have a known size, creating arrays of plain strings is not possible unless you pad all of the strings to the same length or dynamically allocate each string. This also applies to keys of maps for which I use the variable-sized unbounded string type. The concurrency features are a complicated subject, so let's just look at an example of tasking in action. Assume we're loading many files from disk, possibly performing processing, and then storing the result in a cache. File loader task defines what's called an entry named wake. Entries are similar to procedures, they can't return any value, and they perform what's called a rendezvous between tasks or protected objects. One task calls the entry, and then the other task or protected object must accept it. I don't use any here, but you can define guards, which are conditions which must be true for a task or protected object to accept an entry. In the inner loop, this task calls an entry on the queue of files to process. This blocks until a file can be dequeued, or times out and exits the loop and hence the task after one second. If there was an element, then it's loaded, processed, and cached. FileCache here is a protected object preventing multiple file loaders from performing writes at the same time. This loop of the task ends by incrementing another protected object monitoring progress, which is used to display a progress report for the user. Note that the FileQ, FileCache, and Progress variables are not declared in this task. Normal rules regarding scope apply, and these variables had been declared in the same function scope as this task, so they are available. Note that all of this is done with built-in features of Ada. This is a sampling of low-level controls. You can import C functions, and then call them as if they were normal Ada functions. Compiler intrinsics work the same way, as in this example from the Atomic project. Inline assembly is allowed. I use this assembly to cause the debugger to break in the appropriate failing assertion when a test would fail in my test suite. When I did the popular ray tracing in One Week in an Ada, I adjusted binary layout to make a struct appropriate to write a BMP file header. Let's look back at the original layering of the language that I mentioned, and group these features. From here, you can see there is a good base to the language in starting out, and you can incrementally add features as you need them. When I built a septum code search tool, I started on the far left with just the basic primitives that I branched out by bringing in design by contract from the modeling group. When I wanted to expand the filtering mechanism, I converted filters to tag types to be able to use runtime polymorphism and dynamic dispatch, and they're still the only class-like type in the system. Most other types are struct-like records, with many being private types, which hide their contents within their containing packages. When I needed to parallelize search, I brought in tasks and converted the file cache to a protected type. My best way to describe Ada is that it's about intent. These examples show how some of the keywords relate to their goals. You describe what you want to have happen, and then you layer on meaning as needed with the additional controls available. The separation of structural elements and types from low-level controls puts the focus on solving the problem while providing customization where you need it. And we are live for questions and answers from Paul Gerrits. Before we start with the first question, let me remind people that they can always join the live chat after the talk ends. For this reason, they have to join the appropriate room in Matrix. So first question that we had, why does Ada need a move semantics, according to you, Paul? Move semantics are very important because they allow you to transfer resources without a copy or a reference. On the surface, it looks like why do we need that? But the important thing, a later question, is what does Ada need? There's a thing in C++ called perfect forwarding, where it's a variadic template, used to something called an arg pack, where you transfer arguments through a function call to call another function as if it had the exact same parameters. And move semantics would allow that, assuming that we had variadic templates and things like that in Ada. So it also expresses the semantic intent of an expiring object, or I'm not going to use these resources anymore, I'm handing an object off as a parameter. I think it's something that Ada actually definitely needs. But you know that the limited type in Ada is returned by, not by copy, but by address. In-out parameters are also passed by address. Tag types are also passed by address all the time. So move semantics already exists in Ada, simply not implemented through variadic templates. Simply not implemented through limited types and return blocks. Yes, it's a difference. It would be like adding a moved reserved word in addition to in and out. It's semantically different, is the point. Because in, it's transferring the resources. Just formalizes it, when you move an object, you cannot access that anymore. The borrower checker will get upset at you and say that the value has been moved out. Because it's a semantic definition of essentially destroying state. You use it for vectors, like copying resources. And there is a move function in Ada containers, in some of the Ada containers, but it's a semantic way of building it into the language. Right. Well, maybe that's something to look into. I hear that Spark has imported the borrower checker from that word. At least something similar to the borrower checker, first, into Spark. So now at compile time, there are some checks to verify the ownership of objects passed by address. So it's not in Ada, indeed. It's an extension to Ada. It's Spark. But yeah, maybe that might be something to look into. Second question, even though I read your website, what were the things you found really difficult for coming from the C++ world? What were the roadblocks you found? The biggest ones were attributes. I read Barnes's book and it was the primary source that I used. And a lot of the tutorials just assume that you know what attributes are. And it's like, I would expect there to be a keyword or something for that. It's not really a good explanation other than it's something that might, it's built in and built onto a value or an underwrite type. And that's not really explained very well in a lot of tutorials. That was a big trip up. And then generics. Normally instantiations are implicit. Like in C++ and Rust, you just use the type. The whole concept of having to instantiate an entire package, yes, it's what C++ does behind the scenes, but most of the time you don't concern yourself with it as much. But that was a huge one. But everything else, in many respects, Ada builds on the same conceptual foundations. Like I call it C++98 and Pascal because I honestly think someone with C++ could get productive in Ada in less than a month. Because there's a lot of conceptual similarity, even though it's a Pascal-based language and not a C-based language. A lot of the stuff is very compilation model system like the ADS, ADB files, very familiar with that. A lot of the stuff just kind of flows directly from C++ understanding. Does that make sense? Yeah. I didn't really catch what you said about the one month. You mean it takes at least a month to get become fluent in Ada? I think I was productive in Ada in less than a month. I was reading standard library code in less than a month. A lot of languages that you'd not normally at that point that fast. That matches my experience too. Because I was once an outsider too. I taught myself Ada, yeah, less than a month is sufficient. The important thing is that some people say we can't use Ada because there's no programmers or we have trouble finding programmers and I think that something that people don't understand is that you could hire C++ programmers and convert them into Ada programmers relatively quickly. Yeah. Awesome. If you were bound to Ada for some reason. That matches our experience too. Have you managed to convert anyone to Ada from C or C++? Conversion isn't really my goal. My goal was primarily to figure out the language, so to speak. Part of the reason I learned it was there wasn't a lot of information and I don't know anyone in person that actually uses Ada at all. This conference is my first experience actually talking to people who use the language outside of the text forms and things. My intent was to understand the language and all the material I've written has tried to be from the objective point of view of laying out the language because a lot of people don't understand how it fits together. I think there's a lot of conceptual ideas that would improve other languages if they would adopt them. Especially from a university point of view. Ada has a lot of concepts that are used and more difficult to learn languages like C++ that I think especially in a university setting it would be easier to get someone into C++ via Ada rather than just dropping them in there from Python or Java. Does that make sense? It does. It does a lot. You're preaching to the choir in fact. We are all in agreements. Maybe last question. What would you like that Ada did differently? I would like to see the, Nat already has a switch for it, the function call where you can treat basically the controlling parameter. You can always use the dot, the notation, the A dot B notation. I think that would be good because C++ is trying to move to that and I think REST is as well. That would be interesting to see because it would unify the ability to call everything the same way. Sometimes you create tag types just so you can get the A dot B notation. The other thing I would bring in would be I would like to see how control types, I would like the instantiation from aggregates. There's some non-obvious things about how those three initialize and adjust get called. It took a lot of experimenting to get that mental model correct in my head, longer than I would like. We'll know that initialization and finalization are difficult topics in any language. Ada makes them explicit so you have to really think about them. In other languages you might simply forget about them because they are implicit and you don't see them. But they exist nevertheless. I think that's all we have.
projects for almost a year now. I've written in overview of the language for programmers from other languages, from the perspective of someone capabilities and also how its parts work together to provide a different experience from other code, so most of the code samples I'll be showing braces for blocks. In addition, due to the within this talk, I will be treating the NAT isn't a considerable open source presence for be quite unfamiliar. I'll be using the vernacular in particular C++. If you're having trouble with term afterward, this chart should provide a language provides the ability to opt into features. This falls into four categories, the body only. This is different from many other block, which can be used within statements you can create simple maps by using an enum as function then? In other languages, the syntax access on the variable, which also works on back at the high level at how these pieces fit of the Ada standard, but implementation defined package. The language allows returning variable and things. My intent was to understand the don't understand how it fits together. I think difficult to learn languages like C++ that I