Proving the existence of curry

Prove ∀A. ∀B. ∀C. ((A∧B)→C)→A→B→C with a truth table.

WTF does that mean? It means that for any three propositions (A, B, C), then the conjunction (logical AND) of A and B implies C implies A implies B implies C. Note that → is right-associative, so X→Y→Z is the same as X→(Y→Z). If you are unfamiliar with denotational semantics (these crazy symbols), then perhaps this more relaxed notation makes sense forall A B C. ((A, B) -> C) -> A -> B -> C. Remember this notation, because we will use it later.

Here are some truth tables for conjunction and implication to get started:

Conjunction
P  Q  P∧Q
0  0  0
0  1  0
1  0  0
1  1  1

Implication
P  Q  P→Q
0  0  1
0  1  1
1  0  0
1  1  1

We can prove the truth of the above statement by observing a tautology (all true values) in a truth table.

1  2  3  4    5        6    7      8

A  B  C  A∧B  (A∧B)→C  B→C  A→B→C  ((A∧B)→C)→A→B→C
0  0  0  0    1        1    1      1
0  0  1  0    1        1    1      1
0  1  0  0    1        0    1      1
0  1  1  0    1        1    1      1
1  0  0  0    1        1    1      1
1  0  1  0    1        1    1      1
1  1  0  1    0        0    0      1
1  1  1  1    1        1    1      1

Observe that column 8 is true. Therefore ∀A. ∀B. ∀C. ((A∧B)→C)→A→B→C is a true statement.

Remember this statement earlier forall A B C. ((A, B) -> C) -> A -> B -> C? If you have GHCi installed, go to the prompt and type :set -fglasgow-exts. Then ask for the type of the curry function :type curry. You will see this:

Prelude> :type curry
curry :: forall a b c. ((a, b) -> c) -> a -> b -> c

Same same! :)

OK, so Haskell is for crazy people who use (oh dear!) logic which has nothing to do with that real world and they should all be writing enterprise Java applications like real programmers, right? Yeah right. Yep uh huh.

So let’s write this in Java, just for kicks and just to be noisy. Start with the conjunction and implication primitives:

interface Implication<P, Q> {
  Q implies(P p);
}
 
interface Conjunction<P, Q> {
  P p();
  Q q();
}

Next write the method for representing the aforementioned statement:

class S {
  static <A, B, C> Implication<Implication<Conjunction<A, B>, C>, Implication<A, Implication<B, C>>> s() {
    return null; // todo
  }
}

I shall leave it as a reader exercise to implement the s method. I assure you of only one possible implementation if you do not use null, exceptions or side-effects.

Function currying, like all programming concepts, is a logical statement under the Curry-Howard Isomorphism. Have a nice day :)

8 Responses to “Proving the existence of curry”

  1. Pseudonym Says:

    Be careful using truth tables. A theorem which is true in Boolean logic may not be true in a topos, and in intuitionistic logic in particular.

  2. DavidLG Says:

    The statement can be quickly proved without a truth table.

    a->b is logically the same as (!a + b), meaning (not a) or b. So the statement in question is !(!(ab) + c) + (!a + (!b + c)), which simplifies with one application of de Morgan’s law to (ab + !c + !a + !b + c), which is obviously true.

    What does the Java snippet show? Do the Implication and Conjunction interfaces have any semantics, or are they interchangeable? How many implementations of static X foo() are there, and why is your method more interesting? I guess I must be missing your point, I’m just a Scala programmer. :)

  3. DavidLG Says:

    (That was supposed to be static <X> X foo().)

  4. Tony Morris Says:

    David,
    There are many methods of proof, but the point is really the relationship to C-H. I am often faced by people who are baffled by the implication symbol in type signatures and I think it is demystified a little by pointing out its relationship to a simple boolean operator.

  5. Pseudonym Says:

    DavidLG: Even without a truth table, in intuitionistic logic, !!a does not necessarily imply a (though a does imply !!a). This is a direct consequence of not including the law of the excluded middle as an axiom.

  6. Apocalisp Says:

    Pseudonym: That’s not not utter nonsense.

  7. Bobby Moretti Says:

    @Apocalisp: double negation doesn’t always work so well. You picked a rather specific example. You can think of other ones that are a lot more gray. For example, suppose P stands for “Jim Bob is a likable fellow”.

    Then consider !!P: ‘it’s not the case that Jim Bob is not a likable fellow’. If we allow this form of double negation rule, then this would imply P, or ‘Jim Bob is a likable fellow.’ But can we really infer that? It might be the case that everyone is ambivalent towards Jim Bob: he’s kinda likable, but also kinda unlikable.

    The problem with the !!P |– P form of double negation is that assumes that P is either true or false; there’s no middle ground. That’s what’s known as the law of excluded middle. So there are systems of logic (such as the one that Pseudonym is referring to) that deal with more truth values than just purely true and purely false.

  8. Daniel Jimenez Says:

    So I took a crack at implementing S.s (see below).

    I think I understand it: return an implication that, given an implication from a conjunction, returns an implication to an implication.

    My implementation just solves the type signatures, which wasn’t very hard. I don’t like that the two definitions of A (one is an argument to implies, one is an argument to and) aren’t referring to each other, though. I tried to write S.s1 and S.s2 to isolate it but couldn’t quite tie them together.

    Another place where I had trouble understanding it was in the call to what S.s returns. As can be seen from my Call.call, I’m hardcoding the return values of the conjunction and the initial implication. But I want to say that the implication from a conjunction only makes sense if you have both parts of the conjunction, and returning a hardcoded b for any a doesn’t seem right in that way.

    Worse, I end up passing b twice, once hardcoded as the return value from the conjunction, and once when calling the original method.

    Where did I go wrong? I’m pretty sure it’s my understanding of Conjunction that’s incorrect here (I remember and understand first-order logic just fine, I mean how those definitions apply to this Java code).

    // curry.java
    interface Implication<P, Q> {
     Q implies(P p);
    }

    interface Conjunction<P, Q> {
     Q and(P p);
    }

    class S {
     /**
      * return an implication that, given an implication from a conjunction, returns an implication to an implication
      * given: (A^B)->C, return: A->(B->C), overall: [(A^B)->C] -> [A->(B->C)]
      */
     static <A, B, C> Implication<Implication<Conjunction<A, B>, C>, Implication<A, Implication<B, C>>> s() {
      return new Implication<Implication<Conjunction<A, B>, C>, Implication<A, Implication<B, C>>>()
      {
       public Implication<A, Implication<B, C>> implies(final Implication<Conjunction<A, B>, C> i) {
        return new Implication<A, Implication<B, C>>()
        {
         public Implication<B, C> implies(final A a) {
          return new Implication<B, C>()
          {
           public C implies(final B b) {
            return i.implies(new Conjunction<A, B>()
            {
             public B and(A a) {
              return b;
             }
            });
           }
          };
         }
        };
       }
      };
     }
     static <A, B, C> Implication<A, Implication<B, C>> s1(final Implication<Conjunction<A, B>, C> i) {
      return new Implication<A, Implication<B, C>>()
      {
       public Implication<B, C> implies(final A a) {
        return new Implication<B, C>()
        {
         public C implies(final B b) {
          return i.implies(new Conjunction<A, B>()
          {
           public B and(A a) {
            return b;
           }
          });
         }
        };
       }
      };
     }
     static <A, B, C> Implication<B, C> s2(final A a) {
      return new Implication<B, C>()
      {
       public C implies(final B b) {
         throw new Error(); // surely not correct, but I’m stuck here
         // s2(a).implies(conj.and(a))
       }
      };
     }
    }

    class Call {
     static int call(final boolean a, final String b, final int c) {
      Conjunction<Boolean, String> and = new Conjunction<Boolean, String>() {
       public String and(Boolean p) {
        return b;
       }
      };
      Implication<Conjunction<Boolean, String>, Integer> from = new Implication<Conjunction<Boolean, String>, Integer>() {
       public Integer implies(Conjunction<Boolean, String> conjunction) {
        return c;
       }
      };
      Implication<Implication<Conjunction<Boolean, String>, Integer>, Implication<Boolean, Implication<String, Integer>>> s = S.s();
      Implication<Boolean, Implication<String, Integer>> to = s.implies(from);
      return to.implies(a).implies(b);
     }
    }

    public class curry {
      public static void main( String[] args ) {
        int result = Call.call(true, "two", 0);
        System.out.println("call(true, \"two\", 0)=" + result);
      }
    }

Leave a Reply