Subscribe for automatic updates: RSS icon RSS

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

Pages: [1]
  Reply  |  Print  
Author Topic: NULL comparison in FGL  (Read 11448 times)
Huy H.
Posts: 45


« on: March 20, 2019, 05:01:21 pm »

Want to get your feed back on how FGL handles NULL value when comparing variables. 

I've had developers come to me asking why their code doesn't work and they cannot figure out why:
Code
  1. DEFINE cfg_val CHAR(1)
  2. LET cfg_val = get_cfg_val() -- can return null
  3. IF cfg_val != 'Y' THEN
  4.  { code here }
  5. END IF
  6.  

I've known for a while now that when I work with variable that can possibly be NULL, I have to handle it appropriately with IS NULL check. Something to the line of:

Code
  1. DEFINE cfg_val CHAR(1)
  2. LET cfg_val = get_cfg_val() -- can return null
  3. IF cfg_val IS NULL OR cfg_val != 'Y' THEN
  4.  { code here }
  5. END IF
  6.  

I understand this goes back to the way how NULL are handled in databases, but for a programming language, this is a very odd behavior.  I get blank stares and head scratches when I try to explain to other developers.  For cfg_val != 'Y' to return FALSE because cfg_val is NULL just does not make any sense.
 
Sebastien F.
Four Js
Posts: 509


« Reply #1 on: March 20, 2019, 05:18:12 pm »

Hello,

In such case, I would recommend to make sure that the get_cfg_val() function returns either "Y" or "N" ... but never NULL.

Note also that Genero has the NVL() operator ...

Seb
David H.
Posts: 158


« Reply #2 on: March 21, 2019, 02:26:20 pm »

I feel your pain and had similar issues here with developers. In the end we ended up with two functions that they can wrap around any potentially NULL expressions and decide how they want the NULL case to be treated.

Code
  1.  
  2. FUNCTION null_false(argument)
  3.  
  4.    DEFINE argument SMALLINT
  5.  
  6.    IF (argument IS NULL) THEN
  7.        RETURN FALSE
  8.    END IF
  9.    RETURN argument
  10.  
  11. END FUNCTION
  12.  
  13.  
  14.  
  15. FUNCTION null_true(argument)
  16.  
  17.    DEFINE argument SMALLINT
  18.  
  19.    IF (argument IS NULL) THEN
  20.        RETURN TRUE
  21.    END IF
  22.    RETURN argument
  23.  
  24. END FUNCTION
  25.  
  26. IF null_true(cfg_val != "Y") THEN
  27.   -- do something
  28. END IF
  29.  
  30.  

This makes it very clear how the NULL case is treated!

David
Leo S.
Four Js
Posts: 126


« Reply #3 on: March 21, 2019, 02:42:49 pm »

Huy, I recently use String.equals() to minimize the NULL surprise whenever possible.
If your return value is a CHAR[40] this isn't a String for sure, but you can use the String constants equals() method and compare the other way round.
HTH, Leo

Code
  1. GLOBALS
  2.  CONSTANT g_yes="y"
  3. END GLOBALS
  4. ...
  5. MAIN
  6.  IF g_yes.equals(get_cfg_val()) THEN
  7.    DISPLAY "yes"
  8.  END IF
  9.  IF NOT g_yes.equals(get_cfg_val()) THEN
  10.    DISPLAY "not yes"
  11.  END IF
  12. END MAIN
  13.  
  14.  
  15. FUNCTION get_cfg_val()
  16.  RETURN NULL
  17. END FUNCTION
  18.  
Huy H.
Posts: 45


« Reply #4 on: March 21, 2019, 03:06:57 pm »

Interesting approach Leo. 

Most of the time, the values we are dealing with are database field values, so they are char(n). If you can only get "y".equals(cfg_val) to compile instead of having to use a constant g_yes.equals(), that would be golden! 
Reuben B.
Four Js
Posts: 1049


« Reply #5 on: March 21, 2019, 09:28:47 pm »

Huy,

You aren't the first, and won't be the last.  I did a search and found this old Informix-4gl forum post form from 2005 that diverges into discussing this topic https://comp.databases.informix.narkive.com/VEQoSucd/4gl-compares-null-differently.

The 4gl behaviour is consistent with database logic.  Note the output from this little test

Code
  1. CREATE TABLE foo(key INTEGER, data CHAR(10))
  2. INSERT INTO foo VALUES(1,"Y")
  3. INSERT INTO foo VALUES(2,"N")
  4. INSERT INTO foo VALUES(3,NULL)
  5.  
  6. DISPLAY "Not Equal Y"
  7. DECLARE neqy CURSOR  FROM "SELECT key FROM foo WHERE data != 'Y'"
  8.  
  9. FOREACH neqy INTO key
  10.    DISPLAY key
  11. END FOREACH
  12.  
  13. DISPLAY "Equal Y"
  14. DECLARE eqy CURSOR  FROM "SELECT key FROM foo WHERE data = 'Y'"
  15.  
  16. FOREACH eqy INTO key
  17.    DISPLAY key
  18. END FOREACH
  19.  
  20. DISPLAY "is null"
  21. DECLARE in CURSOR  FROM "SELECT key FROM foo WHERE data IS NULL"
  22.  
  23. FOREACH in INTO key
  24.    DISPLAY key
  25. END FOREACH
  26.  
  27. DISPLAY "is not null"
  28. DECLARE inn CURSOR  FROM "SELECT key FROM foo WHERE data IS NOT NULL"
  29.  
  30. FOREACH inn INTO key
  31.    DISPLAY key
  32. END FOREACH



Code:
Not Equal Y
          2
Equal Y
          1
is null
          3
is not null
          1



How does the database solve this?  You define the column as NOT NULL and then you never have to worry about the fact that !="Y" won't include NULL values.

Seb's solution of ensuring that your function get_cfg_val() never returns NULL you could say is the equivalent.  

Could we ever do something on the 4gl as an equivalent e.g.

Code
  1. FUNCTION get_cfg_val() RETURNS CHAR(1) NOT NULL
... I don't know.  For now you can code at the end of the function ...

 
Code
  1. RETURN nvl(cfg_val, "N")

... and ensure that NULL is never returned.

OR now that we have initialisers could we then code

Code
  1. DEFINE cfg_val CHAR(1) NOT NULL = "Y"

and have a runtime error if the variable value is ever NULL just like a database.  I don't think this would be practical as initialisers aren't aways simple values.


I am a pessimist in this area and think that try as you might, a NULL might sneak in somewhere, and so I prefer a technique that was in that old Informix-4gl link.  We called it "test for success" so rather than coding


Code
  1. IF get_cfg_val() != "Y" THEN
  2. ERROR "CFG must be Y"
  3. ...
  4. END IF


you code


Code
  1. IF get_cfgl_val() = "Y" THEN
  2.   # Value OK
  3. ELSE
  4.   ERROR "CFG must be Y"
  5.   ...
  6. END IF


that way if get_cfg_val() does return NULL, it will be caught as you intend

Reuben










« Last Edit: March 21, 2019, 09:38:17 pm »

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


« Reply #6 on: April 05, 2019, 09:29:38 pm »

Sorry to be so late to the party on this one, but perhaps you need to have a discussion with your developers about what NULL really means.  I have found that when they truly understand NULL, this makes a bit more sense.  From your description, I know you understand what's going on, but the following may help you explain things to your developers.

In the old days where everything was done with flat files and COBOL, programmers would typically initialize values with "" (empty string) for character fields and 0 (zero) for numeric fields.  The unfortunate side effect of this is that it implies something that may or may not be true.  The value of the field in question may or may not really be an empty string or a zero, but there is no way to tell whether the value in the field is the correct value or simply the default that was chosen as an default.

Relational databases introduced the NULL to address that. 

What I tell developers is the NULL equates to "I don't know".  How can you compare a known value to "I don't know"?  Comparisons require that both values being compared are known, otherwise you can't have a valid comparison.

Using your example (not sure how to get the pretty box you had around your code):

DEFINE cfg_val CHAR(1)
LET cfg_val = get_cfg_val() -- can return null
IF cfg_val != 'Y' THEN
 { code here }
END IF

if cfg_val is "I don't know", you can't say for certain whether it is or is not equal to any particular value.  That is the reason that the rules of relational databases requires any comparison with one or more NULLs to always return false.  That's why you can't say:

if cfg_val = NULL THEN ...

The hardcoded NULL on the right side of the equal sign would cause the comparison to be false even if cfg_val was also NULL.  That's why the only way to compare something to NULL is with the IS NULL or IS NOT NULL expression.  Or, when available, you can use the nvl() function as an alternate approach.

I hope this helps your developers.
Reuben B.
Four Js
Posts: 1049


« Reply #7 on: April 08, 2019, 12:34:16 am »

Quote
(not sure how to get the pretty box you had around your code):

Above the textedit where you type in your comment, you will see some buttons and two ComboBoxes Change Color and Code Syntax.

Simply highlight the text and either click the

Quote
"Insert Quote" button to mark text as a quote like this
or

Code
  1. # the "Code Syntax" button and select the style of code (in most cases, Genero, per. or XML) so that your code is in a fixed-font, line numbered, keywords highlighted like this

Product Consultant (Asia Pacific)
Developer Relations Manager (Worldwide)
Author of https://4js.com/ask-reuben
Contributor to https://github.com/FourjsGenero
Pages: [1]
  Reply  |  Print  
 
Jump to:  

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines