Four Js Development Tools Forum

Discussions by product => Genero BDL => Topic started by: Eric B. on January 25, 2021, 11:35:35 pm



Title: Language Modernization ideas for discussion - Idea 1
Post by: Eric B. on January 25, 2021, 11:35:35 pm
All,

I was excited about some of the things Rene and Leo demoed at WWDC and at Laurent and Reuben's urging, I'm copying these from the forum posting area to the general area to start some discussion.  I'll make a new thread for each idea...  sorry for taking so long to get here - the day job has definitely been in the way.

All the ideas are in the spirit of language modernization and pretty much all of them are unashamed ripoffs of things in other languages.  In a number of cases, they seemed (to me at least) to be logical outgrowths of things that, based on the WWDC presentations, will be in the 4.0 language spec.

So, the first idea; this idea is basically a ripoff of lambda expressions in other languages – call it “lambdas/private functions”.  The need/attraction is based on the idea that 4GL/BDL is a somewhat wordy language and tends to need significant amounts of boilerplate relative to other languages due to strong typing tending to prevent broad reuse (a reason I’m looking forward to the Reflection API).

As I understand it, you’ve basically already added two capabilities that should make the idea easier:
1.   VAR keyword for dynamic defines, in 4.0.
2.   FUNCTION types already in 3.20.

I think these could be combined to allow for lambas/inner private functions.  These would help condense cases where there is a lot of repetitive code but it’s small enough to dissuade declaration of a separate function.  I think C# and other compilers essentially do the same in their internal implementations.

The following code suggests how it might look, with the function created either in a DEFINE statement or a VAR statement:
Code
  1. FUNCTION b(x INT, y INT) RETURNS INT
  2.   RETURN x - y
  3. END FUNCTION
  4.  
  5. FUNCTION test()
  6.   DEFINE a FUNCTION(x INT, y INT) RETURNS INT
  7.      RETURN x + y
  8.   END FUNCTION
  9.  
  10.   LET b = a(1, 2)
  11.   DISPLAY b  # 3
  12.  
  13.   VAR b = FUNCTION(x INT, y INT) RETURNS INT
  14.      RETURN x + y
  15.   END FUNCTION
  16.   DISPLAY b(1, 2)  # 3
  17.   DISPLAY b(3, 4)  # 7
  18.   DISPLAY OUTER.b(3, 4)  # -1
  19. END FUNCTION

I can understand there's overlap between this and a module private function but the idea still seems worthwhile for short snippets or expressions. From a compiler perspective, the functions “a” and “b” are generated as an anonymous type and function private to the declaring scope, then the VAR is defined to that type.  The functions above are both private to test() – the scoping follows the current variable scoping rules.  So the inner FUNCTION block would in essence be acceptable wherever:
1.   A type declaration can be used
2.   A function reference can be assigned using the FUNCTION keyword already in 3.20, i.e. “LET a = FUNCTION stuff”

Up for debate as to whether shadowing is allowed on the function names.
  • If allowed, any existing outer function with the same name would be shadowed by default – the inner function executes in its place.  You could allow referencing the outer function using a keyword name like OUTER as shown above…
  • Or… it generates a compiler error if that’s not consistent with other aspects of the language.
  • Or… it generates a compiler warning that could be disabled via directive.
I’m thinking if you don’t allow shadowing of variables, you wouldn’t allow it for functions for consistency…  but worth thinking about tradeoffs since there are benefits to it, but at the risk of some reduced clarity and opportunity for creating hidden issues.

I think the above could give the following benefits:
  • Easier to read code – use an expressive function name rather than repeating the underlying statement(s)
  • Easier to maintain – small similar snippets are encapsulated
  • Easer to document – document the lamba, not all its usages
  • Reduced clutter – no need to have lots of little free-standing functions to achieve the effect
  • Keeps small bits of related code together – the declaration is close to the uses
  • Encourages modularization – make a lambda/inner function…  and if it grows too big or is useful in a broader context, it can be “promoted” by cut/pasting the function to the higher level scope, making a TYPE, and replacing any defines with the type.
  • If you can find a more “terse” declaration syntax that’s consistent with 4GL, I’d be for that!  A key advantage of the idea is to reduce the amount of code needed to create the reusable code block – the shorter it is (while still being easy to remember/consistent) the more useful, I think.

PS – Using the rules I give above, the following would be legal (although the example is contrived):
Code
  1. TYPE t_adder FUNCTION b(x INT, y INT) RETURNS INT
  2.   RETURN x + y
  3. END FUNCTION
  4. DEFINE a, b t_adder

You could also do combinations that might allow you do to some interesting dynamic dispatching:
Code
  1. TYPE t_something FUNCTION(s STRING) RETURNS STRING
  2. ...
  3. FUNCTION something_1(s STRING) RETURNS STRING
  4.   RETURN "Hello, ", s
  5. END FUNCTION
  6. FUNCTION something_2(s STRING) RETURNS STRING
  7.   RETURN s, ", Hello"
  8. END FUNCTION
  9.  
  10. FUNCTION test()
  11.   DEFINE say_it FUNCTION (x STRING, y t_something) RETURNS STRING
  12.      RETURN y(x)
  13.   END FUNCTION
  14.   DEFINE a, b t_something
  15.  
  16.   LET a = FUNCTION something_1
  17.   LET b = FUNCTION something_2
  18.   DISPLAY say_it("Leo", a)   # "Hello, Leo"
  19.   DISPLAY say_it("Leo", b)   # "Leo, Hello"
  20. END FUNCTION


Title: Re: Language Modernization ideas for discussion - Idea 1
Post by: Rene S. on January 28, 2021, 03:58:38 pm
Hello,
What is the problem with lambdas and inner functions?

Inner functions have access to the local variables of the enclosing function. An implementation is not rocket science. But stop: what about functions references? A function reference is (as in C) nothing else than the address of a function. This conflicts with inner functions. An inner function is not addressable. Remember: when calling the inner function via a function reference then access to the local variables of the former enclosing function must be possible. In other words, the reference to a inner function requires also references to the local variables of the enclosing function.

Teach yourself: try a Java lambda. perform the command line tool javap to analyze what the compiler has done. The compiler creates for each lambda a hidden class. This class has any variable of the current context used within the lambda as a member variable. The lambdas itself is a (interface-) method of this class. The java compiler produces an error, if the code in the lambda accesses none-final or none de facto final variables. The job for the java compiler is a nightmare (from my perspective).

Lambdas and inner functions? No chance for an implementation in Genero-4GL: a mountain (not a hill, a mountain) of work.

Rene


Title: Re: Language Modernization ideas for discussion - Idea 1
Post by: Reuben B. on February 02, 2021, 02:01:40 am
Eric,

Quote
These would help condense cases where there is a lot of repetitive code but it’s small enough to dissuade declaration of a separate function
...
Reduced clutter – no need to have lots of little free-standing functions to achieve the effect
Keeps small bits of related code together – the declaration is close to the uses
...
If you can find a more “terse” declaration syntax that’s consistent with 4GL, I’d be for that!  A key advantage of the idea is to reduce the amount of code needed to create the reusable code block – the shorter it is (while still being easy to remember/consistent) the more useful, I think.

I'm not sure it helps, but it meets a number of things above is that VAR allows creation of variables inside pre-processor macros.  So for instance this will now be possible ...

Code
  1. DEFINE a,b,c INTEGER
  2.  
  3.    &define swap(p1,p2) if 1=1 then \
  4.    var tmp_swap_variable = p1 \
  5.    let p1 = p2 \
  6.    let p2 = tmp_swap_variable \
  7.    end if
  8.  
  9.    let a = 1
  10.    let b = 2
  11.    let c = 3
  12.  
  13.    display a,b,c
  14.    swap(a,b)
  15.    display a,b,c
  16.    swap (b,c)
  17.    display a,b,c