[fpc-pascal] Optional param modifier
Martin Frb
lazarus at mfriebe.de
Sun Apr 14 23:48:44 CEST 2019
On 14/04/2019 22:08, Ryan Joseph wrote:
>
> Per my example, currently we know that GetThing() COULD return nil so unless we know what the function does by reading the documentation or looking at the implementation we need to test for nil. Right? I suggest the modifier simply to enforce that check and let it be known that’s the desired usage.
I do get what you want. But not why (the personal why I get, the
objective one not).
Or not what sense is it makes... Where again I get your description, but
it seems arbitrary/randomly picked.
Sure perfectly fine on the few examples you gave. But then by that
measure others will bring their examples (and believe me they will, and
it will be examples neither of us would be able to derive today). And
then we need further rules/modifiers/dedicated-checks for whatever
examples they bring up...
As you wrote: "we need to test for nil"
Yes, and we need to do that for all of them. Even if they are not marked
as "expect-me-to-be-nil". Because even if they are not marked, they
still can be.
That means if we start warning in selected cases (the ones that are
marked), then we give the user **false security** over all other cases.
(I did not get a warning, so why does it crash on a nil-deref?)
The only place where we do not need to check for nil, is if we know it
can not be nil.
We can gain that knowledge from 1 of 2 places
- some sort of modifier to the type declaration - or some sort of
class-contract
- the docs
We can NOT get it from code review. Code may change, and then all the
depended code becomes wrong. (It may be that the code should return nil,
but due to a bug the current implementation does not)
> So, in other words:
>
> function FindThing: TThing; optional;
>
> means you MUST check for nil. If the value is nil that’s not an error. “optional” is probably a bad name for this I know.
Don't worry about the name.
How does the compiler know that I have checked. Code can be arbitrarily
complex. There will always be code that does check, but the compiler can
not tell (heck I may have a checkAssigned procedure in assembler).
So at best the compiler can warn during compilation, but never give an
error.
> function MakeThing: TThing;
>
> this means maybe check for nil or maybe not check for nil. We don’t know what the programmer intended because no extra information was given (at least in the declaration). Because of the name we probably assume this is going to return a new object which is safe so checking for nil would be a waste.
What if it run out of memory? And catches the exception.
Checking for nil is never a waste. Unless you have proof (in the
mathematical sense) that nil can not happen.
Or if your rules allow you to trust the docs, and the docs say you can
relay on it.
> But we don’t know that except for what the name implies or if we read the code.
Neither name, nor implementation are a reliable source for that info.
See above.
> Is that more clear? If there’s a better way to express this then please let me know.
Its been perfectly clear from (almost) the start.
I admit the contract/assert stuff may have made it seem different. But
that is because this approach does not hold up (IMHO). And therefore the
closest alternative was presented.
In the very end it is to avoid crashing on nil de-ref. And the
contract/assert can help there. Though in a completely different way.
> For parameters I like what Sven suggested to have a “requires” section
> in functions to check this but wouldn’t it make sense if the function
> parameter gave some hint also? “can be nil” or “can’t be nil” would
> both be helpful in terms of documentation and compile time errors
> instead of runtime errors.
The pre-condition (requires) would have the same amount of compile time
checks (and the remainder runtime) as a keyword like notnil or maybenil.
In the same way range checks and others go part compile, part runtime.
As for "documentation". I disagree with the way it is done in oxygen.
But I am not sure I have any good alternative.
For me a class contract (require/ensure) is part of the interface.
So it would have to be like (and similar for plain procedures, no class)
// can be all on one line....
type
TFoo= class
function DoTheFooThing(Foo1, Foo2: TFoo): Tbar;
requires
assigned(Foo1) or assigned(Foo2): 'Need at least 1 foo, for
the connection';
guarantees // better than ensure
assigned(result);
result.KnowsMyFoo = true: 'Connection exists'; // the =true
is redundant
procedure DoTheBar;
end;
So reading this declaration, you immediately know what is valid. And if
custom messages are give, they may tell you way
It is to be noted, that requires and guarantees contain conditions
(expressions), not pascal code. So technically it is ok, that they are
in the interface and not implementation.
If I want something in the implementation, I can use normal "assert"
(which by the way, have often great documenting value)
More information about the fpc-pascal
mailing list