[racket] arity of + versus <=

From: Neil Toronto (neil.toronto at gmail.com)
Date: Sat Oct 29 13:27:08 EDT 2011

On 10/28/2011 12:00 PM, Stephen Bloch wrote:
>
> On Oct 28, 2011, at 1:57 PM, Jay McCarthy wrote:
>
>> I would expect
>>
>> (not (and (apply<  x) (apply>  x)))
>>
>> to be true for all x
>
> That's not at all obvious to me.  That's like saying you expect
> 	(not (and (all-even? x) (all-odd? x)))
> to be true for all sets x.  It breaks down in the empty case.

Exactly.

The only thing we haven't done in this discussion (and I haven't seen it 
in the discussion Eli linked to, either) is define the various proposals 
in symbolic logic. We keep appealing to it already, but mostly informally.

Proposal 1. No restrictions. Because '<' checks monotonicity for two or 
more arguments, it's already equivalent to the more general

     (< xs ...)  <->
       forall x_i in xs, forall x_j in xs, j > i  ->  (less? x_i x_j)

where 'less?' is the primitive less-than relation. This is vacuously 
true in the empty case and trivially true in the singleton case. It's 
mathematically elegant. It's easy to reason about because there's only 
one rule, which has no alternating quantifiers and no special cases for 
any length. (It's well-defined for empty, singleton, and all finite 
lists - and even countably and *uncountably* infinite lists! Woo!)

Stephen's recursive function gives an equivalent inductive definition:

     (<)  <->  #t
     (< x)  <->  #t
     (< x_1 x_2 xs ...)  <->  (less? x_1 x_2) and (< x_2 xs ...)

Given the third rule, the second has to be "(< x) <-> #t". (Otherwise, 
(< 1 2) could be false.) The first rule seems arbitrary because "(<)" 
isn't really a base case. It could arguably be "(<) <-> #f".

Proposal 2. Different zero-argument case:

     (<)  <->  #f
     (< x)  <->  #t
     (< x_1 x_2 xs ...)  <->  (less? x_1 x_2) and (< x_2 xs ...)

Putting it a different way makes it obvious that "(<) <-> #f" is an ugly 
special case in a pretty inductive disguise:

     (< xs ...)  <->
       not (empty? xs) and
       forall x_i in xs, forall x_j in xs, j > i  ->  (less? x_i x_j)

I don't think anybody is arguing for this. I'm heading off arguments 
against Proposal 1, which use Proposal 2 to try to show that allowing 
zero arguments is inherently ambiguous. It's not.

Proposal 3. Manage the apparent ambiguity by not allowing it:

     (< x)  <->  #t
     (< x_1 x_2 xs ...)  <->  (less? x_1 x_2) and (< x_2 xs ...)

Because this would be motivated by seriously considering Proposal 2, I 
see it as invalid. IOW, the decision should be between Proposal 1 and 
Proposal 4 only.

Proposal 4. The current behavior:

     (< x_1 x_2)  <->  (less? x_1 x_2)
     (< x_1 x_2 x_3 xs ...)  <->  (< x_1 x_2) and (< x_2 x_3 xs ...)

Yes, that's right: allowing one or more arguments makes no sense. It 
should be either zero or more, or two or more. But two or more only 
because that's the status quo. Check out the quantifier version:

     (< xs ...)  <->
       not (empty? xs) and
       not (empty? (rest? xs)) and
       forall x_i in xs, forall x_j in xs, j > i  ->  (less? x_i x_j)

There are worse ones with existential quantifiers, or left-hand sides 
like (< x_1 x_2 xs ...) that require consing on the right-hand side.

So I would like to see '<' (and friends) defined using the elegant 
quantifier rule in Proposal 1, but (of course) implemented recursively.

Neil T


Posted on the users mailing list.