1. I am currently taking a course succinctly named "Computation and Deduction". In reality, this is the secret name for "Immense fun at graduate PL courses". The whole idea of the course is to get acquainted with the theorem representation system Twelf. Based upon the LF-theory, the idea is that one can represent programming languages and their property proofs in a way that ensures correctness. Computer Science proofs tend to be shallow. Usually, one has to run induction over something and most of the proof is just simple slavery to an operational semantics and its inversion rules. Hence, the proof system can usually be kept simple. Twelf is different from traditional proof assistants and theorem provers like Isabelle/HOL and Coq. The latter 2 systems has proof automation support in which the system can automate big parts of the proof. It tends to make easy work on all the boring cases. As it stands, the stable version of Twelf support representation only: You must explicitly write down your proof. However, this turns out to be fairly simple. Further, there is nothing in Twelf which disallows one to add proof automation to the system later. While I do not claim to be a master of Twelf at all, it is not that hard to wrap your head around. Consider this to be an overview of what Twelf is able to do for you. Many parts need further explanation than I provide here, but that may spark the curious mind to dig deeper into the world of Theorem Proving! Also take note that I may have errors in here. Hopefully it will be the case that further proof development will eliminate many of these. Here, we will develop a lambda calculus interpreter, curry style, extended with peano arithmetic (Since I want to add a type system later). What makes the lambda calculus so interesting as a model for computation? Turing completeness, simplicity and basis for functional languages ought to be compelling reasons and enough justification! In Twelf, one begins by declaring exp : type. which reads "exp is a type". "type" here is really the kind (*) symbol in disguise. Then we declare how lambda calculus expressions look syntactically: z : exp. s : exp -> exp. app : exp -> exp -> exp. lam : (exp -> exp) -> exp. Thats it! The symbol 'z' plays the role of zero. It is an expression. The symbol 's' is the successor operator, so when it is used one types 's E' for some expression E which we want the successor of. Hence it will have type 'exp -> exp'. Lambda application is 'app E1 E2'; our encoding of the classical '(E1 E2)' application. So when fed with 2 expressions, E1 and E2 , app will itself be an expression. Finally, the lambda abstraction. It is written as 'lam E' where E is of type 'exp -> exp'. One may wonder where the variables are hidden. The answer, curiously, is that we are using variables in Twelf to represent variables in our lambda calculus. A Lambda Calculus variable 'x' is just 'x' as a Twelf variable. This is clever since we don't have to define a set of variables and then use them in the language as one would have done in Coq. Further, it will become clear that we can get substitution for free by "borrowing" it from the Twelf host language. Again a case where we would had described it directly in Coq. We really do not need it right now, but it may be beneficial to declare what values are in our language. Values are computations which are finished and in due time, we don't want every possible stuck computation to be values. So let us be explicit and define values:
    value : exp -> type.
    
    value_z : value z.
    value_s : value (s S)
               <- value S. 
    value_lam : value (lam [x] E x).
    
    We are declaring that 'value' is of type 'exp -> type'. This means that value is a type family, indexed by expressions. We can then go on and define the members of the type family. The 'value_z' symbol defines that 'z' is a value. The next rule uses an inductive argument: '(s S)' is a value under the assumption that 'S' is a value. Since we are using a capital S, Twelf's type checker will do its magic and infer the type of S. In Twelf variables in uppercase is free in the LF-term and is then treated universially by the system. The rules 'value_z' and 'value_s' together defines a system making all natural numbers into values. We finally address that lambda expressions are values. The notation '[x] E x' is a Twelf-lambda abstraction. in Haskell one would have written something like '\x -> E x' in SML it would have been 'fn x => E x'. This leaves us with the last leg of the journey, namely the semantics of the lambda calculus. The rules are defined as:
    eval : exp -> exp -> type.
    %mode eval +E -V.
    
    eval_z : eval z z.
    eval_s : eval (s S) (s S')
         <- eval S S'.
    eval_app : eval (app E1 E2) V
         <- eval E1 (lam E1')
         <- eval E2 V'
         <- eval (E1' V') V.
    eval_lam : eval (lam [x] E x) (lam [x] E x).
    
    We define a relation on evaluations 'eval E V' where the pair (E, V) is in the relation if the expression E evaluates to V. The '%mode' line is mode information known from logic programming. We define that the first argument is an input and the second argument is an output. This restricts the way the relation is allowed to be used. Twelf will carry out a mode check, ensuring that our use of 'eval' is in accordance with this mode definition. While this is mostly used to ensure certain relations are total, we use it here as an extra check of sanity. If your rules doesn't even pass the mode check, something is definitely wrong. I had a typo originally and the mode check found that immediately since some capitalized free variables were not ground. The symbol 'z' evaluates to itself. In 'eval_s' we call inductively -- if we have '(s S)' and S evaluates to S', then we have (s S'). Lambda abstractions in the last line evaluate to themselves as well. This leaves us with the 'app' case. If E1 evaluates to a lambda abstraction '(lam E1')', E2 evaluates to V', and (E1' V') evaluates to V, then (app E1 E2) evaluates to V. NOTE: The cool thing here is that E1' is of type 'exp -> exp' so we can just apply V'. This is substitution for free! There is no need to define a separate substitution rule because we just borrow it from Twelf. At this point we can begin to play with our toy: add1 : exp = lam [x:exp] (s x). one = s z. two = s one. three = s two. These are straightforward definitions. One can then run queries prolog-style to find solutions to problems. In effect, we have a free interpreter for our language: %query 1 5 eval (app add1 one) V. Twelf outputs 'V = s (s z).' as expected. We have a lot of rough edges in the toy interpreter however, and we may even have bugs in it. But we will weed them out over the coming postings, when we begin to add proofs on top of the system. At the Twelf Website is a lot of tutorials written by the Masters. If this is interesting to you, it is recommended reading!
    0

    Add a comment

  2. This all began with a post on how Ruby optimized Ruby 1.9 by Antinio Cangiano. Don Stewart followed up the race with a pretty cool version at His Blog. Then Michael Speer took the liberty to add memoization to the Python version: Memoized Python. While all this is funny, we are now entering the game where one optimizes like mad until the competition is not fair. I will add my 5 cents with a Standard ML program:
    fun fib n =
      case n of
          0 => 0
        | 1 => 1
        | k => (fib (k-1)) + (fib (k - 2))
    
    val _ =
        let fun loop n =
         if n = 36
         then ()
         else
      (print (String.concat
       ["n=", Int.toString n, " => ", Int.toString (fib n),
        "\n"]);
       loop (n + 1))
        in
     loop 0
        end
    
    A couple of notes: No tail recursion means no loops. Standard ML does not use arbitrary precision integers in the above code, so putting in a large enough n will fail (Though by an Overflow exception, not by wrap-around). There are no memoization in the generated code at all, so we are doing all the heavy work directly. There are no threading as well. The fib function itself is reasonably sized though my counter-loop could be made more functional via calls to List.app and List.tabulate. This yields:
    (Python 2.5.1) python z.py  34.26s user 0.01s system 97% cpu 35.030 total
    (ruby 1.8.6) ruby z.rb  77.28s user 0.00s system 97% cpu 1:19.02 total
    (MLton SVN-HEAD) ./z  0.88s user 0.00s system 89% cpu 0.984 total
    
    The benchmarks were carried out on the following CPU:
    CPU: AMD Athlon(tm) 64 Processor 3000+ (2002.57-MHz K8-class CPU)
      Origin = "AuthenticAMD"  Id = 0xfc0  Stepping = 0  Features=0x78bfbff
    AMD Features=0xe0500800
    
    With the following operating system:
    ogre% uname -a
    FreeBSD ogre 7.0-BETA2 FreeBSD 7.0-BETA2 #5: 
      Mon Nov 19 22:29:30 CET 2007
      root@ogre:/usr/obj/usr/src/sys/OGRE  amd64
    
    While I can't run the Haskell version, my guess is that it is a bit faster than the MLton version due to runtime startup cost. MLton has a pretty grim startup cost to pay.
    3

    View comments

Blog Archive
About Me
About Me
What this is about
What this is about
I am jlouis. Pro Erlang programmer. I hack Agda, Coq, Twelf, Erlang, Haskell, and (Oca/S)ML. I sometimes write blog posts. I enjoy beer and whisky. I have a rather kinky mind. I also frag people in Quake.
Popular Posts
Popular Posts
  • On Curiosity and its software I cannot help but speculate on how the software on the Curiosity rover has been constructed. We know that m...
  • In this, I describe why Erlang is different from most other language runtimes. I also describe why it often forgoes throughput for lower la...
  • Haskell vs. Erlang Since I wrote a bittorrent client in both Erlang and Haskell, etorrent and combinatorrent respectively, I decided to put ...
  • A response to “Erlang - overhyped or underestimated” There is a blog post about Erlang which recently cropped up. It is well written and pu...
  • The reason this blog is not getting too many updates is due to me posting over on medium.com for the time. You can find me over there at thi...
  • On using Acme as a day-to-day text editor I've been using the Acme text editor from Plan9Port as my standard text editor for about 9 m...
  • On Erlang, State and Crashes There are two things which are ubiquitous in Erlang: A Process has an internal state. When the process crashes,...
  • When a dog owner wants to train his dog, the procedure is well-known and quite simple. The owner runs two loops: one of positive feedback an...
  • This post is all about parallel computation from a very high level view. I claim Erlang is not a parallel language in particular . It is not...
  • Erlangs message passing In the programming language Erlang[0], there are functionality to pass messages between processes. This feature is...
Loading
Dynamic Views theme. Powered by Blogger. Report Abuse.