logoalt Hacker News

tremonyesterday at 5:01 PM1 replyview on HN

> while providing the minimum of abstraction people wanted

Yes, I think this is key. I wasn't around in 1985, but on every attempt to write something in Ada I've found myself fighting its standard library more than using it. Ada's stdlib is an intersection of common features found in previous century's operating systems, and anything OS-specific or any developments from the last 30 years seem to be conspicuously absent. That wouldn't be so much of a problem if you could just extend the stdlib with OS-specific features, but Ada's abstractions are closed instead of leaky.

I'm sure that this is less of a problem on embedded systems, unikernels or other close-to-hardware software projects where you have more control over the stdlib and runtime; but as much as I like Ada's type system and its tasking model I would never write system applications in Ada because the standard library abstractions just get in the way.

To illustrate what I mean, look at the Ada.Interrupts standard library package [0] for interrupt handling, and how it defines an interrupt handler:

  type Parameterless_Handler is
    access protected procedure
    with Nonblocking => False;
That's sufficient for hardware interrupts: you have an entry point address, and that's it. But on Linux the same package is used for signal handling, and a parameterless procedure is in no way compatible with the rich siginfo_t struct that the kernel offers. To wit, because the handler is parameterless you need to attach a separate handler to each signal to even know which signal was raised. And to add insult to injury, the gnat runtime always spawns a signal handler thread with an empty sigprocmask before entering the main subprogram so it's not possible to use signalfd to work around this issue either.

Ada's stdlib file operations suffer from closed enumerations: the file operations Create and Open take a File_Mode argument, and that argument is defined as [1]:

  type File_Mode is (In_File, Inout_File, Out_File);  --  for Direct_IO
  type File_Mode is (In_File, Out_File, Append_File); --  for Stream_IO
That's it. No provisions for Posix flags like O_CLOEXEC or O_EXCL nor BSD flags like O_EXLOCK, and since enum types are closed in Ada there is no way to add those custom flags either. All modern or OS-specific features like dirfd on Linux or opportunistic locking on Windows are not easily available in Ada because of closed definitions like this.

Another example is GNAT.Sockets (not part of Ada stdlib), which defines these address families and socket types in a closed enum:

  type Family_Type is (Family_Inet, Family_Inet6, Family_Unix, Family_Unspec);
  type Mode_Type is (Socket_Stream, Socket_Datagram, Socket_Raw);
Want to use AF_ALG or AF_KEY for secure cryptographic operations, or perhaps SOCK_SEQPACKET or a SOL_BLUETOOTH socket? Better prepare to write your own Ada sockets library first.

[0] https://docs.adacore.com/live/wave/arm22/html/arm22/arm22-C-...

[1] https://docs.adacore.com/live/wave/arm22/html/arm22/arm22-A-...


Replies

OneWingedSharktoday at 3:38 AM

To be fair, the file-handling is probably the 'crustiest' part of the standard library. (To use the posix-flags, you use the Form parameter.)

The best way to use Ada, IMO, is type-first: you define your problem-space in the type-system, then use that to solve your problem. -- Also, because Ada's foreign-function interface is dead easy, you could use imports to handle things in a manner more amiable to your needs/preferences, it's as simple as:

    Function Example (X : Interfaces.Unsigned_16) return Boolean
      with Import, Convention => COBOL, Link_Name => "xmpl16";
You can even put pre-/post-conditions on it.
show 1 reply