nil logic feature request

Started by Dmi, August 24, 2005, 11:22:47 PM

Previous topic - Next topic

Dmi

Lutz!



Now some functional constructions such as:
(parse (lookup "key" some-list 1) "delimiter")
can't be constructed because (lookup) can return nil, but parse can't handle nil.



So I must break execution flow into 2 separate operations with temporary variable.

Something like:
(let (p (lookup "key" some-list 1)) (if p (parse p "delimiter)))
Looks quite ugly and less readable.



Can (parse) and similar functions simply return nil if they expect string/list argument, but it is nil?



Possible this can be done in manner of sintax branching:
Quotelast

syntax: (last list)

syntax: (last str)

syntax: (last nil)
WBR, Dmi

Lutz

#1
In an interpreted language, where variables are created on the fly when they occur, misspelliings occur and make passing uninitialized variables passing 'nil' to functions a common error. This is specially true in programming teams, where calling conventions of functions written by others get violated frequently.



Strict type checking for legal types of arguments in functions helps to catch these errors. I think allowing 'nil' in parse as an agrument would potentially hide errors and make code harder to debug.



Lutz

Dmi

#2
I can't be disagree with that! :-)



But I'm confused with the fact that in the result, clear, functional, human-readable phrase is need to be transformed to complex algorithm with tricks, that doesn't ports any useful functionality.

I.e., in my example, we need to use "let", temp. variable, "if", a bunch of parantehisis ony for simply signalling that "nil" will be ok here. Also we got  a visually broken calculations order.



It would be cool to have more lightweight trick here, I think.



What do I want - is to save functional way in some code where now I must use algorithmic tricks.

Frequently I meet situation where this can be done by allowing inner functions to break execution flow of next outer one.

Sort of "near" throw w/o catch.

Consider function "break", working as follows:
Quote(break expr1 expr2)

If the result of expr1 is not nil it is returned.

Else expr2 is evaluated and it's result is returned _instead_ of next outer function.

If expr2 is missing, nil is returned.

Simple example:
(parse (break (lookup "key" some-list 1)) "delimiter")
This solution:

- still requiring explicit care about nil appearance.

- has functional style that keeping clear readability

- often allowing to eliminate temp. variables
WBR, Dmi

nigelbrown

#3
How about:

> (setq some-list   '(("name" "John Doe") ("age" "35") ("gender" "Mere male") (balance 12.34)))

(("name" "John Doe") ("age" "35") ("gender" "Mere male") (balance

  12.34))

> (dolist (x (filter string? (list (lookup "age" some-list 1)))) (parse x "delimeter"))

("35")

> (dolist (x (filter string? (list (lookup "sex" some-list 1)))) (parse x "delimeter"))

nil



Still convoluted but maybe more lispy?



and hide it in a macro as lisp is a roll-your-own language:



> (define-macro (my-parse _s _d) (dolist (x (filter string? (list (eval _s)))) (parse x  _d)))

(lambda-macro (_s _d)

 (dolist (x (filter string? (list (eval _s))))

  (parse x _d)))

> (my-parse (lookup "age" some-list 1) "delimiter")

("35")

> (my-parse nil "b")

nil

> (my-parse "abc" "b")

("a" "c")

> (my-parse (lookup "sex" some-list 1) "delimiter")

nil

>



Nigel

Dmi

#4
dolist on one-item list looks more lispy.

But still it is an complex algorithm, rised in place of simply missing feature.

It gives too big overhead for avoiding nill errors, I think.

It still also broke simplicity and readability, so potentially can cause logical errors.

And it still is ugly looking for everyday usage ;-)



define-macro is unacceptable here because I speak not only about "lookup/parse", but about many of string and list processing functions, that does type checking.
WBR, Dmi

nigelbrown

#5
Perhaps a command line option eg

newlisp --ignore-not-string-error

to continue on returning nil if non-string turns up by

Looking at the source of regex as an eg (see ******************** section)

from nl-string.c in source talball



CELL * p_regex(CELL * params)

{

pcre *re;

int ovector[OVECCOUNT];

int rc, idx;

char * pattern;

char * string;

int options, len;

CELL * cell, * result, * strCell;



cell = getString(params, &pattern);

strCell = evaluateExpression(cell);

if(strCell->type != CELL_STRING)

   return(errorProcExt(ERR_STRING_EXPECTED, cell));

**********

********** here is the error check

********** maybe

if(strCell->type != CELL_STRING) {

        if(flag_ignore_not_string_error)

             return(nil)  /* or however its done */

         else

        return(errorProcExt(ERR_STRING_EXPECTED, cell))};

**********

string = (char *)strCell->contents;



params = cell->next;

if(params != nilCell)

    params = getInteger(params, (UINT *)&options);

else

    options = 0;



/* Compile the regular expression in the first argument */

re = pcreCachedCompile(pattern, options);



/* Compilation succeeded: match the subject in the second argument */

rc = pcre_exec(

    re,                   /* the compiled pattern */

    NULL,                 /* no extra data - we didn't study the pattern */

    string,               /* the subject string */

    (int)strlen(string),  /* the length of the subject */

    0,                    /* start at offset 0 in the subject */

    0,                    /* default options */

    ovector,              /* output vector for substring information */

    OVECCOUNT);           /* number of elements in the output vector */



/* Matching failed */

if (rc == -1)

    return(nilCell);



/* error in pcre_exec() */

if (rc < 0)

    regexError("error in pcre_exec()", rc, "");



/* Match succeded */



/* Show substrings stored in the output vector */

result = cell = getCell(CELL_EXPRESSION);

for(idx = 0; idx < rc; idx++)

    {

    len = ovector[2*idx+1] - ovector[2*idx];

    strCell = stuffStringN(string + ovector[2*idx], len);



    deleteList((CELL*)sysSymbol[idx]->contents);

    sysSymbol[idx]->contents = (UINT)copyCell(strCell);



    if(idx == 0)

        {

        cell->contents = (UINT)strCell;

        cell = (CELL *)cell->contents;

        }

    else   

        {

        cell->next = strCell;

        cell = cell->next;

        }



    cell->next = stuffInteger(ovector[2*idx]);

    cell = cell->next;

    cell->next = stuffInteger(len);

    cell = cell->next;

    }



return(result);

********************* nil returned previously if ignore string error is on

}



but these sort of 'override error' switches can, as Lutz points out, end in tears.



Nigel

Lutz

#6
Again, 'nil' in parameters to functions is an error condition most of the time and should be flagged as such. If 'nil' could occurr as a normal condition calling that functtion, then yes: special code has to be written to treat this case.



But having this implicit, I find dangerous. In the team I am working with at a client's site (9 engineers at Kozoru) 'nil' arguments ar one of the major source of errors and we are all happy they are trigger an error exception as in a project where every programmer works on different newLISP modules, which all have to communicate with each other and calling conventions are frequently violated. Masking those I find dangerous.



Lutz





Lutz

Dmi

#7
Heh...

And after all my epistolar exercices, I found sufficient solution:
(parse (or (lookup "key" some-list 1) "") "delimiter")
It returns empty list instead of nil, that acceptable too.

Quite clear, quite reasonable.

So simple!



Nigel, thanks for discussion! Pointing me to the sources somehow switch my brain ;-)



Lutz, thanks for reasoning. Despite my enormous activity I still learning to program lispish :-)
WBR, Dmi

Lutz

#8
Agreed, this look likes a great solution



Lutz