Paradygmat obiektowy z funkcyjnym

Połączenie najlepszych cech dwóch światów. Twórz elegancką strukturę klas dla osiagnięcia maksimum re-używalności kodu oraz rozszerzalności, implementuj ich zachowanie używając funkcji wyższego rzędu.

Dowiedz się więcej

 

Scala powstała w 2003 roku. Została stworzona przez Martina Oderskyego oraz jego grupę badawczą na Politechnice w Lozannie (EPFL) w Szwajcarii, nieopodal Jeziora Genewskiego i Alp. Scala urosła do postaci dojrzałego, otwarto-źródłowego języka programowania używanego przez setki tysięcy programistów. Jest rozwijana i utrzymywana przez dziesiątki ludzie z całego świata.
Ściągnij API
Spiral
Author.scala
  1. class Author(val firstName: String,
  2. val lastName: String) extends Comparable[Author] {
  3. override def compareTo(that: Author) = {
  4. val lastNameComp = this.lastName compareTo that.lastName
  5. if (lastNameComp != 0) lastNameComp
  6. else this.firstName compareTo that.firstName
  7. }
  8. }
  9. object Author {
  10. def loadAuthorsFromFile(file: java.io.File): List[Author] = ???
  11. }
App.java
  1. import static scala.collection.JavaConversions.asJavaCollection;
  2. public class App {
  3. public List<Author> loadAuthorsFromFile(File file) {
  4. return new ArrayList<Author>(asJavaCollection(
  5. Author.loadAuthorsFromFile(file)));
  6. }
  7. public void sortAuthors(List<Author> authors) {
  8. Collections.sort(authors);
  9. }
  10. public void displaySortedAuthors(File file) {
  11. List<Author> authors = loadAuthorsFromFile(file);
  12. sortAuthors(authors);
  13. for (Author author : authors) {
  14. System.out.println(
  15. author.lastName() + ", " + author.firstName());
  16. }
  17. }
  18. }

Combine Scala and Java seamlessly

Scala classes are ultimately JVM classes. You can create Java objects, call their methods and inherit from Java classes transparently from Scala. Similarly, Java code can reference Scala classes and objects.

In this example, the Scala class Author implements the Java interface Comparable<T> and works with Java Files. The Java code uses a method from the companion object Author, and accesses fields of the Author class. It also uses JavaConversions to convert between Scala collections and Java collections.

Type inference
  1. scala> class Person(val name: String, val age: Int) {
  2. | override def toString = s"$name ($age)"
  3. | }
  4. defined class Person
  5. scala> def underagePeopleNames(persons: List[Person]) = {
  6. | for (person <- persons; if person.age < 18)
  7. | yield person.name
  8. | }
  9. underagePeopleNames: (persons: List[Person])List[String]
  10. scala> def createRandomPeople() = {
  11. | val names = List("Alice", "Bob", "Carol",
  12. | "Dave", "Eve", "Frank")
  13. | for (name <- names) yield {
  14. | val age = (Random.nextGaussian()*8 + 20).toInt
  15. | new Person(name, age)
  16. | }
  17. | }
  18. createRandomPeople: ()List[Person]
  19. scala> val people = createRandomPeople()
  20. people: List[Person] = List(Alice (16), Bob (16), Carol (19), Dave (18), Eve (26), Frank (11))
  21. scala> underagePeopleNames(people)
  22. res1: List[String] = List(Alice, Bob, Frank)

Let the compiler figure out the types for you

The Scala compiler is smart about static types. Most of the time, you need not tell it the types of your variables. Instead, its powerful type inference will figure them out for you.

In this interactive REPL session (Read-Eval-Print-Loop), we define a class and two functions. You can observe that the compiler infers the result types of the functions automatically, as well as all the intermediate values.

Concurrent/Distributed
  1. val x = future { someExpensiveComputation() }
  2. val y = future { someOtherExpensiveComputation() }
  3. val z = for (a <- x; b <- y) yield a*b
  4. for (c <- z) println("Result: " + c)
  5. println("Meanwhile, the main thread goes on!")

Go Concurrent or Distributed with Futures & Promises

In Scala, futures and promises can be used to process data asynchronously, making it easier to parallelize or even distribute your application.

In this example, the future{} construct evaluates its argument asynchronously, and returns a handle to the asynchronous result as a Future[Int]. For-comprehensions can be used to register new callbacks (to post new things to do) when the future is completed, i.e., when the computation is finished. And since all this is executed asynchronously, without blocking, the main program thread can continue doing other work in the meantime.

Traits
  1. abstract class Spacecraft {
  2. def engage(): Unit
  3. }
  4. trait CommandoBridge extends Spacecraft {
  5. def engage(): Unit = {
  6. for (_ <- 1 to 3)
  7. speedUp()
  8. }
  9. def speedUp(): Unit
  10. }
  11. trait PulseEngine extends Spacecraft {
  12. val maxPulse: Int
  13. var currentPulse: Int = 0
  14. def speedUp(): Unit = {
  15. if (currentPulse < maxPulse)
  16. currentPulse += 1
  17. }
  18. }
  19. class StarCruiser extends Spacecraft
  20. with CommandoBridge
  21. with PulseEngine {
  22. val maxPulse = 200
  23. }

Flexibly Combine Interface & Behavior

In Scala, multiple traits can be mixed into a class to combine their interface and their behavior.

Here, a StarCruiser is a Spacecraft with a CommandoBridge that knows how to engage the ship (provided a means to speed up) and a PulseEngine that specifies how to speed up.

Pattern matching
  1. // Define a set of case classes for representing binary trees.
  2. sealed abstract class Tree
  3. case class Node(elem: Int, left: Tree, right: Tree) extends Tree
  4. case object Leaf extends Tree
  5. // Return the in-order traversal sequence of a given tree.
  6. def inOrder(t: Tree): List[Int] = t match {
  7. case Node(e, l, r) => inOrder(l) ::: List(e) ::: inOrder(r)
  8. case Leaf => List()
  9. }

Switch on the structure of your data

In Scala, case classes are used to represent structural data types. They implicitly equip the class with meaningful toString, equals and hashCode methods, as well as the ability to be deconstructed with pattern matching.

In this example, we define a small set of case classes that represent binary trees of integers (the generic version is omitted for simplicity here). In inOrder, the match construct chooses the right branch, depending on the type of t, and at the same time deconstructs the arguments of a Node.

Go Functional with Higher-Order Functions

In Scala, functions are values, and can be defined as anonymous functions with a concise syntax.

Scala
  1. val people: Array[Person]
  2. // Partition `people` into two arrays `minors` and `adults`.
  3. // Use the higher-order function `(_.age < 18)` as a predicate for partitioning.
  4. val (minors, adults) = people partition (_.age < 18)
Java
  1. List<Person> people;
  2. List<Person> minors = new ArrayList<Person>(people.size());
  3. List<Person> adults = new ArrayList<Person>(people.size());
  4. for (Person person : people) {
  5. if (person.getAge() < 18)
  6. minors.add(person);
  7. else
  8. adults.add(person);
  9. }

In the Scala example on the left, the partition method, available on all collection types (including Array), returns two new collections of the same type. Elements from the original collection are partitioned according to a predicate, which is given as a lambda, i.e., an anonymous function. The _ stands for the parameter to the lambda, i.e., the element that is being tested. This particular lambda can also be written as (x => x.age < 18).

The same program is implemented in Java on the right.