Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative to handle functions returning no value #831

Closed
wants to merge 1 commit into from

Conversation

frankluebeck
Copy link
Member

This is an experiment to allow users statements like
a := fu();
even if fu() does not return a value. In this case the statement
unbinds 'a' so that one can detect that fu() did not return a value
by 'IsBound(a);'.

The behaviour is similar for assignments to lists or record components
where statements like
l[i] := fu();
r.comp := fu();
in case of fu() not returning a value have the effect of
Unbind(l[i]);
Unbind(r.comp);

This is an experiment to allow users statements like
  a := fu();
even if fu() does not return a value. In this case the statement
unbinds 'a' so that one can detect that fu() did not return a value
by 'IsBound(a);'.

The behaviour is similar for assignments to lists or record components
where statements like
  l[i] := fu();
  r.comp := fu();
in case of fu() not returning a value have the effect of
  Unbind(l[i]);
  Unbind(r.comp);
@frankluebeck frankluebeck added the do not merge PRs which are not yet ready to be merged (e.g. submitted for discussion, or test results) label Jun 19, 2016
@frankluebeck
Copy link
Member Author

When I wanted to propose a simpler way to create a 'NoValue' object in GAP on GAP level (see "Add a generic 'no return' value to GAP" #829) and thought about a sensible behaviour of this special object, an alternative idea occurred to me.

In short: assigning results of functions which do not return a value behaves like unbinding. Here is an example session with the code from this pull request:

gap> fu:=function()end;
function(  ) ... end
gap> a := fu();
gap> IsBound(a);
false
gap> r:=rec();
rec(  )
gap> r.a:=fu(); r;
rec(  )
gap> r.a := 1; r;
1
rec( a := 1 )
gap> r.a := fu(); r;
rec(  )
gap> l := List([1..40],function(i) if IsPrimeInt(i) then return i^2; fi; end);
[ , 4, 9,, 25,, 49,,,, 121,, 169,,,, 289,, 361,,,, 529,,,,,, 841,, 961,,,,,, 
  1369 ]
gap> Filtered([1..40], i-> IsBound(l[i]));
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 ]
gap> f := function()  local a, r, l;
> a := fu(); Print(IsBound(a),"\n");
> r:=rec(); r.a := fu(); Print(r,"\n");
> r.a := 1; Print(r,"\n"); r.a := fu(); Print(r,"\n");
> l := List([1..40],function(i) if IsPrimeInt(i) then return i^2; fi; end);
> Print(l,"\n"); Print(Filtered([1..40], i-> IsBound(l[i])),"\n");
> end;
function(  ) ... end
gap> f();
false
rec(
   )
rec(
  a := 1 )
rec(
   )
[ , 4, 9,, 25,, 49,,,, 121,, 169,,,, 289,, 361,,,, 529,,,,,, 841,, 961,,,,,, 
  1369 ]
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 ]
gap> 

This should be completely backward compatible with correct current GAP code. It only allows one to use statements which ran into an error before (and now can be catched by the user, or will lead to an error in some other place).

Does this look reasonable.

(This is a quick hack for an experiment. It is likely, that more changes are needed and that some of the current changes can be achieved a bit easier.)

@fingolfin
Copy link
Member

I really like this idea, it mirrors what assign "None" to an array or hash entry does in Python, without forcing us to introduce a new singleton variable.

Still feel I should think a bit more about the consequence, though :-).

default:
ASS_REC( record, rnam, rhs );
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: This looses the optimization (?) for T_COMOBJ. But since AssPRec was modified to deal with rhs==0 anyway, the if (rhs == 0) check could be moved right next to ASS_REC.

Or, going one further, one could modify ASS_REC (andd ASS_LIST, ASSB_LIST, ...) to perform that check, thus making many other changes in this PR redundant. Alas, this might incur a speed penalty. Not sure how bad that would be in practice, though?

@fingolfin
Copy link
Member

One (minor?) concern is the loss of "transitivity of assignment". I.e. x:=func(); y:=x; may fail on the second assignment. While this example is harmless, people might still be confused when this works:

f := function() return Print("x"); end

but this does not:

g := function()
  local tmp;
  tmp := Print("x");  # tmp gets unbound resp. stays unbound
  return tmp;   # errors, since tmp is unbound
end;

Based on this, a logical extrapolition would be to allow assigning or returning unbound variables (with the effecting of unbinding the target resp. returning nothing) -- but don't get me wrong, I am not claiming this is a good idea, to the contrary, I am sceptical...

That said, this is right now just a hypothetical concern. But I'd like to think this through very carefully before making this change...

@ChrisJefferson
Copy link
Contributor

ChrisJefferson commented Jul 20, 2016

Having thought about this, I think it's "too clever", I don't think it will get much use, but I think it will confuse people when functions don't return values.

I'd prefer to go back to CallListFuncWrap (a function which explicitly special cases no return value), as it makes makes people consider when they want to handle functions which may, or may not, return a value.

Remark by @alex-konovalov: CallListFuncWrap suggested in PR #824

@fingolfin
Copy link
Member

Is this really "too clever"? Python essentialy does the same, and nobody seems to consider it confusing -- but then, of course, they have an expicit None value, and Python does distinguish between a variable which is not bound, and one which is bound to the value None... Hrm.

Of course this feature would be very, very rarely used to begin with. And of course one would have to be careful about documenting it clearly...

And this method is certainly more convenient than having to use CallListFuncWrap. OTOH, uses cases for both features should be very, very rare, so maybe it's a good thing it's slightly more verbose, and stands out explicitly (you can grep for CallListFuncWrap; it's in general impossible to grep for code that explicitly is prepared to deal with a function call not returning anything and thus unbinding the result variable...

Now, my only concerns with CallListFuncWrap were/are that it feels somewhat ad-hoc, and also that once added, we would have a hard time removing it again if we discovered it was a bad idea. It seems to me that this argument applies equally to this PR, and a change of language semantics is a deeper change than adding a function.

So I guess for now the conservative choice is to add CallListFuncWrap, and not this PR...

@ChrisJefferson
Copy link
Contributor

My issue with this is how many people would do this on purpose, and how many people will assign the return value of a function that doesn't return a value accidentally, and then get confused? I suspect the second would be more common.

@frankluebeck
Copy link
Member Author

I still think this is an interesting idea. But I have no objections if this experimental pull request is rejected and ChrisJefferson's variant of CallFuncList is implemented. (In python and Maple it is more important to have a None or NULL object because these systems have sequences, that is lists without surrounding []'s or ()'s, as data types and an object is needed that represents the empty sequence.)

@ChrisJefferson
Copy link
Contributor

While I like this idea, I think we are in agreement it's too big a change -- certainly without more support. Just because it is closed doesn't mean someone can't bring it back if they want to champion it and/or improve it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do not merge PRs which are not yet ready to be merged (e.g. submitted for discussion, or test results)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants