chapter 8 the language design space
play

Chapter 8: The Language Design Space Aarne Ranta Slides for the - PowerPoint PPT Presentation

Chapter 8: The Language Design Space Aarne Ranta Slides for the book Implementing Programming Languages. An Introduction to Compilers and Interpreters, College Publications, 2012. How simple can a language be? Two minimal Turing complete


  1. Chapter 8: The Language Design Space Aarne Ranta Slides for the book ”Implementing Programming Languages. An Introduction to Compilers and Interpreters”, College Publications, 2012.

  2. How simple can a language be? Two minimal Turing complete languages: Lambda calculus, Brainfuck. Criteria for a good programming language Domain-specific languages Approaching natural language Concepts and tools for Assignment 6

  3. Models of computation In the 1930’s, before electronic computers were built, mathematicians developed models of computation : • Turing Machine (Alan Turing), similar to imperative program- ming. • Lambda Calculus (Alonzo Church), similar to functional program- ming. • Recursive Functions (Stephen Kleene), also similar to functional programming. These models are equivalent: they cover exactly the same programs. Turing-complete = equivalent to these models They correspond to different styles, programming paradigms

  4. The halting problem Turing proved that a machine cannot solve all problems. In particular, the halting problem : to decides for any given program and input if the program terminates with that input. All general-purpose programming languages used today are Turing- complete. Hence its halting problem is undecidable.

  5. Pure lambda calculus as a programming language* A minimal Turing-complete language The minimal definition needs just three constructs: variables, applica- tions, and abstractions: Exp ::= Ident | Exp Exp | "\" Ident "->" Exp This language is called the =pure lambda calculus . Everything else can be defined: integers, booleans, etc.

  6. Church numerals Church numerals : integers in pure lambda calculus 0 = \f -> \x -> x 1 = \f -> \x -> f x 2 = \f -> \x -> f (f x) 3 = \f -> \x -> f (f (f x)) ... A number n is a higher-order function that applies any function f , to any argument x , n times. Addition: PLUS = \m -> \n -> \f -> \x -> n f (m f x) gives a function that applies f first m times and then n times.

  7. Examples of addition (Using operational semantics for more details!) PLUS 2 3 = (\m -> \n -> \f -> \x -> n f (m f x)) (\f -> \x -> f (f x)) (\f -> \x -> f (f (f x))) = \f -> \x -> (\f -> \x -> f (f (f x))) f ((\f -> \x -> f (f x)) f x) = \f -> \x -> (\f -> \x -> f (f (f x))) f (f (f x)) = \f -> \x -> f (f (f (f (f x)))) = 5 Multiplication : add n to 0 m times. MULT = \m -> \n -> m (PLUS n) 0

  8. Booleans and control structures Church booleans : TRUE = \x -> \y -> x FALSE = \x -> \y -> y TRUE chooses the first argument, FALSE the second. (Notice that FALSE = 0 ) Conditionals (the first argument is expected to be a Boolean): IFTHENELSE = \b -> \x -> \y -> b x y The boolean connectives (are they lazy?): AND = \a -> \b -> IFTHENELSE a b FALSE OR = \a -> \b -> IFTHENELSE a TRUE b

  9. Recursion To be fully expressive, we need recursion. We cannot just write (for e.g. the factorial n !), fact n = if x == 0 then 1 else n * fact (n - 1) because the pure lambda calculus has no definitions (the ones above were just shorthands, where the ”defined” constant does not appear). Solution: fix-point combinator , also known as the Y combinator : Y = \g -> (\x -> g (x x)) (\x -> g (x x)) This function has the property (exercise!) Y g = g (Y g) which means that Y iterates g infinitely many times.

  10. Following the idea fact = \n -> if x == 0 then 1 else n * fact (n - 1) we define FACT = Y (\f -> \n -> IFTHENELSE (ISZERO n) 1 (MULT n (f (PRED n)))) where we need ISZERO (equal to 0) and PRED (predecessor, i.e. n − 1) ISZERO = \n -> n (\x -> FALSE) TRUE PRED = \n -> \f -> \x -> n (\g -> \h -> h (g f)) (\u -> x) (\u -> u) (Exercise: verify that PRED 1 is 0).

  11. Another Turing-complete language* BF , Brainfuck , designed by Urban M¨ uller based on the theoretical language P” by Corrado B¨ ohm. Goal: to create a Turing-complete language with the smallest possible compiler. M¨ uller’s compiler was 240 bytes in size. BF has • an array of bytes, initially set to zeros (30,000 bytes in the original definition) • a byte pointer, initially pointing to the beginning of the array • eight commands, – moving the pointer – changing the value at the pointer – reading and writing a byte – jumps backward and forward in code

  12. The BF commands increment the pointer > decrement the pointer < increment the byte at the pointer + decrement the byte at the pointer - output the byte at the pointer . input a byte and store it in the byte at the pointer , jump forward past the matching ] if the byte at the pointer is 0 [ jump backward to the matching [ unless the byte at the pointer is 0 ] All other characters are treated as comments.

  13. Example BF programs char.bf , displaying the ASCII character set (from 0 to 255): .+[.+] hello.bf , printing ”Hello”: ++++++++++ Set counter 10 for iteration [>+++++++>++++++++++<<-] Set up 7 and 10 on array and iterate >++. Print ’H’ >+. Print ’e’ +++++++. Print ’l’ . Print ’l’ +++. Print ’o’

  14. A BF compiler Here defined via translation to C: > ++p; < --p; + ++*p; - --*p; . putchar(*p); , *p = getchar(); while (*p) { [ } ] The code is within a main () function, initialized with char a[30000]; char *p = a;

  15. Criteria for a good programming language Turing completeness might not be enough! Other reasonable criteria: • Orthogonality : small set of non-overlapping language constructs. • Succinctness : short expressions of ideas. • Efficiency : code that runs fast and in small space. • Clarity : programs that are easy to understand. • Safety : guard against fatal errors.

  16. Criteria not always compatible: there are trade-offs. Lambda calculus and BF satisfy orthogonality, but hardly the other criteria. Rich languages such as Haskell and C++ have low orthogonality but are good for most other criteria. In practice, different languages are good for different applications. Even BF can be good - for reasoning about computability! (There may also be languages that aren’t good for any applications. And even good languages can be implemented in bad ways, let alone used in bad ways.)

  17. Some trends Toward more structured programming (from GOTOs to while loops to recursion). Toward richer type systems (from bit strings to numeric types to structures to algebraic data types to dependent types). Toward more abstraction (from character arrays to strings, from ar- rays to vectors and lists, from unlimited access to abstract data types). Toward more generality (from cut-and-paste to macros to functions to polymorphic functions to first-class modules). Toward more streamlined syntax (from positions and line numbers, keywords used as identifiers, begin and end markers, limited-size iden- tifiers, etc, to a ”C-like” syntax that can be processed with standard tools and defined in pure BNF).

  18. Domain-specific languages As different languages are good for different purposes, why not turn the perspective and create the best language for each purpose? More or less equivalent names: • special-purpose languages • minilanguages • domain-specific languages • DSL ’s

  19. Examples • Lex for lexers, Yacc for parsers; • BNFC for compiler front ends; • XML for structuring documents; • make for specifying compilation commands; • bash (a Unix shell) for working on files and directories; • PostScript for printing documents; • JavaScript for dynamic web pages.

  20. Design questions for DSL’s • Imperative or declarative? • Interpreted or compiled? • Portable or platform-dependent? • Statically or dynamically checked? • Turing-complete or limited? • Language or library?

  21. Turing completeness PostScript and JavaScript are Turing-complete DSL’s The price to pay: • halting problem is undecidable • no complexity guarantees for programs E.g. BNFC is not Turing-complete: it can just defines LALR(1) grammars with linear parsing complexity (or, with a suitable back-end, context-free grammars with cubic complexity).

  22. Embedded languages* Embedded language = minilanguage that is a fragment of a larger host language Advantages: • It inherits the implementation of the host language. • No extra training is needed for those who already know the host language. • An unlimited access to ”language extensions” via using the host language.

  23. Disadvantages: • One cannot reason about the embedded language independently of the host language. • Unlimited access to host language can compromise safety, effi- ciency, etc. • May be difficult to interface with other languages than the host language. • Training programmers previously unfamiliar with the host language can have a large overhead.

  24. Example: parser combinators in Haskell An alternative to using a grammar formalisms: write recursive-descent parsers directly in Haskell Clearer and more succinct than raw coding without the combinators (Chapter 3) The basic operations of sequencing ( ... ), union ( ||| ), and literals ( lit ). The power to deal with arbitrary context-free grammars, and even beyond, because they allow recursive definitions of parsing functions. The next slide is a complete parser combinator library.

Recommend


More recommend