Ryerson Computer Science CPS506: Comparative Programming Languages

(go back)

Preliminaries

Language Selection Criteria:

  • readability
    • simplicity
    • orthogonality
    • data types
    • syntax design
  • writability
    • simplicity
    • orthogonality
    • abstraction support
    • expressivity
  • reliability
    • type checking
    • exception handling
    • aliasing
  • cost

Influences on Language Design: computer architecture. Von Neumann architecture - influenced imperative programming languages

Language Categories: imperative, visual, scripting, logic, markup (not real programming language)

Implementation: compiled, interpreted, hybrid, preprocessors

Typing: Strong (java) vs weak (C); Static (java) vs dynamic (ruby)

Expression Syntax

Example of an Abstract Syntax Tree

How to remember each *fix from each other: the pre/in/post refer to the position of the operator; the child nodes always go left then right

Postfix:

Left child, Right child, then Operator (Postfix should not have brackets)

	a b c + d e + f + - * foo g -

Prefix:

Operator, Left child, then Right child

	(-(foo a (*(+ bc) (-(+ d e f ))))) g)

Infix:

Left child, Operator, then Right child

Visitor Pattern

Implements double-dispatch for a language that does not natively support it. Double dispatch is essentially calling a method that calls you back in an appropriate place.

Ruby - object-oriented with syntactic sugar for programmer efficiency

Dynamically duck typed language designed for optimum programmer performance (possibly at the cost of software performance).

Overview

Paradigms

  • Object-Oriented
    • Fully - everything is an object
    • Smalltalk equivalent
    • Single inheritance
    • Includes Mixins
  • Imperative subset
    • Open coding is in a default class

Syntax

  • C-like
  • Infix multi-precedent operators (~18 levels)
  • control structures are statements
  • Classes can be nested within modules
  • Blocks allow extending control structures
  • fixed arity methods, but trailing "named" parameters automatically put in a hash
  • can open already defined classes/modules to change them

Semantics

  • everything except assignment and basic control (return, if, while, yield) are message sends
  • everything returns a value, control are parts of expressions
  • parameters are call-by-value
  • dynamically typed - "duck typing"
  • blocks are closures - can yield intermediate values

Pragmatics

  • tokenized interpreter
  • highly dynamic semantics make optimization challenging
  • designed for programmer efficiency, not machine efficiency
  • conveniently implements Domain Specific Language
  • file based, no IDE, although irb allows interactivity, and supported by Eclipse

Strings

  • #{var} is evaluated when inside "double quotes", not evaluated in single quotes
  • to show actual #{var}, escape the # with \

Dynamic Typing Concerns (passing integers vs objects)

Roughly how it works: When passing a numeric type variable, if the least significant bit is = 0, then ruby assumes it is an integer value, and extracts the value from the next 31 bits. If the LSB is = 1, then ruby assumes then entire number is the id of the object.

Sample Ruby Class Code

					class Person 
						include ToFile
						attr_accesor :name
						def initialize(name)
							@name = name
						end
		
						def to_s
							name
						end
					end

Mixins

Ruby allows developers to add functionality to include centralized code functions into any class using the "include {module}" statement.

Visitor pattern sample code in Ruby

					class MyFunctionEvaluator
						def visit(someItem)	
							someItem.accept(self)
						end
						def visitItemTypeA(item)
							return item.field
						end
						def visitItemTypeB(item)
							return item.field
						end
					end
					
					class ItemTypeA
						def accept(visitor)
							visitor.visitItemTypeA(self)
						end
					end
					a = ItemTypeA.new()
					mfe = MyFunctionEvaluator.new()
					puts mfe.visit(a)

Scala - somewhat functional, with interesting type inferencing and case classes

Overview

Paradigms

  • Object-Oriented
    • Fully - everything is an object
    • Smalltalk equivalent
    • Single inheritance
    • Includes traits(Mixins)
    • Definable singleton objects
  • Functional subset
    • immutable classes
    • first-class functions
    • type inference
  • Imperative subset
    • Open coding is in a default class

Syntax

  • C-like
  • Definable infix multi-precedent operators (10 levels)
  • control structures are expressions

Semantics

  • everything except assignment and basic control (if,for) are message sends
  • everything returns a value, control are parts of expressions
  • parameters are call-by-value, or call-by-name
  • statically typed, type inference

Pragmatics

  • compiles to JavaVM or CLR
  • file based, no IDE, although scala allows interactivity, and supported by Eclipse

Traits

Scala features "traits" which are essentially like interfaces, but that come with implementations. Another way to look at it is that they provide similar functionality to multiple inheritence but without inheriting the baggage of an entire ancestry

Pattern Matching: Case Classes

  • "new" keyword not needed when using
  • constructor parameters are public "fields"
					sealed abstract class Tree 
						case class Children(li:List[Tree]) = List() extends Tree
						case class Node(i:Integer)extends Tree
		
					// this tries to find the 'li' parameter of Children 
					//(if someObject is type Children) 
					val Children(x) = someObject
		
					// OR...
					someObject match {
						case Children(x) => println (x)
						case Node(x) => println(x)
						case x => List(x) // catch all
					}

Parsers in Scala

Some discussion of combinators: functions that combine to make composite functions. Parsers often made up of these.

Domain Specific Language

Examples from the lecture included a partial BASIC language implementation for a lunar lander, and a financial application (stocks, bonds, and so on)

Summary

Strengths

  • concurrency
  • domain-specific languages

Weaknesses

  • static typing
  • ugly syntax
  • mutability with "var" keyword can cause concurrency issues

Haskell - purely functional

Wiki Books

Overview

Paradigms

  • Functional
    • Fully - side effects are restricted to monads
    • Lazy evaluation outside of monads
    • staticly typed
  • Imperative subset
    • command line in some compilers

Syntax

  • mathematical
  • Infix multi-precedent operators (standard 10 levels, definable)
  • control structures are expressions
  • no special forms except definitions
  • all functions have arity 1, currying

Semantics

  • everything is lazy function application
  • everything returns a value, control are parts of expressions
  • parameters are call-by-need
  • richly statically typed - parametric polymorphism

Pragmatics

  • native compilers
  • lazy evaluation makes some optimization challenging
  • designed for purity
  • conveniently implements Domain Specific Language
  • file based, no built-in IDE, although hugs allows interactivity

Basic Function Syntax

Defining a function requires the syntax: let [functionname] [list of parameters] = [function_expression]. Eg:

			> let sum x y = x + y

Function with helper (this seems to be incorrect):

				> let f1 a = f1 f2 a
					where f2 x = x + x

Currying (and the :t command)

The type of a function is composed of types like the function itself. The :t instruction in the interactive Haskell environment returns the type of a function in the form: [function]::[param1Type]->[param2Type]->[outputParamType]. It gets a little confusing with composite functions. Example:

					> let simpleFunction p1 p2 p3 = p3(p1 + p2)
					> :t hs
					hs :: Num a => a -> a -> (a -> t) -> t

My interpretation is: hs, where a is a Num (to the left of => is a restriction on the type), is of type where p1 is type a, p2 is type a, and p3 maps type a to type t. The type after the last -> is the resultant type. Summary:

  • name of function is first, followed by ::
  • the resultant value type is always at the far right
  • types are listed in order of the parameters listed on the LHS of the = sign
  • same types use the same identifier
  • function types are shown as two identifiers inside brackets, where the output value type is on the RHS of ->
					> let twice f x = f(f(x))
					> :t twice
					twice :: (a -> a) a -> a

More complex example

				let myprodt x = (prod ((fst x) (snd x )))::Double

List Comprehension, etc

String Concatenation

"hello " ++ "world"

Guards

Guard is like an if/else or case statement in a haskell function

						factorial :: Integer -> Integer
						factorial x
							| x > 1 = x * factorial(x -1)
							| otherwise = 1

Tuples

Fibonaci in linear time

					fibNextPair::(Integer, Integer) -> (Integer, Integer)
					fibNextPair (x, y) = (y, x + y)
					fibNthPair 1 = (1, 1)
					fibNthPair n = fibNthPair(fibNthPair(n-1))

Lists & Zip

Zip takes two lists and turns them into a single list of tuples consisting of members of each list at the same index location

					[1..3] 		-- gives [1, 2, 3]
					take 4 [1,5..100] 	-- [1,5,9,13]
					let x = [1,5..] 	-- infinite list
					-- pairs the elements: [(1,1),(2,2),(3,3),(4,4),(5,5)]
					take 5 (zip x x) 	
					-- [(1,6),(2,7),(3,8),(4,9),(5,10)]
					take 5 (zip x (drop 5 x))

Map

map: applies a function to every element of a list, returns the affected list

						let x = [1,3..]
						let sqr x = x * x
						take 10 (map sqr x)
						-- output: [1,4,9,16,25,36,49,64,81,100]

Haskell Types

  • use :set +t to turn on the type option. Shows the type of anything you enter
  • use :unset +t to turn off

User Defined Types

					-- makes a type called Boolean that can have two values
					data Boolean = True | False
					data Suit = Spades | Hearts deriving (Show)

Alias Types

					type Card = (Rank, Suit)
					type Hand = [Card]

Pattern Matching

Here is an example of pattern-matching in Haskell. Pattern matching is similar to overloading in an object-oriented language. There are multiple definitions for the same function signature:

					maze2String :: Bool -> [Wall] -> String
					maze2String horiz (Horizontal (row,0):rest) = "\n"++maze2String True rest
					maze2String horiz (Vertical (row,0):rest) = "\n| "++maze2String False rest
					maze2String horiz (NoWall (row,0):rest) = "\n  "++maze2String False rest
					maze2String horiz (Horizontal (row,col):rest) = "+-"++maze2String True rest
					maze2String horiz (Vertical (row,col):rest) = "| "++maze2String False rest
					maze2String True (NoWall (row,col):rest) = "+ "++maze2String True rest
					maze2String False (NoWall (row,col):rest) = "  "++maze2String False rest
					maze2String horiz []= ""

Summary

Strengths

  • purely functional language
  • very expressive
  • lazy semantics

Weaknesses

  • inflexible (due to purely functional)
  • learning curve
  • small community

Clojure

Getting started: http://clojure.org/getting_started

Overview

Paradigms

  • Functional
    • Limited mutability
  • Imperative subset
    • Open coding is in a default environment

Syntax

  • Lisp
  • Prefix notation
  • control structures are expressions
  • First-class functions/macros allow extending control structures
  • variable arity functions
  • modified from standard Lisp syntax - fewer parentheses, more special syntax
  • destructuring bindings

Semantics

  • everything except limited special forms (def, defn, if, do, let, quote, var, fn, loop, recur, throw, try, set!, ., new) are message sends
  • everything returns a value, control are parts of expressions
  • parameters are call-by-value
  • dynamically typed - "duck typing"
  • refs, atoms, agents, futures

Pragmatics

  • runs on JVM/CLR interpreter
  • conveniently implements Domain Specific Language, using macros
  • integrates the static types of the JVM with dynamic Lisp
  • file based, no IDE, although allows interactivity, and supported by Eclipse

Features

Expressions

					  (+ 1 2 3 (* 2 2))
					  (< 1 2 3 4 5)
					  (if nil nil "nil is false")
					  (if false nil "false is false")
					  (if 0 nil "everything else is true")
					  (1 2 3)
					  (list 1 2 3)
					  '(1 2 3)
					  ;Zfirst last rest cons nth
					  [1 2 3 4];  - can apply as functions to index
					  #{1 3 4 2} ; - sets
					  {1 1, 2 4, 3 9, 4 16}; - maps - commas optional
					  ; keywords - like symbols:
					  {:foo 1, :bar 4, :brillig 9, :tove 16} 

Definitions

					  	(def four (+ 3 1))
					  	(defn funname "doc string" [param list]
					 		(+ param list))
					  	(defn abc [a [_ b] [[c _ d]]] [a b c d])
					  	(let [[_ a] value] (+ a 1))
					  	(let [{name :name} value] (list name))
						; two forms of anonymous functions:
					  	(fn [params] (list params))
					  	#(list (+ 1 (count %)))

Protocols

						(defprotocol Compass
						(defrecord SimpleCompass

Imperative

						(def aref (ref "value")) @aref (dosync (alter aref str "update"))
						(def val (atom "old")) @val (reset! val "new") (swap! val fun "update")
						(def bond (agent 1)) @bond (send bond double)
						(def iNeed (future (Thread/sleep 10000) "a result")) @iNeed

Higher Order Functions, Sequences

					  (map count '((1 2 3) [4 5 6] #{7 8 9} {10 11 12 13})
					  (apply + [1 2 3 4])
					  (filter odd? #{1 2 3 4})
					  (every? odd? '(1 2 3))
					  (some odd? '(1 2 3))
					  (not-every? odd? '(1 2 3))
					  (not-any? odd? '(1 2 3))
					  (for [x seq1, y seq2] (list x y))
					  (for [x seq1, y seq2, :when (<= x y)] (list x y))
					  (reduce + '(1 2 3))
					  (sort '(4 -1 2 -3))
					  (sort-by abs '(4 -1 2 -3))

Loops, Recursion

						(defn mycount [v]
				 			(if (empty? v)
				  			0
				  			(inc (mycount (rest v)))))
		
						(loop [v] (if (empty? v) 0 (inc (size (rest v)))))
		
						(defn mycount2 [v]
				 			(loop [v v, c 0]
				   				(if (empty? v)
				    				c
				    					(recur (rest v) (inc c)))))
		
						(repeat 4)
						(cycle '(1 2 3))
						(take 4 (cycle '(1 2 3)))
						(drop 2 (take 4 (cycle '(1 2 3))))

Anonymous Functions

				user > (defn twice-count [w] (* 2 (count w)))
				#'user/twice-count
				user > (twice-count "Lando")
				10
				user > (map twice-count people)
				(6 16)
				; anonymous version
				user > (map (fn [w] (* 2 (count w))) people)
				(6 16)
				; alternate anonymous version
				user > (map #(* 2 (count %)) people)
				(6 16)

Atoms, Agents, and Futures

From here:

  • Refs are for coordinated, synchronous access to many identities.
  • Atoms are for uncoordinated, synchronous access to a single identity.
  • Agents are for uncoordinated, asynchronous access to a single identity.
  • Vars are for thread local isolated identities with a shared default value.
				; async Agent:
				(def agent_variable (agent 1)) @some_agent (send agent_variable double)
				; a future from the book
				(def future_handler (future (Thread/sleep 5000) "take time"))

Summary

Strengths

  • lips syntax makes for a very powerful language
  • uses the JVM so has a large library of functions
  • concurrency control
  • lazy evaluation

Weaknesses

  • readability - prefix notation
  • learning curve

Erlang

Designed by phone company for highly reliable software. Functional paradigm.

Overview

Paradigms

  • Functional
    • Mostly immutable
    • Rich concurrency support

Syntax

  • Prolog-like
  • Infix multi-precedent operators
  • control structures are pattern matching
  • Functions are defined in modules
  • Only control structures are matching, recursion, list comprehensions
  • fixed arity functions, but can share a name with different arity
  • spawn, receive, ! for communication

Semantics

  • tail recursion is recognized
  • everything returns a value, control are parts of expressions
  • parameters are call-by-value
  • dynamically typed
  • functions can be spawned as processes - can receive messages

Pragmatics

  • tokenized interpreter ?
  • designed for programmer efficiency, not machine efficiency
  • file based, no IDE, although erl allows interactivity
  • extreme stability - processes fail, system keeps running

Features

Expressions

				 	2 + 2.
					2 < 4.0.
					symbol
					Variable % must begin with uppercase char
					= % is pattern-match/binding - once
					[[3,4],5,6]. [3,4|[5,6]]. % can have improper lists
					{2,3,4}.
					{valid,[H|T],X} = {valid,[1,2,3],blat}.

Definitions

				  	c(matching_function).
					%- can apply as functions to index
					matching_function:number(one). 
					c(yet_again).
					Negate = fun(X) -> -X end.

Loops, Recursion

  • tail-calls properly recognized
  • loops are simply tail-recursive function calls
  • full power of function pattern-matching for loop control

Higher Order Functions, Lists

					lists:foldl(fun(X, Sum) -> X + Sum end, 0, Numbers).
					lists:map(fun(X) -> X + 1 end, Numbers).
					lists:filter(Small, Numbers).
					lists:all(Small, [0, 1, 2]).
					lists:any(Small, Numbers).
					lists:takewhile(Small, Numbers).
					lists:dropwhile(Small, Numbers).
					lists:foldl(fun(X, Sum) -> X + Sum end, 0, Numbers).
					  % list comprehensions:
					  %   generators and filters:
					  [{X,Y}||X <-[1,2,3,4],X < 3,Y<-[5,6],Y<=X].

Processes, Concurrency, Failure

					  receive Pattern -> ...; Pattern -> end
					  Pid = spawn(fun abcdef)
					  Pid = spawn(module,fun,[args])
					  Pid ! message
					  link(Pid) % leads to
						{'EXIT', From, Reason}
					  Pid = spawn_link(fun abcdef)
					  register(atom,Pid)
					  self()

Summary

Strengths

  • excellent concurrency and fault tolerance
  • well tested core libraries

Weaknesses

  • poor adoption rate due to being a niche language
  • syntax is awkward

Languages Summary

LanguageTypingParadigm(s)Notes
Ruby Dynamic (duck) Object Oriented Designed for programmer efficiency
Scala Static Object Oriented with Functional subset Partial hybrid of OO and functional
Haskell Static Pure Functional, stateful with the use of Monads Lazy evaluation
Clojure Dynamic (duck) Functional with imperative subset Lisp-based language with lazy evaluation
Erlang Dynamic Functional, mostly immutable Prolog-like syntax; excellent concurrency support