Talk About Network



Register and Login
Nick
Password
Register create new account Sign up is FREE and you can post replies, new topics, bookmark posts and more!
Recover lost password


Programming > Functional > Phantom boolean...
Latest [ Topics | Posts ] Archive Post A New Topic Post a Reply
<< Topic < Post Post 1 of 1 Topic 2802 of 2841
Post > Topic >>

Phantom booleans for encoding arbitrary finite relations

by Vesa Karvonen <vesa.karvonen@[EMAIL PROTECTED] > Mar 25, 2008 at 05:36 PM

I'm not sure whether this technique has already been documented and/or
discovered earlier (wouldn't surprise me if it has), but at least I
haven't seen it, and the result seems theoretically interesting.
Basically, I will briefly present a technique that makes it relatively
straightforward to encode arbitrary finite relations at the type level
using just the very simple type system of Standard ML.

Tinkering the other day on some SML code, I wanted to encode a constraint
of the form

  T(false) x T(false) -> T(false)
   T(true) x T(false) -> T(true)
  T(false) x  T(true) -> T(true)
   T(true) x  T(true) -> type error

using phantom types.  (The type would be for a kind of join operation that
ensures that a particular property can only be specified once.)  I first
tried to design an ad hoc scheme for the constraint, but it proved
surprisingly elusive.  Then it occurred to me that basically the same
encoding technique as employed in the "static sum" technique

   http://mlton.org/StaticSum

should also work purely at the type level.  The basic trick used in the
static sum technique is that the type of a static sum specifies the type
of deconstructing the sum.  Adapting the static sum technique to work
purely at the type level, one can drop the type variables that specify the
type of the value carried by a sum.

Below is a bit of SML code that implements the idea.  You should be able
to evaluate it using any SML implementation.

  infix 6 andb
  infix 5 xorb
  infix 4 orb

  structure PhantomBool :> sig

     type ('false, 'true, 'result) t
     (* Type level boolean.  To inspect a type-level boolean one specifies
      * the 'false and 'true types and unifies with the boolean.  The
      * 'result is then either the 'false or the 'true type. *)

     (* Abbreviations for true and false. *)
     type ('f, 't) T = ('f, 't, 't) t  (* {true} *)
     type ('f, 't) F = ('f, 't, 'f) t  (* {false} *)

     (* The following values are just for playing with type level
      * booleans.  Ordinarily type level booleans would just appear as
      * phantom type constraints.  Type expressions involving type level
      * booleans can be quite complicated.  The following values allow one
      * to use the type checker to infer desired type constraints from a
      * program term. *)

     val t : ('f, 't) T
     val f : ('f, 't) F

     val split : (('f1, 't1) F * ('f2, 't2) F,
                  ('f3, 't3) T * ('f4, 't4) T,
                  ('f5, 't5, 'r5) t * ('f6, 't6, 'r6) t) t ->
                 ('f5, 't5, 'r5) t * ('f6, 't6, 'r6) t
     (* In the absence of first-class polymorphism, split allows one to
      * deconstruct a boolean multiple times with different types. *)

     val iff : ('f, 't, 'r) t -> 't -> 'f -> 'r
     (* Does not return normally. *)

     val notb : (('f1, 't1) T, ('f2, 't2) F, ('f, 't, 'r) t) t ->
                ('f, 't, 'r) t
     val andb : (('f1, 't1) F * ('f2, 't2) F, ('f3, 't3) F * ('f4, 't4) T,
                 'a * 'b) t * ('a, 'b, ('f, 't, 'r) t) t
                -> ('f, 't, 'r) t
     val orb : (('f1, 't1) F * ('f2, 't2) T, ('f3, 't3) T * ('f4, 't4) T,
                'a * 'b) t * ('a, 'b, ('f, 't, 'r) t) t
               -> ('f, 't, 'r) t
  end = struct
     type ('f, 't, 'r) t = unit
     type ('f, 't) T = unit
     type ('f, 't) F = unit
     val t = ()
     val f = ()
     fun split () = ((), ())
     fun iff _ = raise Fail "Phantom.Bool.iff"
     val notb = ignore
     val op andb = ignore
     val op orb = ignore
  end

  open PhantomBool

Now, below are a few small examples.  To actually see the types, which is
the whole point, you need to evaluate them in a SML REPL.

First a simple equality test for type level booleans:

  fun eq (a, b) = let
     val (a, a') = split a
  in
     iff b a (notb a')
  end

First evaluate it and then try each of the following:

  fn () => eq (f, f)
  fn () => eq (f, t)
  fn () => eq (t, f)
  fn () => eq (t, t)

Here is a definition of exclusive or:

  fun a xorb b = let
     val (a, a') = split a
  in
     iff b (iff a f t) (iff a' t f)
  end

Evaluate it and then try each of the following:

  fn () => f xorb f
  fn () => f xorb t
  fn () => t xorb f
  fn () => t xorb t

Here is a definition of 2-bit modular arithmetic addition:

  fun add2 ((l0, l1), (r0, r1)) = let
     val (l0, l0') = split l0
     val (r0, r0') = split r0
  in
     (l0 xorb r0, l0' andb r0' xorb l1 xorb r1)
  end

If you evaluate it, you can see that it has a fairly complicated type
(which likely isn't the simplest type possible to express the relation).
Nevertheless, it works as it should:

  fn () => add2 ((t, f), (t, f))  (* 1 + 1 = 2 = (f, t) *)
  fn () => add2 ((t, t), (t, f))  (* 3 + 1 = 0 = (f, f) *)
  (* ... *)

As an exercise, you might wish to try to come up with a type that
expresses 2-bit multiplication.  (Hint: Implement a multiplication
function using t, f, iff, ... and read the type of the function.)

Now, if you think about it for a moment, this technique, type level
booleans, allows one to encode arbitrary finite relations at the type
level using just the simple type system of Standard ML.  Furthermore, it
is relatively straightforward to come up with a particular relation.  The
obvious downside of the technique is that the types grow rather rapidly.

Finally, here is an encoding of the constraint I mentioned at the
beginning:

  functor TryJoin
     (type error
      val join : (('f1, 't1) F * ('f2, 't2) T, ('f3, 't3) T * error,
                  'a * 'b) t * ('a, 'b, ('f, 't, 'r) t) t
                 -> ('f, 't, 'r) t) = struct
     val a = fn () => join (f, f)
     val b = fn () => join (f, t)
     val c = fn () => join (t, f)
  (* val d = fn () => join (t, t) (* uncomment for a type error *) *)
  end

If you compare the type of join to the type of PhantomBool.orb, you can
see that it is the same except for the "error" at a position that
corresponds to the case where both arguments to join are true.

-Vesa Karvonen




 1 Posts in Topic:
Phantom booleans for encoding arbitrary finite relations
Vesa Karvonen <vesa.ka  2008-03-25 17:36:41 

Post A Reply:
  Go here to Signup

AddThis Feed Button


About - Advertising - Contact - Frequently Asked Questions - Privacy Policy - Terms of Use - Signup

Contact
tan12V112 Thu May 15 0:53:51 CDT 2008.