Having written Java code for a few decades, there are some facets that I find to be a “must” (without having any good reason for it). One such thing is the way object-orientation makes use of methods. So, while I now try to learn myself Ada (have some PL/SQL experience), I find myself searching for method declaration.
As everyone who has coded Java knows, a method is written as a function within the class declaration. When the method is called, it is called as something that belongs to the class (or rather the object).
Ex. A Java class C may have a method m(x, y, …). And object o of class C can then use this method through the call:
o.m(x, y, …)
Having search (or skimmed) through both literature and the net, it is my understanding that the closet we get in Ada is to have a procedure or a function, with the first argument being of the containing type, declared in the type declaration, and then calling that proc/func by passing a variable of that type as the parameter (sorry if I mess this up, I know what I’m thinking, but putting it in writing is less clear). This would lead to a proc/func call similar to:
m(o, x, y, …)
Or have I missed something?
What is the “correct” object oriented way of writing the following in Ada? public class Hello
{
public void SayHello()
{
System.out.println( “Hello World!”);
}
}
And similar upon use:
{
Hello greeting = new Hello();
greeting.SayHello();
}
Ada’s tagged types are equivalent to Java classes. tagged is meant to be used when you need inheritance, but is often added just to get the “dot syntax” for calling procedures and functions. Note that the . implies a dereference, so this may have performance implications. In practice, the impact is negligible.
Your example above would look something like this, with the tagged type defined in a package specification or declaration block.
app_namespace.ads
package App_Namespace is
type Hello is tagged null record;
procedure Say_Hello
(This : in out Hello);
end App_Namespace;
app_namespace.adb
with Ada.Text_IO;
package body App_Namespace is
procedure Say_Hello
(This : in out Hello)
is
begin
Ada.Text_IO.Put_Line ("Hello, Ada!");
end Say_Hello;
end App_Namespace;
main.adb
with App_Namespace; use App_Namespace;
procedure Main is
Greeting : Hello;
begin
Greeting.Say_Hello;
end Main;
Thank you for a very clear example and explanation. This is not far away from what I concluded. I was, however, confused about the declaration of the methods parameters (with the use of the first parameter).
Also, thank you for the reference link. I did read this before I posted the question, but obviously I misunderstood some of it.
I’m still on the fence as to whether I prefer to use “use” or not. I can see the point about polluting the namespace. Back in my Pascal days, I would prefer not to use the “with” statement for similar reason. While coding Java, I am happy that an “import” implied the “with”+“use” construct, since fully package-qualified class paths would become very tiresome, and make the code much less readable.
Another option is to use only the package hierarchy, but not the final package. In that way, name clashes are minimal and comprehension is not hindered. Example:
use Ada;
use Ada.Containers;
use Gtk;
In general practice, I use the fully-qualified names.
Packages including nested packages can also provide dot syntax. I prefer fully qualified names too unless from the same package or parent package. Especially in the middle of a refactor. Even on learn.adacore.com I would have found removing use helpful to point out what is what when I was unfamiliar. The problem is that some already see Ada as verbose, I guess. I am not sure if those criticism are born from those who are just trying to defend C without an open mind though.
Just wanted to add some clarification here. In Ada, it doesn’t matter if the containing type is the first argument, the last argument, or somewhere in the middle. Ada compilers can still associate the function or procedure as the equivalent of a class method. Being the first argument does allow the Object.Method usage though, so that is helpful. In addition, functions that return the containing type are also considered the equivalent of a method and can be overridden, so long as they are in the same package as the class type declaration. So for example:
package App_Namespace is
type Hello is tagged null record;
procedure Say_Hello
(This : in out Hello);
procedure Say_Other_Things(Value : Integer; This : Hello);
function Construct return Hello;
end App_Namespace;
are all the equivalent of “methods” for Hello.
In Ada, the terminology for “methods” is “primitive operations”.
Also side note, to call a function primitive operation that returns the containing type but doesn’t have an argument of the containing type, you can’t use the Object.Method notation discussed above by Jeremy. You still have to use Package.Method in that case, but any procedure or function that has an argument of the containing type can use Object.Method.
procedure Test is
Hello : App_Namespace.Hello := App_Namespace.Construct;
begin
Hello.Say_Hello;
App_Namespace.Say_Other_Things(100,Hello); -- still a method, but can't use Object.Method syntax
end Test;
Coming from Java, you might find it strange to have to use the object as first parameter to be able to use the object.method syntax - but it’s not that uncommon in other languages. For example, in Python you have to provide “this” as first parameter of methods, too.
I don’t understand less readable here. I find it more readable. I almost never hit the line limit either with vertical one parameter per line etcetera. Similar to the Flutter ui toolkit.
Often it just goes from Timer_Start to Timer.Start too.