Very dumb review question: How to 'pass a function' to a procedure in Ada?

Hi;

Please pardon the elementary nature of the question.

I’m trying to remember (I know I did something like this previously), but I can’t remember when/where; also I really don’t feel that ‘pass a function in Ada’ is a sufficiently well phrased search question. Meanwhile I guess I’ll use it as I can’t think of anything better yet.

The end result would be a library of Ada code where someone wants to explore specific results (numerical integration in this case) from an arbitrary function, where the code being excercised has already been provided as a library.

So, how are functions passed as parameters in Ada? Or how the heck does I library know what function it is going to be executing in the future?

Right now, I’m just going to hard-code a function inside the integration procedure.

Thanks in advance for your patience.

RBE

As generic formal parameters.
Technically you can use an access to a subprogram, but generics ought to be preferred over mere accesses.

For a generic, let me present the problem where you want to insert a print/logging of a value and return that value:

Generic
   Type Value(<>) is limited private;
   with Procedure Print( Object : Value ) is null;
Function Debug_Value( X: Value ) return Value;
Function Debug_Value( X: Value ) return Value is
Begin
  Return Result : Constant Value := X do
    Print( X );
  End return;
End Debug_Value;

Here, we are providing a Print function (as well as the type) as a formal parameter, this allows us to instantiate Debug_Value with something like, say, a function of Write_Line( Object'Image ).

Note: It may be prudent to have a polynomial type, perhaps implemented as Array (Natural range <>) of Real, where the elements thereof are the [coefficients of the] exponents.

3 Likes

I will try to digest these two methods and research them and do lots of experimentation.

Thanks,
RBE

Sorry, I meant “coefficients of the exponents”.
EG: (3 => 2, 2 => -4, 1 => 7, 0 => 11) would be the array for 2x³-4x²+7x¹+11x⁰.

1 Like

Thanks for the clarification regarding the coefficients :slight_smile:

Let’s say the function in question was in terms of more than one variable…or was a trig function?

I was thinking about defining the function as something like (typing on the fly here):

function f (x : float) return float is
begin
  return sin (x); -- potentially much more complicated 
end f;

then the function which is in the library (not yet) which does the math (a numerical integration method in this case) would be given the parameter of ‘f’, the function.

Again, I really do not feel that I am expressing myself fluently here :frowning:

Thanks,
RBE

You’re getting into potentially tricky territory.
While it is easy to see that integrating sin(x) gives you -cos(x)[+C], the thing that you have to remember is that the computer isn’t doing symbol manipulation (well, it can, but then you’re in for doing the whole of the engine itself, in which case you’re probably better off using LISP) — there are ways to tackle the problems, but full-blown numerics is…difficult.

Consider:

Generic
  with Function F( X : Real ) return Real;
Package Numeric_Stuff is

  -- Note that by instantiating this, you get a function which
  -- returns a single value, and is without parameters.
  Generic
    Start, Stop : Real;
  Function Generic_Evaluate return Real;
--…
  Function Generic_Evaluate return Real is
  (F(Stop) - F(Start));
--…

As you can see, there are major consequences of how to use a library based on the choices in design, the above is probably less useful than some other approach, but might be wellsuited for the particular application.

1 Like

Subroutines are passed as access to subprogram. For example:

   function Newton
           (  F   : access function (X : Float) return Float;
              DF  : access function (X : Float) return Float;
              X   : Float;
              Eps : Float := 1.0E-6
           )  return Float is
      X0 : Float := X;
      X1 : Float;
   begin
      loop
         X1 := X0 - F (X0) / DF (X0);
         exit when abs (X1 - X0) <= Eps;
         X0 := X1;
      end loop;
      return X1;
   end Newton;

Then you call it as

Newton (F'Access, DF'Access, 0.5);

1 Like

This is the case when you do not want generics. Normally generics are always the last design choice because of their static nature. You cannot do anything that requires late bindings with them. As it was already pointed out you can use access to subprogram and thus pass any subprogram from anywhere, e.g. from a library loaded after your program was compiled.

A more complicated but also more flexible way is to use methods. This approach is actively deployed in various software patterns, e.g. in the visitor pattern when you need to call something when iterating something while maintaining some state external to the iterator itself. In its simple form you pass an object of a type that has a primitive operation which is then called back. The primitive operation is your function. Now this is also static as you need to override the operation in order to put you function body in there. But you can make it dynamic using an access discriminant. So you pass your function as a discriminant of:

   type Functor
        (  F : access function (X : Float) return Float
        )  is tagged null record;
   function Call_Me (Object : Functor; X : Float) return Float;

Now let your integrator declared as:

function Integrate (What : Functor; From, To : Float);

Then you can use it as:

Integrate (Functor (Sin'Access), 0.1, 0.5);

2 Likes

Everyone has already discussed pros and cons. Here are a few examples in compilable code if you need.

With Ada.Text_IO; Use Ada.Text_IO;  
With Ada.Integer_Text_IO; Use Ada.Integer_Text_IO;

procedure Test is

	-- WAY #1
	-- Named subprogram access type parameter is used when you want to 
	-- "save" the passed in function for later
	type Function_Access is access function(Item : Integer) return Integer;
	function f1(f : Function_Access) return Integer;
	
	-- WAY #2
	-- Anonymous access parameter is used when you just want to execute
	-- it as soon as possible
	function f2(f : not null access function(Item : Integer) return Integer) return Integer;
			
	-- WAY #3
	-- Generic is another option
	generic
		with function f(Item : Integer) return Integer;
	function f3_generic return Integer;
	
	-- Junk implementations just for testing
	function f1(f : Function_Access) return Integer
		is (f(100));
	function f2(f : not null access function(Item : Integer) return Integer) return Integer
		is (f(100));
	function f3_generic return Integer is
		(f(100));
		
	-- Heres the function to pass in
	function Test(Item : Integer) return Integer is (2*Item);
	
	-- Jumk variables to hold results
	v1 : Integer := f1(Test'Access);
	v2 : Integer := f2(Test'Access);
	
	function f3 is new f3_generic(Test);
	
	v3 : Integer := f3;
		
begin
	null;
end Test;
1 Like

This won’t compile. Just keep:

  Function Generic_Evaluate( Start, Stop : Real ) return Real;
1 Like

No, I meant:

Generic
    Start, Stop : Real;
Function Generic_Evaluate return Real;

Because then it’s a no-parameter function you get from instantiation, becoming/illustrating exactly how design-decisions constrain and impact usage.