Call functions annotations

Topics: C# Language Design, VB Language Design
May 8, 2014 at 2:00 PM
Edited May 8, 2014 at 2:16 PM
Today, calling function system is based on 'black box model', where function is black box with optional inputs and output. This black box call internal methods, but these calls are invisible outside black box, which is problem sometimes.
Sub P1 (par1 as integer)
    ...
End Sub

Sub P2 ()
    ...
    P1 (5)
    ...
End Sub
In most cases we call P2(), and accept what P2 passes to P1 internally.
We call it:
P2()
But sometimes we want that P1 (from within P2) should by called with value defined outside P2, for example 10. There are two ways to achieve this:

Create additional parameters to P2:
Sub P2 (Optional P1OverrideCall as Boolean = False, Optional P1CallValue as Integer = 0)
    ...
    if P1OverrideCall Then P1 (P1CallValue)
    if Not P1OverrideCall Then P1 (5)
    ...
End Sub
Then we call it:
P2(True, 10)
But this way, P2 is polluted with parameters that are rarely used. If we have more deeper call tree and more parameters to transfer to P1, then this way quickly create mess. Other problem is that P2 (or other function that call P1), may be part of compiled library, and there is no way to modify it.

Modyfing P1:
Dim P1OverrideCall as Boolean
Dim P1CallValue as Integer
Sub P1 (par1 as integer)
    if P1OverrideCall Then par1 = P1CallValue
    ...
End Sub
Then we call it:
P1OverrideCall = True
P1CallValue = 10
P2()
Better, but still far from elegant, and limited if we have multiple calls to P1 from within P2.


Better way to pass these external parameters, is to implement call annotations. This way we can annotate function call, and use this annotation from outer function calls.

Modified P2 with annotation:
Sub P2 ()
    ...
    meta P1Call P1 (5)
    ...
End Sub
Then we call it:
P2() (P1Call.par1 = 10)
If we enhance P1 with annotation call to P0:
Sub P1 (par1 as integer)
    ...
    meta P0Call P0 (7)
End Sub
then we can pass outer values also to this call
P2() ' without external parameters
P2() (P1.P0Call.par0 = 77) ' for all P1 calls
P2() (P1Call.P0Call.par0 = 77) ' only for specifed call of P1, even if P2 call P1 multiple times
We can extend these annotations to expressions:
Sub P1 (par1 as integer)
    Dim Val = meta ValExpr 11
    ...
    meta P0Call P0 (7)
    P0 (Val)
End Sub
and we use it:
P2() ' without external parameters
P2() (P1.ValExpr = 12) ' for all P1 calls
P2() (P1Call.ValExpr = 12) ' only for specifed call of P1, even if P2 call P1 multiple times
Annotations allow do make visible call functions tree, and pass values to specified named nodes, if necessary. This way opaque 'black box model', can by partially transparent and more usefull.
May 8, 2014 at 4:41 PM
The use of external variables to select special options is sometimes a reasonable approach in things like memory-constrained embedded systems, but makes use of the methods in thread-safe fashion almost impossible. The only possible thread-safe mechanism I can see for to implement what I think you're describing would be to have the parameters for the inner function call placed in an open-field struct, and having the outer method receive a delegate that would take that structure type as a ref parameter and apply its desired changes.

I think a somewhat more broadly useful pair of features would be to first of call, allow a method to declare and tag a parameter of a flags enum type whose members would correspond to the other parameters, and which would indicate which parameters had been specified. This would allow the main parameters to be efficiently usable directly, unlike parameters of type Nullable<T> whose values would first need to be extracted in order to actually be useful.
May 9, 2014 at 8:18 AM
@codefox: I fail to see how these annotations are better than having few P2() overloads. Overloads are available already and are much simpler to resonate about. And using them you don't need to pollute your "primary" overload with optional parameters
May 9, 2014 at 8:57 AM
Edited May 9, 2014 at 8:57 AM
@Przemyslaw: Overloads means code duplication, which is much bigger pollution than adding optional parameters to 'primary' function.
May 12, 2014 at 7:50 AM
codefox wrote:
Overloads means code duplication
Not if all overloads call the "core" overload which has implementation and pass all parameters and supply default values. But you are right. All this is tedious code and optional parameters are supposed to address exactly this problem. You seem to not like them however.

But, in my opinion, proposed idea breaks encapsulation of method body. Methods will be hard to call if one needs to analyze their body in order to call them right. Also tooling needs to be adjusted. How do you expect intellisense to work with this feature? Do you expect to see these annotated parameters next to regular parameters?
If yes, then this feature can be seen as a way to define optional parameters "inline". But then, you don't need additional syntax at the caller side.
May 12, 2014 at 3:53 PM
There are certainly some situations where it would be helpful to be able to declare a method similar to
void doSomething<T1,T2>(int param1, Action<T1,T2> passedInProc, T1 p1,T2 p2)
{
   doStuff();
   passedInProc(p1,p2);
   doMoreStuff();
}
but be able to work with any kind of delegate, rather than just those that take exactly two parameters. Conceptually, the code would be something like:
void doSomething(int param1, Delegate(...) passedInProc, ...)
{
   .. do stuff ..
   passedInProc(...);
   .. do more stuff ..
}
Where the delegate would be required to take arguments of the same type as the ... [note that in some very important usage cases, some or all of the arguments may need to be byrefs!] and the arguments passed to the passedInProc would match those supplied by the outside caller.

At present, the Framework's generic mechanisms would have no facility for supporting variable numbers of parameters or freely-intermixed byref parameters here. Nonetheless, there could be some usefulness to allowing one method definition in source code to generate methods that can accept delegates of one parameter, two parameters, three parameters, etc. At present there's no particularly elegant way of accomplishing that if .. do more stuff .. would need to use values computed in .. do stuff ... One could probably, without adding too much inefficiency at run-time, hoist all such variables into a private structure with exposed public fields, which would be declared in each doSomething and passed by ref to methods implementing both do stuff and do more stuff, but that would seem a bit icky.