Programming Paradigms Concepts Programming paradigms Declarative/Constraint satisfaction programming (Prolog) Imperative programming (C) You define objects and their behaviors, and those objects interact to perform the computation Functional programming (Clojure, Haskell, OCaml) You give the computer step-by-step instructions on exactly what to do Object-Oriented programming (C++, C#, Java) You define the nature of the problem, and the computer figures out the solution Programs are mathematical objects Dataflow/Reactive programming (Excel, Elm) Functions are executed as and when their data become available 2 Paradigm-like aspects of programming “Think of a Paradigm Shift as a change from one way of thinking to another” (taketheleap.com) Recursive programming Object-oriented programming Recursion was not available in the earliest languages, but every modern language supports it Recursion is by far the best way of working with recursively defined data structures (which also were not present in the earliest languages) Still considered a “paradigm,” although almost all modern languages support it to some extent (Python, Ruby, Perl) Concurrent programming Various approaches, each of which is a paradigm in its own right: Shared state, Actors, Agents, Software Transactional Memory 3 Beginning to program Beginning programmers have to learn about loops, if statements, and input/output; and maybe some form of function or procedure Programming concepts are easy—it’s writing programs that’s hard! These concepts are easy Tell a beginner that a loop does the same thing over and over, and they have no trouble understanding that The same goes for if statements and functions So why do beginners have so much trouble writing programs? Understanding the concepts individually is no big deal Learning to program is all about making these concepts an automatic (invisible) part of your mental toolkit In this course I’ve introduced more programming concepts I’ve tried to introduce them in their “native habitat” Most of the concepts are easy, and (once understood) are just common sense Hopefully, some of these concepts become invisible to you! 4 What we did not cover The ecosystem surrounding a programming language is very important Is there a community of programmers for that language who can help when you have trouble? Is there a decent IDE for the language? Are there good debugging tools? Are there good build tools for larger programs? make, sbt, ant, leinengen, etc. Is there a good web application framework? Or at the very least, an editor that does syntax coloring? Ruby/Rails, Python/Django, Scala/Lift, Java/Scala/Play!2 Is the language stable enough to use for large projects? 5 The Blub paradox These languages are full of unnecessary weird stuff My language, Blub, has everything I ever want or need These languages are defective, because they don’t have feature x, which I use all the time 6 Moore’s Law Moore’s law states: 1. Transistor density on a chip doubles about every two years 2. Computer speed doubles correspondingly #1 is still true, but the limit will be reached shortly #2 ended in about 2003, when we reached 3 GHz Faster speeds result in too much heat to dissipate Consequences: We are moving from single- to multi-core computers Concurrent programming is becoming increasingly important All modern languages are getting functional 7 Declarative languages Prolog 8 A declarative language Most programming languages are imperative—you tell the computer what to do to solve a problem Prolog is declarative—you give it the data it needs, and it solves the problem for you Consider: append([], List, List). append([Head | Tail], List, [Head | More]) :append(Tail, List, More). This defines what it means to append lists You can use it to append two lists You can use it to find what to append to a list to get another list You can use it to find what list to append to, to get another list You can test whether the relation holds between three lists 9 A constraint satisfaction language Prolog is also one of a number of constraint satisfaction languages You tell it the constraints on a problem (e.g. the solution must be someone who is both female and rich), and it finds a solution that satisfies those constraints A “logic puzzle” is nothing more than a set of constraints (e.g. every man has a different tie), in logical notation 10 Prolog does backtracking You don’t have to implement backtracking in Prolog; the language does it for you The only other “language” I know of that does this is Regular Expressions, which are a “sublanguage” of most modern programming languages loves(chuck, X) call fail female(X) rich(X) exit redo 11 Prolog uses unification Unification is when two structures “become one,” often by attaching values to variables The basic rules of unification are: Any value can be unified with itself A variable can be unified with another variable A variable can be unified with any value Two different structures can be unified if their constituents can be unified Lists are a particularly important kind of structure A variable can be unified with a structure containing that same variable Unification is reflexive, symmetric, and transitive Prolog parameter transmission is by unification, not assignment 12 Resolution A clause is a disjunction (“or”) of zero or more literals, some or all of which may be negated sinks(X) dissolves(X, water) ¬denser(X, water) Any expression in the predicate calculus can be put into clause form X Y can be rewritten as X Y Conjunctions (“ands”) can be rewritten as separate clauses The existential quantifier can be replaced by a Skolem function If the existential quantifier is under control of a universal quantifier, replace it with a function of the universally quantified variable Otherwise, just replace with a constant (whose value need not be known) Universal quantifiers, , can be dropped Here is the resolution principle: X someLiterals X someOtherLiterals ---------------------------------------------conclude: someLiterals someOtherLiterals Clauses are closed under resolution From and 13 Prolog is a resolution theorem prover Prolog is an implementation of resolution theorem proving in a programming language A := B, C, D. in Prolog is equivalent to A B C D in logic. Because it is based on logic, there is very little that is ad hoc or arbitrary about Prolog Forward reasoning is: Given some premises, ask “What can we conclude?” Backward reasoning is: Given some premises and a conjectured conclusion, try to derive the conclusion from the premises To avoid a combinatorial explosion, Prolog uses backward reasoning The development of resolution theorem proving by J.A.Robinson made theorem proving by computer feasible for the first time, and led directly to the Prolog language 14 Functional Languages Clojure, Erlang, Haskell 15 Abstract syntax trees The syntax of a language defines its surface form The internal form is an AST (Abstract Syntax Tree) Programming language syntax is a compromise between A form that is easy for the programmer to work with A form that can be translated into an AST Lisp syntax is a direct representation of an AST Parenthesized expressions directly represent tree structures Therefore, Lisp syntax is the most general form possible Clojure is a Lisp variant with added syntax, but it is still homoiconic Basic functional programming Functions are values (“first-class objects”) Functions have no side effects May be passed as parameters to other functions May be returned as the result of a function call May be stored in variables or data structures Given the same parameters, a function returns the same result Referential transparency: The result of a function call may be substituted for the function call Data is immutable and persistent Immutable data makes concurrency much easier Persistent means that structure is shared Functional Programming (FP) In FP, Functions are first-class objects. That is, they are values, just like other objects are values, and can be treated as such Functions have no side effects Functions can be assigned to variables, Functions can be passed as parameters to higher-order functions, Functions can be returned as results of functions Functions can be manipulated to create new functions There are function literals Given the same parameters, a function returns the same result Referential transparency: The result of a function call may be substituted for the function call Data is immutable and persistent Immutable data makes concurrency much easier Persistent means that structure is shared One author suggests that the most important characteristic of a functional language is that it be single assignment, and everything else (?) follows from this 18 Higher-order functions A higher-order function is a function that takes a function as an argument, returns a function as its value, or both Higher-order functions add expressiveness to a programming language Higher-order functions can replace explicit recursions (or explicit loops) The use of higher-order functions in place of explicit loops (or explicit recursions) usually makes code simpler, clearer, and more concise Higher-order functions do not add power to a programming language Any Turing complete language can compute anything that can be computed It doesn’t take much for a language to be Turing complete All programming languages you know are equal in power Common higher-order functions Almost every functional languages has these features and functions: Anonymous (literal) functions, so a function doesn’t have to be defined independently, but can be placed directly as an argument to a higher-order function (map function sequence) applies the function to each element of the sequence, yielding a sequence of the results (filter predicate sequence) applies the predicate to each element of the sequence, yielding a sequence of the elements that satisfy the predicate (reduce initial-value binary-function sequence) applies the binaryfunction to each pair of elements of the sequence, starting with aninitialvalue , and yielding a single value List comprehensions combine and may simplify the above Clojure=> (for [x (take 10 (iterate inc 1))] (* x x)) (1 4 9 16 25 36 49 64 81 100) Closures When a function definition uses variables that are not parameters or local variables, those variables are “closed over” and retained by the function The function uses those variables, not the value the variables had when the function was created Clojure=> (defn rangechecker [min max] (fn [num] (and (>= num min) (<= num max))) ) #'user/rangechecker Notice that the function being returned gets the values of min and max from the environment, not as parameters Clojure=> (def in-range? (rangechecker 0 100)) #'user/in-range? Currying Currying is absorbing a parameter into a function to make a new function with one fewer parameters Haskell> let times x y = x * y Haskell> let times100 = times 100 Haskell> times100 7 700 Clojure=> (def times100 (partial * 100)) #'user/times100 Clojure=> (times100 7) 700 scala> def times(x: Int)(y: Int) = x * y times: (x: Int)(y: Int)Int scala> def times100 = times(100) _ times100: Int => Int scala> times100(7) res0: Int = 700 Function composition Function composition is combining two or more functions into a new function Clojure=> (def third (comp first rest rest)) #'user/third Clojure=> (third '(one two three four five)) three Haskell Data.List> let third = head . tail . tail Haskell Data.List> third "ABCDE" 'C' scala> def add1(x: Int) = x + 1 add1: (x: Int)Int scala> def times10(x: Int) = 10 * x times10: (x: Int)Int scala> val addThenTimes = add1 _ andThen times10 _ addThenTimes: Int => Int = <function1> scala> addThenTimes(5) res3: Int = 60 Persistence and laziness A persistent data structure is one that is itself immutable, but can be modified to create a “new” data structure The original and the new data structure share structure to minimize copying time and wasted storage A lazy data structure is one where parts of it do not exist until they are accessed They are implemented as functions that return elements of a sequence only when requested This allows you to have “infinite” data structures Pattern Types Just as Prolog does parameter passing by unification, Haskell does parameter passing by pattern matching There isn’t a lot of difference between the two techniques A variable will match anything A wildcard, _, will match anything, but you can’t use the matched value A constant will match only that value Tuples will match tuples, if same length and constituents match Lists will match lists, if same length and constituents match (h:t) will match a nonempty list whose head is h and whose tail is t “As-patterns” have the form w@pattern However, the pattern may specify a list of arbitrary length When the pattern matches, the w matches the whole of the thing matched (n+k) matches any value equal to or greater than k; n is k less than the value matched Monads A monad consists of three things: A type constructor M This basically defines the form that M can take A bind operation, >>= (Haskell) or flatMap (Scala) For a monad m, this is a function m a -> (a -> m b) -> m b This function takes an a out of monad, applies a function a -> m b to it, and returns the new monad m b A function that puts a value into a monad: a -> m a Scala example: scala> val v = Some(2.0) v: Some[Double] = Some(2.0) scala> def root(n: Double) = if (n >= 0) Some(math.sqrt(n)) else None root: (n: Double)Option[Double] scala> v flatMap root res0: Option[Double] = Some(1.4142135623730951) Homoiconic languages Homoiconicity is when there is no distinction between code and data Prolog and Lisp (Clojure) are homoiconic—that is, there is no distinction between “statements” in the language and the “data” that the languages processes Lisp code is lists; Lisp data is lists Prolog code is predicates; Prolog data is predicates When you provide “input” to a homoiconic program (e.g. for an adventure game), you use the same syntax as when you write the program 27 Macros and metaprogramming A macro is like a function whose result is code Metaprogramming is using programs to write programs Arguments to a macro are not evaluated Macro calls are evaluated at compile time The return value of a macro should be executable code This is the primary use of macros in Clojure Macros are used to implement DSLs (Domain Specific Languages) Metaprogramming can be done in any language It's just a lot easier in a homoiconic language FP rules and benefits To get the benefits of functional programming, functions should be “real” functions, in the mathematical sense Functions should be free of side effects (input/output, mutable state) Functions should be deterministic The arguments to a function, and nothing else, determines its result If a function is side-effect free and deterministic, it has referential transparency—all calls to the function could be replaced in the program text by the result of the function Benefits: Because functions don’t make use of external, mutable data, they are easier to reason about, both mathematically and informally Because immutable data is a natural consequence of functional programming, correct concurrent programming becomes feasible Functional programming adds some powerful and convenient tools to the programmer’s toolbox (such as map, filter, and reduce) 29 Reactive Programming 30 Signals In reactive programming, a signal is a variable whose value changes as a result of some action external to the program itself Lifting is the automatic application of a function to a signal to produce a new signal Functional reactive programming (FRP) is using signals in an essentially functional language (Elm) Reactive programming is especially useful for handling real-time events, such as GUIs or embedded systems FRP may (or may not) become a significant paradigm in the near future 31 Scala Both Object-Oriented and Functional 32 Scala I like to say: Scala = Java – cruft + functional programming Scala is based on Java but borrows a lot from Haskell All the functional stuff Hadley-Milner type inference (to the extent possible) Monads. Monads are everywhere in Scala, but “under the hood” where they “just work” Scala borrows actors from Erlang, which we unfortunately did not cover Erlang, in turn, is based on Prolog 33 Scala’s cruft removal In many ways, Scala is a simplification of Java Semicolons, and many other bits of punctuation, are unnecessary Most type declarations are unnecessary Separate constructors are not necessary Static variables and methods do not exist However, Scala’s for loop has much more power Scala’s match, unlike Java’s switch, does not “fall through” Their place is taken by objects, which is arguably better There are no checked exceptions == works! The exact equivalent of the for loop has simpler, better syntax There is no “disappearing constructor” problem Plus, match is far more general than switch Scala has raw strings—great for multiline strings and regular expressions Scala allows many more kinds of nesting (like, methods within methods) 34 Scala’s corrections Scala has corrected some of Java’s mistakes Fall-through in switch statements was always a bad idea Tony Hoare, who invented null, calls it his “billion-dollar mistake” Scala uses a type lattice rather than a type hierarchy, which solves some technical problems Scala has null, but only so it can talk to Java Instead, Scala has the Option monad In Java, null should be a supertype of everything, but is a subtype Scala eliminated Java’s depth subtyping, which is not type safe, and which generics made unnecessary Scala satisfies the Uniform Access Principle: "All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation" (Example: length in Java fails this principle) 35 Scala’s additions Scala has: Functional programming, including map, filter, fold, and for-comprehensions Higher-order functions largely eliminate the need for loops (and are usually more readable) Loops are one of the main reasons for needing mutable variables Scala’s “for loops” are compiled into higher-order functions Pattern matching (in many places, not just match expressions) Actors (a well-tested technology, from Erlang) 36 Covariance, contravariance, invariance Covariance and contravariance are properties of collections A collection of values of type A is covariant if it may be treated as a collection of values of some supertype of A A collection of values of type A is contravariant if it may be treated as a collection of values of some subtype of A That is, you can use a subtype of the expected type Lists are covariant because a List[Dog] may be treated as if it were a List[Animal] class List [+A] extends LinearSeq[A] That is, you can use a supertype of the expected type trait Function1 [-T1, +R] extends AnyRef This trait defines a function that is contravariant in its argument type, and covariant in its return type A collection is invariant if it is neither covariant or contravariant Functions are contravariant in their argument types and covariant in their return types 37 Example of variances scala> class Animal defined class Animal scala> class Dog extends Animal defined class Dog scala> class Beagle extends Dog defined class Beagle scala> def foo(x: List[Dog]) = x foo: (x: List[Dog])List[Dog] // Given a List[Dog], just returns it scala> val an: List[Animal] = foo(List(new Beagle)) an: List[Animal] = List(Beagle@284a6c0) contravariant: can be treated as a collection of values of some subtype Parameter x of foo is contravariant; it expects an argument of type List[Dog], but we give it a List[Beagle], and that's okay covariant: can be treated as a collection of values of some supertype The return type is covariant; foo returns a List[Dog] but we assign the result to a variable of type List[Animal], and that's okay 38 Scala’s problems As I see it, Scala has three main problems: 1. The documentation is, shall we say, intimidating? Most of the time you don’t need to know about contravariance and IterableOnce The API could benefit from “progressive disclosure” 2. It needs a better infrastructure They’ve made a good start on an IDE, but there are a lot of tools (good debuggers, static analysis, etc.) that are still lacking 3. It doesn’t have a major company behind it Ok, it has Twitter, but they are (mostly) just users École Polytechnique Fédérale de Lausanne and now TypeSafe Mostly, it has to survive on its merits 39 Types 40 Type systems Static type checking is ensuring at compile time that all variables have known, fixed types, and that all operations used are appropriate for that type In dynamic type checking, variables may hold any type of value, and the types are checked only when an operation is applied In duck typing, the type of a variable is inconsequential, so long as the requested operations can be performed Programs don’t check the type, they check whether the desired operations are (currently) available Haskell uses static types, but uses Hindley-Milner type checking to determine the types for itself 41 Static and dynamic languages In a static language, all the types and all the methods are determined before the program runs Some examples are C, C++, Java, Scala, Fortran, … Generally regarded as better for programming in the large Faster, because compiler optimization techniques are better developed (this is changing) In an dynamic language, types can change, methods can be created (and used) or destroyed at runtime Dynamic languages typically have eval (or equivalent) Some examples are Perl, Lisp(s), Prolog, REBOL, Ruby, Javascript, Python, … Generally regarded as better for programming in the small Often faster in practice, because less code A lot of work is currently being done to optimize JavaScript 42 Typeclasses A Haskell typeclass is like a Java interface, or a Scala trait—it tells what functions an object can support Some typeclasses and what they support: Eq -- == and /= Ord -- < <= >= > Num -- + - * / and others Show -- show (enables printing as a string) Read -- read (conversion from a string to something else) Functor -- fmap (enables mapping over things) Lists belong to the Functor typeclass Monad -- >>= >> return fail The importance of a typeclass is that it can be mixed in to other classes, giving them features that would otherwise have to be programmed in each case Scripting languages A scripting language is a language that automates tasks that would otherwise be done manually at the command line The typical use is to “script” together other programs, including those provided by the operating system (cd, chmod, etc.) Scripting languages are typically dynamic and interpreted Perl is the most popular, but other languages can be so used (AppleScript, Bash, PowerShell, Python, Ruby, even Scala) 44 Recursion 45 Recursion Loops and recursion are equivalent in power If a function is tail-recursive, it can be automatically turned into a loop (thus saving stack frames) Anything that can be done with a loop can be done recursively, and vice versa Tail recursion is when the recursion is the last thing done in every branch of the function Loops are less useful when data is immutable Loops are used primarily to modify data When data is immutable, the substitution rule applies: Any variable or function call may be replaced by its value The substitution rule simplifies reasoning about programs Four rules for doing recursion Do the base cases first Recur only with simpler cases Don't modify and use non-local variables You can modify them or use them, just not both Remember, parameters count as local variables, but if a parameter is a reference to an object, only the reference is local—not the referenced object Don't look down 47 Tail call elimination Recursive functions can often be rewritten to be tail recursive This is done by creating a helper function that takes an additional “accumulator” parameter Non-tail recursive function to find the factorial: (defn factorial-1 [n] (if (= n 1) 1 (* n (factorial-1 (dec n))) ) Tail recursive function to find the factorial: (defn factorial-2 ([n] (factorial-2 1 n)) ([acc n] (if (= n 1) acc (recur (* n acc) (dec n)) ) ) ) Concurrency Four Paradigms 49 Shared state The traditional way of communicating between processes is to give each of them read/write access to the same memory Locks (semaphores, synchronization) are used to allow only one process at a time access to given memory This results in numerous problems: Race conditions Deadlock Livelock Starvation Corrupted data Indeterminacy 50 Actors An actor is an independent flow of control An actor does not share its data with any other process This means you can write it as a simple sequential process, and avoid a huge number of problems that result from shared state However: It is possible to share state; it’s just a very bad idea Any process can send a message to an actor with the syntax actor ! message An actor has a “mailbox” in which it receives messages An actor will process its messages one at a time, in the order that it receives them, and use pattern matching to decide what to do with each message You can think of an actor as a Thread with extra features Except: Messages which don’t match any pattern are ignored, but remain in the mailbox (this is bad) An actor doesn’t do anything unless/until it receives a message 51 Agents An agent is a thread that holds a value Like actors, agents use mailboxes and message passing Unlike actors, the messages are functions to be executed, not data to be processed. 52 Software Transactional Memory A transaction takes a private copy of any reference it needs Since data structures are persistent, this is not expensive The transaction works with this private copy If the transaction completes its work, and the original reference has not been changed (by some other transaction), then the new values are copied back atomically to the original reference But if, during the transaction, the original data is changed, the transaction will automatically be retried with the changed data However, if the transaction throws an exception, it will abort without a retry Miscellaneous 54 JVM Languages Designed for the JVM Java 8 (for the patient and hopeful developer) Scala Clojure Kotlin Ceylon Groovy Fantom Mirah Existing languages ported to the JVM Ruby (JRuby) Python (Jython) Javascript (Rhino and others) Erlang (Erjang) Scheme (different implementations like Kawa) Source: http://www.infoq.com/research/next-jvm-language 55 Programming paradigms According to Wikipedia, there are five main paradigms: imperative, functional, object-oriented, logic and symbolic programming Some imperative languages are Fortran, Basic, and C Functional languages include ML, Ocaml, and Haskell Object-oriented languages include Java, C++, and C# Prolog and its derivatives are the main logic languages Symbolic programming languages include Prolog, Lisp, and Clojure A multiparadigm language is one with significant support for more than one paradigm Scala is both object-oriented and functional This makes it a good “capstone” language for CIS 554 Oz is a research language designed to include all paradigms 56 Summary of summary “The world will little note, nor long remember, what we say here…” We haven’t spent enough time on any of these languages for you to have gotten good at them (Though I hope I’ve given you a good start on Scala ) So what’s the point? In previous years I’ve had students tell me that the course has given them the confidence to take on new languages If I’ve succeeded in that, I don’t feel this course was a waste of time, and I hope you don’t, either 57 The final exam We have covered a lot of languages, and I don’t expect you to remember the syntax of them all However, there may be questions where you will need to read the syntax well enough to answer questions I may ask you to define some terms A definition is a membership test: You should be able to use the definition to determine whether a thing is or is not an example of the term 58 Genealogy Simula objects Lisp C functional programming syntax ML Haskell Prolog Smalltalk pattern matching C++ Clojure Erlang Java reactive programming Actors Elm Scala 59 The End 60