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: Why I hate the Concatenate operator...  (Read 30017 times)
.
Posts: 7


« Reply #15 on: October 05, 2009, 12:16:08 am »

The 2009 version looks as follows:

Code
  1. FUNCTION nvl(l_original, l_if_null)
  2.  
  3. DEFINE
  4.   l_original   STRING,
  5.   l_if_null    STRING
  6.  
  7. DEFINE
  8.   l_result   STRING
  9.  
  10.   IF l_original IS NULL
  11.   OR LENGTH(l_original) == 0 THEN
  12.       LET l_result = l_if_null
  13.   ELSE
  14.      LET l_result = l_original
  15.   END IF
  16.  
  17.   RETURN l_result
  18.  
  19. END FUNCTION
Sebastien F.
Four Js
Posts: 545


« Reply #16 on: October 05, 2009, 09:34:42 am »

Reuben,


I used to have a nvl() and if() in our libraries in my previous job.

The 1996 version of our nvl is available in the IIUG software repository

FUNCTION nvl(l_original, l_if_null)
DEFINE  l_original                     CHAR(80),
        l_if_null                      CHAR(80)

    IF LENGTH(l_original) = 0 THEN
        RETURN l_if_null
    ELSE
        IF l_original[1]="$" THEN   # Cope with money fields
           LET l_original[1]=" "
        END IF
        RETURN l_original
    END IF

END FUNCTION


Yes I expected that people can write such a utility function....

However if we would implement that as a real operator, I believe we could keep the original type of the value, and that's important for example when formatting values for output:

Try this:

Code
  1. MAIN
  2.    DEFINE x1, x2 DECIMAL(10,2)
  3.    LET x1 = 1234.56
  4.    LET x2 = NULL
  5.    DISPLAY "Value x1         : [", x1, "]"
  6.    DISPLAY "Value x2         : [", x2, "]"
  7.    DISPLAY "Value x1 MOD 2   : [", x1 MOD 2, "]"
  8.    DISPLAY "Value x2 MOD 2   : [", x2 MOD 2, "]"
  9.    DISPLAY "Value NVL(x1,0)  : [", NVL(x1,0), "]"
  10.    DISPLAY "Value NVL(x2,0)  : [", NVL(x2,0), "]"
  11. END MAIN

You get:

Code
  1. Value x1         : [     1234.56]
  2. Value x2         : [            ]
  3. Value x1 MOD 2   : [          0]
  4. Value x2 MOD 2   : [      ]
  5. Value NVL(x1,0)  : [1234.56]
  6. Value NVL(x2,0)  : [0]

Seb
Andrew C.
Posts: 48


« Reply #17 on: January 05, 2010, 01:47:33 am »

Sorry to come so late to the thread, but if I may play devil's advocate for a moment before I get serious?

If you add a IFF(cond, expr1, expr2) type of operator, can it be defined so that

Code
  1. let expr = null
  2. let x = iff(expr, 10, 20)

yields null? it's only consistent, and I think more correct. If people want to avoid that, then of course the suggested NVL function will help them:

Code
  1. iff(nvl(expr, false), 10, 20)

So rewriting Reuben's IFF function, I would have written:

Code
  1. function IFF(cond, x, y)
  2.    case cond
  3.      when cond != false return x
  4.      when cond == false return y
  5.      otherwise return null
  6.    end case
  7. end function
(I am using tests against false (aka zero) just so that cond can actually be any non-zero number for truth)

Also the point about both x and y being evaluated before the call is definitely a problem so I'm not a fan of this type of work-around function.

What's more curious to me is the apparent fear of NULL that is so common both in my company and around the world. I agree, the null-behaviour of || is kind-of annoying, but technically it's more correct, and I must confess that the annoyance factor is only because I'm so used to the polite but technically inappropriate behaviour of the comma concatenate operator.

I don't understand the need to "code around" the so-called null problem. I find that null's behaviour is great for REDUCING code. There are a few simple tricks that help:

Code
  1. if a >= b or a is null or b is null then DO_WORK end if

becomes

Code
  1. if a < b then else DO_WORK end if

This is a trivial trick for inverting the flow and reducing code when you need it. The sample CASE statement in my function above is also often surprising to many people but it's a great technique. A built-in NVL would be fantastic because it could return the real object with it's original data type instead of all being converted to a string. A built-in IFF() would be nice - how's about allowing more pairs too?

Code
  1. iff(c1, r1, c2, r2, c3, r3, e1)

for an elif effect.

There is one critical point I would like to make about the choice of operator: I do not like the suggestion that the keyword IF be used as a function. We are writing a lot of code-metric scripts, so having to cope with a STATEMENT keyword as a possible operator would play havoc with the parsers. I know that 4GL and SQL are not supposed to have keywords as such, but seriously, I'm sure we're not the only company on the planet writing ad-hoc parsers for 4GL. PLEASE do not use the IF keyword!

Why not use the world-standard ternary operator ?: from C? Neither ? nor : are important tokens in 4GL

Code
  1. let x = cond ? a : b
  2. call ftn(cond ? a : b, c, d)

looks fine to me. If you choose to go this way, could I also request that you resist the temptation to make it ass-backwards like Python's inline     a if cond else b     rubbish? There's merit in copying the style of the familiar rather than trying to be too clever...

Finally, a built-in concat() operator would be very nice and would increase overall comfort.

Just my 2 pfennigs worth.
Andrew C.
Posts: 48


« Reply #18 on: January 05, 2010, 02:51:39 am »

I had a failure to new-paragraph in my previous post:
Quote
This is a trivial trick for inverting the flow and reducing code when you need it. The sample CASE statement in my function above is also often surprising to many people but it's a great technique. A built-in NVL would be fantastic because it could return the real object with it's original data type instead of all being converted to a string. A built-in IFF() would be nice - how's about allowing more pairs too?

should be

Quote
This is a trivial trick for inverting the flow and reducing code when you need it. The sample CASE statement in my function above is also often surprising to many people but it's a great technique.
[NEW PARAGRAPH]
A built-in NVL would be fantastic because it could return the real object with it's original data type instead of all being converted to a string. A built-in IFF() would be nice - how's about allowing more pairs too?
Reuben B.
Four Js
Posts: 1119


« Reply #19 on: January 06, 2010, 12:39:33 am »


I don't understand the need to "code around" the so-called null problem. I find that null's behaviour is great for REDUCING code. There are a few simple tricks that help:

Code
  1. if a >= b or a is null or b is null then DO_WORK end if

becomes

Code
  1. if a < b then else DO_WORK end if

This is a trivial trick for inverting the flow and reducing code when you need it.

Agreed.  If I want to check that a is less than b, the temptation is to test only for the error condition...

Code
  1. IF a>=b THEN
  2.   ERROR "A must be less than B"
  3.   NEXT FIELD CURRENT
  4. END IF

which if a or b is NULL will have an unexpected result.

Better to test for the good condition

Code
  1. IF a<b THEN
  2.   # Ok Do nothing
  3. ELSE
  4.   ERROR "A must be less than B"
  5.   NEXT FIELD CURRENT
  6. END IF

and if a or b is NULL then the test will fail as well. 


Quote
If you add a IFF(cond, expr1, expr2) type of operator, can it be defined so that


Code: (genero)
let expr = null
let x = iff(expr, 10, 20)
yields null? it's only consistent, and I think more correct. If people want to avoid that, then of course the suggested NVL function will help them:

...or maybe consider in addition to  IFF(expr,10,20)

IFFN(expr,10,20,NULL) -- returns last argument if expr IS NULL

... and keep everyone happy.





Quote
Also the point about both x and y being evaluated before the call is definitely a problem so I'm not a fan of this type of work-around function.

I have often wondered if we should have a means to specify a lazy AND, and a lazy OR so that ...

IF A AND B
IF A OR B

... B is only evaluated where necessary.  It also helps with coding the else expression once rather than twice e.g.

Code
  1. IF A lazy-and B THEN
  2.   # OK
  3. ELSE
  4.   C
  5. END IF
  6.  
  7. versus
  8.  
  9. IF A THEN
  10.   IF B THEN
  11.      #OK
  12.   ELSE
  13.      C
  14.   END IF
  15. ELSE
  16.   C
  17. END IF





Quote
Why not use the world-standard ternary operator ?: from C? Neither ? nor : are important tokens in 4GL

Genero Report Writer uses the ?: notation. e.g. to display negative numbers in a red font ...

field.value<0?Color.RED:Color.BLACK

I wasn't a big fan of this as it is not consistent with our existing 4GL, and to the untrained eye it is not readable. 
Given the choice between

Code
  1. IF expr THEN
  2.   A
  3. ELSE
  4.   B
  5. END IF
  6.  
  7. IF(expr,A,B)
  8.  
  9. expr?A:B

I'd prefer to code IF(expr,A,B) in both products as it is both readable and precise.

Reuben

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


« Reply #20 on: January 07, 2010, 12:00:50 am »

Quote from: Reuben
Agreed.  If I want to check that a is less than b, the temptation is to test only for the error condition...

Code
  1. IF a>=b THEN
  2.   ERROR "A must be less than B"
  3.   NEXT FIELD CURRENT
  4. END IF

which if a or b is NULL will have an unexpected result.

Better to test for the good condition

Code
  1. IF a<b THEN
  2.   # Ok Do nothing
  3. ELSE
  4.   ERROR "A must be less than B"
  5.   NEXT FIELD CURRENT
  6. END IF

and if a or b is NULL then the test will fail as well. 

Ummm, but if a or b is null, then you CAN'T say a has a bad value in relation to b. You don't know. First you need to tell the user that a and b must be filled in, and that's a separate test and separate error message if you care about clarity. Or, if the required fields are only tested at ACCEPT (as our code does), then the first version is better because it politely keeps quiet about fields the user hasn't filled in yet. However! that's an argument over UI style, and either case shows that choosing which way to handle null can be seamless rather than a chore. QED.
Pages: 1 [2]
  Reply  |  Print  
 
Jump to:  

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines