[m-dev.] Determinism assertions, and functions whose forward mode fails

Peter Moulder pmoulder at mail.csse.monash.edu.au
Sat Aug 23 00:13:37 AEST 2003


On Mon, Aug 11, 2003 at 02:17:28AM +1000, Fergus Henderson wrote:

> There are certainly important practical advantages to [throwing an
> exception in functions that are undefined for the given arguments].
> But the declarative semantics with this approach are surprising.
> For example, in Mercury the goal 
> 
> 	some [X `with_type` integer, Y `with_type` integer]
> 		((X * Y) / Y \= X)
> 
> is true.  The same is the case for the analagous goal in Goedel.
> This is because the although the operational semantics says that
> 0 / 0 will throw an exception, the declarative semantics says that
> 0 / 0 has some unspecified value.

That seems to be a problem with the use of error/exceptions with any det
procedure, not just functions.  However, I may be misunderstanding
Fergus' reasons for saying that "the declarative semantics says that
0 / 0 has some unspecified value".


I believe there's a problem with the following sentence of the reference
manual (node Determinism categories):

# An annotation of `det' or `multi' is an assertion that for every value
# each of the inputs, there exists at least one value of the outputs for
# which the predicate is true, or (in the case of functions) for which
# the function term is equal to the result term.

Possibly the sentence should be changed to have some exclusion on inputs
that don't terminate, like paragraph 2 of the node.  However, I don't
know whether or not one could usefully make such a modification, i.e.
whether the sentence would still say anything if one did add such an
exclusion.

Taking this sentence as it is and by itself, I would be fairly confident
that the declaration

  :- mode p(in, in, ..., out, out, ...) is {det or multi}.

implies:

  all[In1] all[In2] ... some[Out1] some[Out2] ...
    p(In1, In2, ..., Out1, Out2, ...).

Picking a random det/multi predicate from the standard library that
includes a call to error:

  :- pred string__index_det(string, int, char).
  :- mode string__index_det(in, in, uo) is det.
  string__index_det(String, Int, Char) :-
	( string__index(String, Int, Char0) ->
		Char = Char0
	;
		error("string__index_det: index out of range")
	).

The corresponding determinism assertion from above:

  all[String, Int] some[Char] string__index_det(String, Int, Char).

Considering the case String = "", Int = 5 (i.e. values where the
string__index call fails), the definition of string__index_det gives

  all[Char] string__index_det("", 5, Char) <=> (
    ( false -> ... ; error("...") )

which simplifies to

  all[Char] string__index_det("", 5, Char) <=> error("...")

while the determinism assertion becomes

  some[Char] string__index_det("", 5, Char)

Substituting such a value of Char into the previous, we get

  true <=> error("...")

which I believe means that compilers are allowed to replace the error
call with `true' even for values of String,Int like "",5 such that the
string__index call fails.

This makes the error/1 call useless other than in strict sequential
semantics and other low optimization settings.

Similarly for most other uses of error/1 in det/multi procedures.

require.require/2 is an interesting case.  If we accept the predicate
argument as an input, then given:

  :- pred require(pred, string).
  :- mode require((pred) is semidet, in) is det.

it would seem that the declarative semantics of

  require(P, "...")

is simply

  true

even if P is false.

Currently, the definition of require/2 is:

  require(Goal, Message) :-
        ( call(Goal) ->
                true
        ;
                error(Message),
                fail
        ).

Declaratively (eliding type fluff):

  all[Goal, Message] (
     require(Goal, Message) <=>
     (
	call(Goal)
     ;
        not(call(Goal)),
        error(Message),
        false
     )
  ).

Consider the case where Goal = false.  Then we have:

  all[Message] (
    require(false, Message) <=>
    (
      error(Message),
      false
    )
  )

However, applying the paragraph from the reference manual, we also have

  all[Goal, Message] require(Goal, Message)

because we've declared require(in,in) is det.  Substituting into the
above, we get

  all[Message] (
    true <=> (..., false)
  )

which simplifies to simply

  false

so it seems that the standard library is inconsistent.  [Ah, no wonder my
programs don't behave!]

If we were to change the definition of require/2 to remove the `fail'
conjunct, we'd get a different problem when considering Goal = false:

  all[Message] (
    true <=> error(Message)
  )

i.e.

  all[Message] error(Message)

which I believe means that compilers would be allowed to replace all
calls to error/1 with simply `true'.

pjm.

--------------------------------------------------------------------------
mercury-developers mailing list
Post messages to:       mercury-developers at cs.mu.oz.au
Administrative Queries: owner-mercury-developers at cs.mu.oz.au
Subscriptions:          mercury-developers-request at cs.mu.oz.au
--------------------------------------------------------------------------



More information about the developers mailing list