[email protected] wrote:
> Thank you for the writeup; interesting. Just one point of motivation
> that perhaps I missed from the original post:
> 
> On Mon, Dec 7, 2009 at 11:17 PM, David-Sarah Hopwood
> <[email protected]> wrote:
>> To dodge this issue, let's provisionally call a function *instance*
>> "copacetic" [*] if:
>>  - that instance has only captured copacetic values, and
>>  - it has no side effects and is deterministic whenever it is
>>   only called with copacetic argument values, and
>>  - it uses no side-effecting or nondeterministic primitives.
> 
> I'm getting at something similar but distinct, call it "i-copacetic".
> :)  Specifically:
> 
>   - it has no side effects on its lexical environment regardless
>      of its argument values

You're right, that's a better definition.

The implementation sketch I gave for 'copacetic' actually ensures that
property already, even though the definition above doesn't. That's
because the verification that captured variables are const is done
statically, and the check that captured values are copacetic is done
for each instantiation of a function marked as /*...@functional*/, before
any call to that instance.

Note that this implies that the lexical environment of each successfully
created instance is observationally immutable, not just that it can't
be directly changed by that instance. I.e. it also can't be changed
indirectly via an operation on an argument (even if the checks for
arguments being copacetic are dropped as discussed below), or by other
code.

> The motivation is this: An object's state is managed by some
> surrounding system. However, it is allowed to expose "read()" services
> to the outside world that do not participate in this state management.
> Each "read()" service may side-effect the supplied arguments, but it
> must not side-effect the lexical environment of the service (i.e., the
> object itself).

The implementation sketch I gave would preclude that use case because
if the supplied argument values are not copacetic, the object's read()
method would throw. This might have been too conservative -- if those
argument checks were omitted then the property above (that the function
instance can't change its lexical environment) would still hold.

Similar cases occur for higher order functions. For example 'map' or
'fold' functions have no side effects except via their arguments.
With the checks that arguments and result are copacetic, you would need
both /*...@functional*/ and non-/*...@functional*/ variants of each such
function, with the same implementation. If the argument *and* result
checks were omitted (as David Wagner suggested), the same function
could serve both cases.

Another motivation, of course, is that not doing these checks is more
efficient.

It would be possible to make the argument and result checks optional
depending on the function annotation: say, /*...@pure*/ includes them but
/*...@functional*/ doesn't. (The environment checks would be the same,
and both /*...@pure*/ and /*...@functional*/ would mark instances as
copacetic.)

In that case, /*...@pure*/ would imply unconditional determinism
(referential transparency), whereas /*...@functional*/ would only imply
determinism for calls for which all arguments are copacetic.

What a fun discussion! Thanks for starting it.

-- 
David-Sarah Hopwood  ⚥  http://davidsarah.livejournal.com

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to