CSC/ECE 517 Fall 2014/ch1a 9 aa: Difference between revisions
No edit summary |
|||
Line 370: | Line 370: | ||
Now if we make a call to the schedule method on an instance of CollegeStudent, the compiler knows that we’re referring to the implementation of schedule in the Worker trait. This is because Worker was mixed in after theStudent trait. | Now if we make a call to the schedule method on an instance of CollegeStudent, the compiler knows that we’re referring to the implementation of schedule in the Worker trait. This is because Worker was mixed in after theStudent trait. | ||
= References = | |||
<references /> | |||
= Further reading = | = Further reading = | ||
Line 375: | Line 378: | ||
# [http://docs.scala-lang.org/tutorials/ Scala Documentation] | # [http://docs.scala-lang.org/tutorials/ Scala Documentation] | ||
# [http://shipilev.net/blog/2014/java-scala-divided-we-fail/#_analyzing_bottlenecks Java vs. Scala] | # [http://shipilev.net/blog/2014/java-scala-divided-we-fail/#_analyzing_bottlenecks Java vs. Scala] | ||
Revision as of 22:29, 17 September 2014
What is Functional Programming?
In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. The programming is done with the use of expressions, that is, it is a declarative programming paradigm. One key feature in a functional language is the concept of first-class functions. The idea is that you can pass functions as parameters to other functions and return them as values. Also, the output value of a function depends only on the arguments that are input to the function, unlike in imperative programming languages. Eliminating side effects, i.e. changes in state that do not depend on the function inputs, can make it much easier to understand and predict the behaviour of a program, which is one of the key motivations for the development of functional programming. Some examples of functional programming languages: Common Lisp Scheme Erlang Haskell
What is Object Oriented Programming?
Object-oriented programming (OOP) is based upon the concept of real world "objects". These objects have data fields called attributes that describe the object and some procedures associated with them called methods. Object-oriented programming takes the view that what we really care about are the objects we want to manipulate rather than the logic required to manipulate them.
Some examples of object oriented programming languages are: Simula Smalltalk C++ Java C#
Features of Functional Programming
Higher-order functions:
Higher-order functions (HOFs) are functions that take other functions as their arguments. A basic example of a HOF is map which takes a function and a list as its arguments, applies the function to all elements of the list, and returns the list of its results. Higher-order functions are very useful for refactoring code and reduce the amount of repetition. Let’s see an example of a higher order function that receives a message, transforms it in various ways, and forwards it to another server.
class MessageHandler { void handleMessage(Message msg, Function getClientCode) { // ... Message msg1 = msg.setClientCode(getClientCode()); // ... sendMessage(msg1); } // ... } String getClientCodeOne() { return "ABCD_123"; } String getClientCodeTwo() { return "123_ABCD"; } MessageHandler handler = new MessageHandler(); handler.handleMessage(someMsg, getClientCodeOne);
Here, we see that we have created no new types and no class hierarchy. We simply pass appropriate functions as a parameter. We don't restrict ourselves to class hierarchies: we can pass new functions at runtime and change them at any time with a much higher degree of granularity with less code.
Purity
Some functional languages allow expressions to yield actions in addition to return values. These actions are called side effects to emphasize that the return value is the most important outcome of a function (as opposed to the case in imperative programming). Languages that prohibit side effects are called pure. Even though some functional languages are impure they often contain a pure subset that is also useful as a programming language. It is usually beneficial to write a significant part of a functional program in a purely functional fashion and keep the code involving state and I/O to the minimum as impure code is more prone to errors.
Immutable data
Purely functional programs typically operate on immutable data. Instead of altering existing values, altered copies are created and the original is preserved. Since the unchanged parts of the structure cannot be modified, they can often be shared between the old and new copies, which saves memory.
Referential transparency
Pure computations yield the same value each time they are invoked. This property is called referential transparency and makes possible to conduct equational reasoning on the code. For instance if y = f x and g = h y y then we should be able to replace the definition of g with g = h (f x) (f x) and get the same result; only the efficiency might change.
Lazy evaluation
Since pure computations are referentially transparent they can be performed at any time and still yield the same result. This makes it possible to defer the computation of values until they are needed, that is, to compute them lazily. Lazy evaluation avoids unnecessary computations and allows, for example, infinite data structures to be defined and used. Let’s take a look at the following piece of code:
String s1 = somewhatLongOperation1(); String s2 = somewhatLongOperation2(); String s3 = concatenate(s1, s2);
In an imperative language the order of evaluation would be clear. Because each function may affect or depend on an external state it would be necessary to execute them in order: first somewhatLongOperation1, then somewhatLongOperation2, followed by concatenate. Not so in functional languages. somewhatLongOperation1 and somewhatLongOperation2 can be executed concurrently because we're guaranteed no function affects or depends on global state. But what if we don't want to run the two concurrently, do we need to run them in order? The answer is no. We only need to run these operations when another function depends on s1 and s2. We don't even have to run them before concatenate is called - we can delay their evaluation until they're required within concatenate. If we replace concatenate with a function that has a conditional and uses only one of its two parameters we may never evaluate one of the parameters at all!
Recursion
Recursion is heavily used in functional programming as it is the canonical and often the only way to iterate. Functional language implementations will often include tail call optimisation to ensure that heavy recursion does not consume excessive memory.
Pattern Matching
In the context of pure functional languages, Pattern Matching is a dispatch mechanism: choosing which variant of a function is the correct one to call. Example:
factorial(0) ::= 1 factorial(n) ::= n * factorial(n-1)
The function has been given two definitions. Which is used for dispatch when a call is made will depend, in this case, on whether the actual parameter pattern matches 0 or not. The matching is not limited to constants; in general, functions have a TypeSignature used for matching. Currying is integrated into this mechanism. 1.Pattern matching is the compiler's ability to compare both the form and the content of two expressions. It is used in two ways: All or part of a data structure may be assigned to one or several variables in a single expression. 2.A function may have several definitions (clauses), according to the form and/or content of its arguments. This second use is usually combined with the first to initialize the variables that will be used in the clause. In both these uses, a pattern match has the additional benefit of making an assertion about the form and/or content of a variable.
Closures
Passing functions around in impure languages is a little bit different than doing it in the confines of lambda calculus and requires support for an interesting feature often referred to as lexical closure. Let's take a look at some sample code. Remember, in this case variables aren't final and functions can refer to variables outside of their scope:
Function makePowerFn(int power) { int powerFn(int base) { return pow(base, power); } return powerFn; } Function square = makePowerFn(2); square(3); // returns 9
The function makePowerFn returns a function that takes a single argument and raises it to a certain power. What happens when we try to evaluate square(3)? The variable power isn't anywhere in scope of powerFn because makePowerFn has returned and its stack is long gone. How can square work, then? The language must, somehow, store the value of power somewhere for square to work. What if we create another function, cube, that raises something to the third power? The runtime must now store two copies of power, one for each function we generated using makePowerFn. The phenomenon of storing these values is called a closure.
Comparison between Functional Programming and OOP <ref>http://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2013/ch1_1w25_aras&action=edit§ion=35</ref>
Let us compare both the programming paradigms with respect to several disctinction attributes.
Point of Comparison | Functional Languages | Object-Oriented Languages |
---|---|---|
|
Functional programming emphasizes on functions that produce results which depend only on their inputs and not on the program state - i.e. pure mathematial functions. | Focus on identifying and representing the problem in terms of an 'object' which has its own data, sub-routines and state. Different objects in the problem interact by sending messages to each other and thus result in change in its internal state. The final state and values of the objects refer to the solution. It is data-centric. |
|
Primarily Top-down design | Identification and design of necessary objects. Close to being 'better models of the way the world works'. |
|
Often sequential with program having single point of entry and exit. | Complex program flow. Can sometimes depend on the internal state of the objects. |
|
Limited modularity. Program is divided into modules or per say procedures independent of each other but are constrained due to uniqueness to that particular problem. | Extremely modular due to the presence of objects which contain their own data and sub-routines. |
|
No concept of data-hiding. Variables local to one method cannot be accessed by other method. But, Global variables can be accessed anywhere within the program. | One of the main fundamentals of O-O languages. Access specifiers like 'public', 'private' and 'protected' dictate the rules of data-hiding. Data which is private is confined to one object and cannot be directly changed by any other method except its own. This places the responsibility of managing data with the object itself This is called as ownership. Thus, data can be accessed ( read/write/modified ) only through the object's own interfaces. |
|
Smaller programs are easy to understand but as the program increases in size; understanding the code becomes more and more difficult. | Easy to understand due to its real world-like design and flow. |
|
Very Limited or no code re-usability. | Highly re-usable code as the code developed can be easily modified or extended to suit a problem's need. |
|
Difficult due to limited in-built functionality. | Easily possible due to the concept of classes. Generic classes can be built as per the required specifications. |
|
Efficient for solving small problems. | Efficient for solving large problems which have a complex structure and require complex data-types, abstraction and data-security. |
|
Maintenance is easy for smaller programs but can consume a-lot of effort for larger program size as it requires the programmer to know and understand the dependencies of every module in the program. This makes it difficult to debug and test the program. | Extremely simple as O-O languages aim for high modularity. Secondly, programmer is not concerned with the details of how the data is stored and represented. Thirdly, they also tend to keep low coupling which makes it easy to debug and test different modules in the program. |
|
Less Extensible as modules developed need to be re-organised and re-structured heavily in order to meet different needs. | High extensibility is one of the most important advantages of OOP. Code can be easily modified and 'plugged-in' to a different program. Methods can be exteneded due to many properties such as polymorphism, inheritance and support for multiple inheritance through interfaces. |
|
Less flexible. Sometimes, certain problems do not fit into the 'top-down design' approach. | High flexibility. The modelling of problems into world-like objects makes it easy to solve any practical problem. |
|
Haskell, Scala, Erlang | C++, Java, Ruby, Python. |
Scala : An overview
Scala is an object-functional programming and scripting language generallly used in software applications. Scala has full support for functional programming (including currying, pattern matching,algebraic data types,lazy evaluation,tail recursion,immutability, etc.) and a very strong static type system. This allows programs written in Scala to be very concise and thus smaller in size than most general purpose programming languages. Many of Scala's design decisions were inspired by criticism over the shortcomings of Java.
Scala source code is intended to be compiled to Java bytecode, so that the resulting executable code runs on a Java virtual machine. Java libraries can be used directly in Scala code, and vice versa.
object HelloWorld { def main(args: Array[String]) { println("Hello, world!") } }
This program consists of one method called main which takes the command line arguments, an array of strings, as parameter; the body of this method consists of a single call to the predefined method println with the greeting as argument. The main method does not return a value (it is a procedure method). Therefore, it is not necessary to declare a return type. Such a declaration introduces what is commonly known as a singleton object, that is a class with a single instance. The declaration above thus declares both a class called HelloWorld and an instance of that class, also called HelloWorld. This instance is created on demand, the first time it is used. Notice that the main method is not declared as static (like in Java) here. This is because static members (methods or fields) do not exist in Scala. Rather than defining static members, the Scala programmer declares these members in singleton objects.
Features of Scala <ref>http://www.scala-lang.org/old/node/104</ref>
Scala is object-oriented
Scala is a pure object-oriented language in the sense that every value is an object. Types and behavior of objects are described by classes and traits. Classes are extended by subclassing and a flexible mixin-based composition mechanism as a clean replacement for multiple inheritance.
Scala is functional
Scala is also a functional language in the sense that every function is a value. Scala provides a lightweight syntax for defining anonymous functions, it supports higher-order functions, it allows functions to be nested, and supports currying. Scala's classes and its built-in support for pattern matching model algebraic types used in many functional programming languages. Furthermore, Scala's notion of pattern matching naturally extends to the processing of XML data with the help of right-ignoring sequence patterns. In this context, sequence comprehensions are useful for formulating queries. These features make Scala ideal for developing applications like web services.
Scala is statically typed
Scala is equipped with an expressive type system that enforces statically that abstractions are used in a safe and coherent manner. In particular, the type system supports: generic classes, variance annotations, upper and lower type bounds, inner classes and abstract types as object members, compound types, explicitly typed self references, views, and polymorphic methods.
Scala is extensible
In practice, the development of domain-specific applications often requires domain-specific language extensions. Scala provides a unique combination of language mechanisms that make it easy to smoothly add new language constructs in form of libraries: any method may be used as an infix or postfix operator, and closures are constructed automatically depending on the expected type (target typing). A joint use of both features facilitates the definition of new statements without extending the syntax and without using macro-like meta-programming facilities.
Scala interoperates with Java and .NET
Scala is designed to interoperate well with the popular Java 2 Runtime Environment (JRE). In particular, the interaction with the mainstream object-oriented Java programming language is as smooth as possible. Scala has the same compilation model (separate compilation, dynamic class loading) like Java and allows access to thousands of existing high-quality libraries.Support for the .NET Framework (CLR) is also available.
Similarities between Scala and Java
1) Both are JVM based language, Scala produce same byte code as Java and runs on Java Virtual Machine. Similar to Java compiler javac, Scala has a compiler scalac, which compiles Scala code into byte code. At this level, all JVM language like Groovy,JRuby, Scala becomes equals to Java, because they use same memory space, type system and run inside same JVM.
2) You can call Scala from Java and Java from Scala, it offers seems less integration. Moreover, you can reuse existing application code and open source Java libraries in Scala.
3) Major Java programming IDE like Eclipse, Netbeans and InetelliJ supports Scala.
4) One more similarity between Scala and Java is that both are Object Oriented, Scala goes one steps further and also supports functional programming paradigm, which is one of it's core strength.
Scala vs JAVA
Syntactic flexibility
Scala has a very powerful and flexible syntax as it relates to methods, both declaration and invocation. In Java you can create methods with different visibilities, modifiers andreturn types.Scala does allow for different visibilities on not just methods, but any members. For example:
class Person { private var name = "Daniel Spiewak" val ssn = 1234567890 // public constant field def firstName() = splitName()(0) // public method private def splitName() = name.split(" ") // private method protected def guessAge() = { import Math._ round(random * 20) } }
Scala provides he option to import into a specific scope.The import statement within guessAge()is much like a Java static import statement which only provides access to the Math members within theguessAge() method. So we couldn’t just make a call to round() from within the splitName() method. Scala access modifiers are also quite a bit more powerful than Java’s. For example,protected by default limits access to only subclasses, unlike Java which also allows access to other classes in the same package.
Unified type system
Java makes a sharp distinction between primitive types (e.g. int and boolean) and reference types (any class). Only reference types are part of the inheritance scheme, deriving from java.lang.Object. In Scala, however, all types inherit from a top-level class Any, whose immediate children are AnyVal(value types, such as Int and Boolean) andAnyRef (reference types, as in Java). This means that the Java distinction between primitive types and boxed types (e.g. int vs. Integer) is not present in Scala; boxing and unboxing is completely transparent to the user.
For-expressions
Instead of the Java "foreach" loops for looping through an iterator, Scala has a much more powerful concept of for-expressions. These are similar to list comprehensions in languages such as Haskell, or a combination of list comprehensions and generator expressions in Python. For-expressions using the yield keyword allow a new collection to be generated by iterating over an existing one, returning a new collection of the same type. They are translated by the compiler into a series of map,flatMap and filter calls. Where yield is not used, the code approximates to an imperative-style loop, by translating to foreach. A simple example is:
val s = for (x <- 1 to 25 if x*x > 50) yield 2*x The result of running it is the following vector: Vector(16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50)
Everything is an expression
Unlike C or Java, Scala makes no distinction between statements and expressions. All statements are in fact expressions that evaluate to some value. Functions that would be declared as returning void in Java, and statements like while that logically do not return a value, are in Scala considered to return the type Unit, which is a singleton type, with only one object of that type.
// Java: int hexDigit = x >= 10 ? x + 'A' - 10 : x + '0'; //Scala: val hexDigit = if (x >= 10) x + 'A' - 10 else x + '0'
Type inference
Due to type inference, the type of variables, function return values, and many other expressions can typically be omitted, as the compiler can deduce it. Examples are val x = "foo" (for an immutable, constant variable or immutable object) or var x = 1.5 (for a variable whose value can later be changed). Type inference in Scala is essentially local.
Anonymous functions
In Scala, functions are objects, and a convenient syntax exists for specifying anonymous functions. An example is the expression x => x < 2, which specifies a function with a single parameter, that compares its argument to see if it is less than 2.
An even shorter form of anonymous function uses placeholder variables: For example:
list map { x => sqrt(x) } can be written more concisely as list map { sqrt(_) }
Immutability
Scala enforces a distinction between immutable (unmodifiable, read-only) variables, whose value cannot be changed once assigned, and mutable variables, which can be changed. A similar distinction is made between immutable and mutable objects. The distinction must be made when a variable is declared: Immutable variables are declared with val while mutable variables use var.
Tail recursion
Functional programming languages commonly provide tail call optimization to allow for extensive use of recursion without stack overflow problems. Limitations in Java bytecode complicate tail call optimization on the JVM. In general, a function that calls itself with a tail call can be optimized, but mutually recursive functions cannot. Trampolines have been suggested as a workaround.[24] Trampoline support has been provided by the Scala library with the object scala.util.control.TailCalls since Scala 2.8.0 (released July 14, 2010).[25]
Pattern Matching
pattern matching in Scala is really a lot like Java’s switch/case construct. So in Java, one might write something like this:
public boolean checkPrime(int number) { // checks if a number between 1 and 10 is prime switch (number) { case 1: return true; case 2: return true; case 3: return true; case 5: return true; case 7: return true; default: return false; } }
One of the major limitations of switch/case in Java is that it can only be used on primitives. You can’t use switch/case to test a String, for example (a need which arises more often than one would think). In fact, the most complex type testable within switch/case is the Enum, and even this is just being reduced to its ordinal values under the surface The designers of Scala chose not to include this “feature” in their new language.Instead, they implemented a “new” concept called pattern matching.At a basic level, it allows algorithms which are very similar to the checkPrime(int) example:
def checkPrime(number:Int):Boolean = { number match { case 1 => return true case 2 => return true case 3 => return true case 5 => return true case 7 => return true case _ => return false } }
The biggest difference which likely jumps out at you is the lack of a default statement.Instead, we see the return of Scala’s ubiquitous wildcard character, the underscore. Literally read, this example means: match the value within number; in the case that it is 1, return true; in the case that it is 2, return true; …; for any previously unmatched value, return false. Scala case statements can’t “overflow” into each-other (causing multiple matches) like Java’s can, so even if we weren’t returning values, the algorithm would still be safe.
Traits
Java’s designers recognized the need for multiple typing (e.g. CollegeStudent is both a Student and a Worker), but they wanted to avoid the issues associated with inheriting conflicting method definitions along multiple paths. Their solution was to design the interface mechanism, a feature which allows multiple typing without the complications of multiple inheritance. Scala recognizes that interfaces have their issues. So rather than blinding creating a reimplementation of the same problems found in either Java or C++, Scala takes a new approach. Inspired by a combination of Java’s interfaces and Ruby’s mixins, the designers of Scala have created the trait construct.
trait Book { def title:String def title_=(n:String):Unit def computePrice = title.length * 10 }
Scala’s traits are quite nice in that they can not only define abstract members, but also full method definitions. At the same time, they allow inheriting classes to inherit from more than one trait. They pass on their type information and implementations to their children, as well as enforcing the abstract members. At first glance, this seems like it would be just as bad as straight-up multiple inheritance, but it turns out the problems have been mitigated in some very clever ways. Traits are actually mixins, not true parent classes. Any non-abstract trait members are actually included in the inheriting class, as in physically part of the class.Well, not physically, but you get the picture.It’s as if the compiler performs a cut-and-paste with the non-abstract members and inserts them into the inheriting class.This means that there’s no ambiguity in the inheritance path, meaning no diamond problem. We can rewrite ourCollegeStudent example in Scala without redundancy or fear of paradox:
abstract class Person { def schedule:Schedule } trait Student extends Person { private var classSchedule:Schedule = ... override def schedule = classSchedule def learn() = {...} } trait Worker extends Person { private var workSchedule:Schedule = ... override def schedule = workSchedule def work() = {...} } class CollegeStudent(school:School, company:Company) extends Student with Worker { // ... }
Now if we make a call to the schedule method on an instance of CollegeStudent, the compiler knows that we’re referring to the implementation of schedule in the Worker trait. This is because Worker was mixed in after theStudent trait.
References
<references />