Programming Paradigms Concepts

advertisement
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
Download