Subscribe for automatic updates: RSS icon RSS

Login icon Sign in for full access | Help icon Help
Advanced search

Pages: [1] 2
  Reply  |  Print  
Author Topic: Monday - Plenary - vNext: Language Modernization - Rene Schacht  (Read 3789 times)
Reuben B.
Four Js
Posts: 834


« on: November 16, 2020, 12:12:57 pm »

Quote
vNext: Language Modernization

This thread will be used to collate questions and answers from the live chat of Rene's presentation and any questions you may have after watching a recording.

Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero
David H.
Posts: 158


« Reply #1 on: November 20, 2020, 06:03:47 pm »

Hi all.

I've been a bit too busy with the day job to attend any sessions live so I'm just starting to work my way through the recordings. As a bit of an old school command line guy I really enjoyed this session and was impressed how Rene has managed to introduce some great new features without sacrificing backwards compatibility. I do have a couple of questions though:-

1. When using the Reflection API, can you inspect variables where the variable name is passed as string? I ask because we currently have a XML mapping layer where complex expressions embedded in the XML interact with runtime variables. Currently we have to do a lot of coding to retrieve those runtime variables that we could remove if you can inspect a variable where its name is passed as a string.

2. The XML mapping layer I mentioned also has the ability to call BDL functions. Are there any plans to be able to do things like check if a function exists (instead of calling it and potentially getting a runtime error) or for a specific function name, get details of its parameters and return values, so these can be checked again for better error handling?

For our specific needs in 1 and 2 it would be nice to have an interpreted 4GL like scripting language so small code blocks of business logic could be constructed which could interact with variables and SQL etc so we no longer have to invent and maintain our own mini (and rather basic) expression language!

TIA,

David

Leo S.
Four Js
Posts: 102


« Reply #2 on: November 25, 2020, 03:27:35 pm »

Hi David,
For 1:
There is no reflection API to set module or global scoped variables "by name" directly .
But you can pack your wanted variable set into a RECORD and then its fairly easy (see attached recordvarbyname.4gl)
The output would be:
{"foo":5,"d":"1999-01-02"}

For 2:
Are you referring to the om.SaxDocumentHandler class  (http://4js.com/online_documentation/fjs-fgl-manual-html/?path=fjs-fgl-manual#fgl-topics/c_fgl_ClassSaxDocumentHandler_example_1.html) with "XML mapping layer" ? Or is it a C-extension you did write ?

Regards, Leo

* recordvarbyname.4gl (0.59 KB - downloaded 111 times.)
Leo S.
Four Js
Posts: 102


« Reply #3 on: November 25, 2020, 03:37:05 pm »

And..forgot to ask : for your wanted expression parser it would be nice if you would give some examples of what you are after.
Regards, Leo
Reuben B.
Four Js
Posts: 834


« Reply #4 on: November 26, 2020, 12:46:14 am »

... Are there any plans to be able to do things like check if a function exists (instead of calling it and potentially getting a runtime error) ...

TIA,

David



David,

A technique you can use involves combining FUNCTION REFERENCES and DICTIONARY.

So scenario in below, you have a generic program and you want to put logic for wether each fieldname is visible into a FUNCTION that is named fieldname_visible() RETURNS BOOLEAN.  In 90%+ of cases, the result is always going to be TRUE and you don't want to create many functions like in the code below name_visible() that simply return TRUE, you want to spend your time on the functions that contain logic such as salary_valid() (only return TRUE if the user has appropriate permissions to view employee salaries, otherwise RETURN FALSE)

This technique maintains a dictionary of function references and the key of the dictionary is the field name.  At the beginning you register the function you actually have by inserting them into the dictionary, and then when you want to call the function for a particular fieldname, you first check if it is in the DICTIONARY using the contains() method.  If it is there you can safely call it by calling that dictionary entry, if it is not in the dictionary you return the default result. 


Code
  1. TYPE visible_function_type FUNCTION () RETURNS BOOLEAN
  2. DEFINE visible_function_list DICTIONARY OF visible_function_type
  3.  
  4. MAIN
  5.    CALL register_visible_functions()
  6.  
  7.    DISPLAY call_visible_function("name") -- function defined
  8.    DISPLAY call_visible_function("salary") -- function defined
  9.    DISPLAY call_visible_function("xxx") -- no function defined - returns  true
  10. {
  11.    Typical usage
  12.    BEFORE INPUT
  13.        FOR -- loop to iterate through each field in form
  14.             CALL ui.Form.setFieldHidden(fieldname, NOT call_visible_function(fieldname)
  15.        END FOR
  16. }
  17.  
  18. END MAIN
  19.  
  20. PRIVATE FUNCTION call_visible_function(field_name STRING) RETURNS BOOLEAN
  21.    -- Test if function has been registered
  22.    IF visible_function_list.contains(field_name) THEN
  23.        -- It has been registered so call it
  24.        RETURN visible_function_list[field_name]()
  25.    ELSE
  26.        --  No function has been registered for this fieldname, so assume TRUE, all users can see this field
  27.        RETURN TRUE
  28.    END IF
  29. END FUNCTION
  30.  
  31. PRIVATE FUNCTION register_visible_functions() RETURNS ()
  32.    LET visible_function_list["name"] = FUNCTION name_visible
  33.    LET visible_function_list["salary"] = FUNCTION salary_visible
  34. END FUNCTION
  35.  
  36.  
  37.  
  38. -- Business Logic
  39.  
  40. -- Not necessarily to explicitly code this function
  41. FUNCTION name_visible() RETURNS BOOLEAN
  42.    -- Can always see name field
  43.    RETURN TRUE
  44. END FUNCTION
  45.  
  46. -- Only want to explicitly code functions with logic
  47. FUNCTION salary_visible() RETURNS BOOLEAN
  48.    -- Can only see salary field if user has administrator permissions
  49.    IF administrator() THEN
  50.        RETURN TRUE
  51.    ELSE
  52.        RETURN FALSE
  53.    END IF
  54. END FUNCTION
  55.  
  56. FUNCTION administrator() RETURNS BOOLEAN
  57.    -- Test if user is an administrator, for test purposes return false
  58.    RETURN FALSE
  59. END FUNCTION



Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero
Snorri B.
Posts: 103


« Reply #5 on: November 30, 2020, 05:47:35 pm »

Sorry for the late question, but is something being considered for vNext that would solve this issue?
https://forum.4js.com/fjs_forum/index.php?topic=1398.0

Best regs,
-Snorri
David H.
Posts: 158


« Reply #6 on: November 30, 2020, 07:39:15 pm »

Hi again,

To be useful in our scenario we'd really need a by name option as that is all we know, something like reflect.Value.valueByNameOf("variablename"). If the "variable" does not exist then return NULL.

Things we can do currently with our rather crude expression language include:-

1. Get values of variables by name, i.e. {_REEN.reen_key_} = reen global record, reen_key field.
2. Combine multiple variable (concatentation)
3. Chop string variables to a given length.
4. Extract parts of a string variables (from position, to position etc).
5. Format variables (uppercase, lowercase, number, datetime, date formats etc)
6. Create Simple expressions (i.e. if condition then something else something else)
7. Get values by executing SQL statements.
8. Map variables from one value to another (via lookup tables)
9. Maintain multiple counters (get current value, next value etc like sequences)
10. Define basic conditions (AND OR NOT < <- > >= = <> etc)
11. Pass variables as parameters.
12. Use SQL expressions to loop through multiple records and pass that data as parameters (like a record) that can be used in expressions/conditions etc.
13. Get environment variables.
14. Get resource (FGLPROFILE) variables.
15. Call BDL functions to get values.

We are basically mapping data from within our systems into the format another system requires. Some data just goes as is, other data is changed from one format to another or is perhaps conditional depending on other data etc. Sometimes additional data needs to be derived/supplemented to enrich our rather basic data, so the above allows us to define all this outside of the application in configuration files (we use XML), that can vary from one requirement to another and from one customer to another, without having to make any application changes.

Sorry its not easy to explain in a post!

Regards,

David


 
Leo S.
Four Js
Posts: 102


« Reply #7 on: December 01, 2020, 06:23:25 pm »

@Snorri Hi Snorri,I think it would be relatively easy in the GDC/GBC clients to implement a new frontcall "notifyChildren" with an action name as an additional parameter and pass this action to all 4GL apps in this client which were started as direct children with "RUN WITHOUT WAITING" (Need to think a while if that works also in a chain of RUN/RUN WITHOUT WAITING).
So in the  master program  one could write

Code:
CALL ui.Interface.frontCall("standard","notifyChildren",["new_order"],[number_of_children_caught_with_this_action])
and in the child program
Code:
ON ACTION new_order
   ...
Let me know if that would be sufficient for you and I create requests for GBC/GDC.
Obviously it won't work for all mobile configurations yet due to the current RUN WITHOUT WAITING limitations.
Regards, Leo
Leo S.
Four Js
Posts: 102


« Reply #8 on: December 01, 2020, 06:38:51 pm »

@David, thank you for the explanations.
I think your use case it pretty special (and I still don't get if your XML thingy is written in Genero or in something else).
I doubt that we are ever going to make variables and functions accessible by name in the reflection API because it undermines the type safety of the system.
Looking at other reflection systems like Java or Google Go: you simply can't do that.
It's also a problem of variable scopes: global, modal , local.
It should be possible however to have a DICTIONARY of reflect.Value and then you might register your wanted variable set by adding the variable names you want to expose in that dictionary.
Currently : You can't expose simple primitives that way , only RECORDs.
Please check in the upcoming early access program about your possibilities.
 
Regards, Leo
Snorri B.
Posts: 103


« Reply #9 on: December 01, 2020, 06:45:23 pm »

Thanks Leo.

I think this will be sufficient! Thanks.

Regards,
-Snorri
Reuben B.
Four Js
Posts: 834


« Reply #10 on: December 02, 2020, 12:13:16 am »

Snorri,

I wonder if Leos solution suffers the same fate as the existing base.MessageServer class http://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_fgl_ClassMessageServer.html in that the child program has to have a dialog open with that ON ACTION at the time of sending. 

I wonder if the better solution is to mimic what happens with Push Notifications http://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_fgl_mobile_push_notifications.html where the recipient has to explicitly ask the message server if there are any messages for them.  If so that might require a solution where we have our own equivalent of FCM/APN where the Push Service / Push Provider / Token DB / Token Maintainer are another provided service in FGLDIR/web_utilties/... For parent/child communication it would just require a few nuances such as the parent registering first before it launches children, and passing a token to its children and the children registering with this token.

Reuben


Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero
Leo S.
Four Js
Posts: 102


« Reply #11 on: December 02, 2020, 10:02:11 am »

I wonder if Leos solution suffers the same fate as the existing base.MessageServer class http://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_fgl_ClassMessageServer.html in that the child program has to have a dialog open with that ON ACTION at the time of sending. 
Reuben , it wouldn't be exactly the same fate. For example the recent "notificationpushed" and "cordovacallback" actions don't necessarily require that a dialog having this action is actually active and on screen. Instead those actions are triggered also later on if the program enters a dialog having this action (internally the client buffers the action until the wanted dialog appears..or it stays forever in the buffer. Duplicates are eliminated btw.). The same mechanism could be done for the planned notifyChildren frontcall. It isn't the case for base.MessageServer: the VM side handles this (but it could apply the same rules btw).

I wonder if the better solution is to mimic what happens with Push Notifications http://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_fgl_mobile_push_notifications.html where the recipient has to explicitly ask the message server if there are any messages for them.  If so that might require a solution where we have our own equivalent of FCM/APN where the Push Service / Push Provider / Token DB / Token Maintainer are another provided service in FGLDIR/web_utilties/...
I don't see much difference compared to poll in the database if something relevant happened, except that you have to maintain an additional machinery.
All you want is a reliable trigger in the sense of the notificationpushed action.

Regards, Leo


Eric B.
Posts: 10


« Reply #12 on: January 08, 2021, 09:16:29 pm »

Rene and Leo, I was excited and intrigued by the presentation on new language elements during WWDC.  I wanted to try some enhancement ideas out on everybody – if they makes sense, call them language suggestions.  They are pretty much all in the spirit of language modernization to add capabilities enjoyed by most recent general-purpose languages.  I’ll do one per post.

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:
FUNCTION b(x INT, y INT) RETURNS INT
RETURN x - y
END FUNCTION

FUNCTION test()
      DEFINE a FUNCTION(x INT, y INT) RETURNS INT
            RETURN x + y
      END FUNCTION

      LET b = a(1, 2)
      DISPLAY b  # 3

      VAR b = FUNCTION(x INT, y INT) RETURNS INT
                  RETURN x + y
            END FUNCTION
      DISPLAY b(1, 2)  # 3
      DISPLAY b(3, 4)  # 7
      DISPLAY OUTER.b(3, 4)  # -1
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):
TYPE t_adder FUNCTION b(x INT, y INT) RETURNS INT
RETURN x + y
END FUNCTION
DEFINE a, b t_adder

You could also do combinations that might allow you do to some interesting dynamic dispatching:
TYPE t_something FUNCTION(s STRING) RETURNS STRING

FUNCTION something_1(s STRING) RETURNS STRING
RETURN "Hello, ", s
END FUNCTION
FUNCTION something_2(s STRING) RETURNS STRING
RETURN s, ", Hello"
END FUNCTION

FUNCTION test()
DEFINE say_it FUNCTION (x STRING, y t_something) RETURNS STRING
RETURN y(x)
END FUNCTION
DEFINE a, b t_something

LET a = FUNCTION something_1
LET b = FUNCTION something_2
DISPLAY say_it("Leo", a)   # "Hello, Leo"
DISPLAY say_it("Leo", b)   # "Leo, Hello"
END FUNCTION
Eric B.
Posts: 10


« Reply #13 on: January 08, 2021, 09:21:48 pm »

Next idea:  User-defined exceptions.  Completely ripped off from other languages.  The declaration would be similar to the syntax of a variable definition with a type of EXCEPTION, including an optional status code to map a STATUS value to an exception.  There could be multiple CATCH clauses.  The CATCH clause would accept an optional defined exception name or system status code; if no name was provided, it would capture any generic exception not previously matched.

A status value-based exception, like e_TableNotFound below, would be triggered whenever a statement generated the given status.  A user-defined exception would be triggered using RAISE.

Bonus points for adding a FINALLY clause (although I guess it’s possible to work around that)!

For instance:
PRIVATE DEFINE e_PrivateBadStuff EXCEPTION
PUBLIC e_PublicBadStuff EXCEPTION

PUBLIC DEFINE e_RowLocked EXCEPTION -244

FUNCTION test()
   DEFINE e_LocalBadStuff EXCEPTION

   TRY
      IF myBadStuff() THEN
         RAISE e_LocalBadStuff
      END IF
   CATCH e_RowLocked
      DISPLAY "Next row is locked!"
   CATCH e_LocalBadStuff
      DISPLAY "Something bad happened!"
   FINALLY
      ROLLBACK WORK
   END TRY
END FUNCTION

I think this would greatly improve readability, familiarity to developers of other languages, reduce the temptation toward complicated CASE and IF nesting scenarios, and help clean up 4GL’s less-than-wonderful error handling patterns.
Eric B.
Posts: 10


« Reply #14 on: January 08, 2021, 09:28:11 pm »

Next idea:  I’ll call this one “record field getters and setters”.  The idea is a ripoff of Java beans and C# property getters and setters as applied to BDL record types.

The following code suggests how it might look, given the following record type:
TYPE  t_rec RECORD
   Username STRING,
   Active CHAR(1)
END IF

FUNCTION (self t_rec.Active SET) (d CHAR(1)) RETURNS ()
   CASE d
      WHEN d = '0' OR d = 'N' LET self = 'N'
      WHEN '1' LET self = 'Y'
      OTHERWISE LET self = 'N'
   END CASE
END FUNCTION

FUNCTION (self t_rec.Active GET) RETURNS CHAR(1)
   CASE self
      WHEN 'Y' RETURN '1'
      OTHERWISE RETURN '0'
   END CASE
END FUNCTION

FUNCTION (self t_rec) isActive() RETURNS BOOLEAN
   RETURN (self.Active = 'Y')
END FUNCTION

FUNCTION test()
   DEFINE r_rec t_rec

   LET r_rec.Username = “xyz1234”
   LET r_rec.Active = TRUE

   IF r_rec.isActive() THEN
      DISPLAY “User active!”
   END IF
END FUNCTION

Some basics:
•   I invented the syntax for the GET/SET functions modeled after interface functions.  There is no function name – I’m sure the compiler would construct one internally but a function name isn’t meaningful once you specify its purpose as a GET/SET function.  Don’t read too much into this – I’m just making this up and you can likely come up with something cleaner.
•   The function specification includes both a type (a record type) and a field name, assigned to “self”.  “self” is than assigned to put the desired result in the variable.
•   The SET function must accept one parameter of the same type as the field;  the GET function must return one value with the same type as the field.  The compiler checks for this at compile time.
•   Note the data type transformation in Active.  TRUE is converted to CHAR(1), which emits as either “0”, “1”, or null.  That is, the normal type transformations would be allowed (or disallowed) before the SET or after the GET.

Benefits:
•   To me, this greatly improves readability and consistency.  All data layer field mapping code is put in one spot.  The compiler automatically enforces it.
•   In fact, this could potentially be used to get rid of a whole class of dumb data storage errors that likely are common in old 4GL apps, without have to scrub every line of an old app.  You could get the benefits of this mapping by replacing RECORD LIKE mytable.* in a legacy app with a TYPE declaration on that record and the appropriate setter/getter functions.  I could see that being a big plus in migration.
•   Simple storage transformations (i.e. upper casing, encoding/encryption, changing a boolean to “Y” or “N”, etc.) become much easier to code and manage.  No more code like:
IF blah = 'N' THEN…
Only to find out that the test fails when the value is null.  Or:
IF blah = 'Y' OR blah = ‘y’ THEN…
… repeated ad nauseum because you can’t be totally sure what’s in there.  Or whatever.
•   It should cut way down on common patterns and opportunities for bugs like not testing consistently for nulls.
•   Helps segregate data layer code into a separate module dedicated to it.
Pages: [1] 2
  Reply  |  Print  
 
Jump to:  

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines