Введение в


Что такое Scala?


  • SCAlable LAnguage
  • появился в 2003
  • компирируется в byte code JVM
  • оказали влияние: Java, Pizza, Haskell, Erlang, Standard ML, Objective Caml, Smalltalk, Scheme, Algol68, Lisp
  • мультипарадигменный
  • строгая статическая типизация*
  • type inference
  • true OOP
  • sbt
  • REPL


*type erasure


Привет, Мир!


object HelloWorld {
  def main(args: Array[String]) = {
    println("Привет, МИР!")
  }
} 


$ scalac hello.scala
$ scala HelloWorld 
Привет, МИР!


Типы

Byte 8 bit signed value. Range from -128 to 127
Short 16 bit signed value. Range -32768 to 32767
Int 32 bit signed value. Range -2147483648 to 2147483647
Long 64 bit signed value. -9223372036854775808 to 9223372036854775807
Float 32 bit IEEE 754 single-precision float
Double 64 bit IEEE 754 double-precision float
Char 16 bit unsigned Unicode character. Range from U+0000 to U+FFFF
String A sequence of Chars
Boolean Either the literal true or the literal false
Unit Corresponds to no value
Null null or empty reference
Nothing The subtype of every other type; includes no values
Any The supertype of any type; any object is of type Any
AnyRef The supertype of any reference type

REPL


$ scala
Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> object HelloWorld {
| def main(args: Array[String]) = {
| println("Привет, МИР!")
| }
| }
defined module HelloWorld

scala> HelloWorld.main(Array())
Привет, МИР!


Приведение типов

scala> val a:Int = 1
a: Int = 1

scala> val a = 2
a: Int = 2

scala> val a = 2.0
a: Double = 2.0

scala> val a = 2/3
a: Int = 0

scala> val a = 2.0/3
a: Double = 0.6666666666666666

val vs var


scala> var a = 1
a: Int = 1

scala> a = 2
a: Int = 2

scala> val b = 1
b: Int = 1

scala> b = 2
:8: error: reassignment to val
       b = 2
         ^ 

всё - объекты


scala> val b = 1
b: Int = 1

scala> b.toString
res0: String = 1

scala> val s = "1"
s: String = 1

scala> a.toInt
res1: Int = 2
StringOps
scala> val str = "hello"
scala> str.
+ asInstanceOf charAt codePointAt codePointBefore codePointCount compareTo compareToIgnoreCase
concat contains contentEquals endsWith equalsIgnoreCase getBytes getChars indexOf
intern isEmpty isInstanceOf lastIndexOf length matches offsetByCodePoints regionMatches
replace replaceAll replaceFirst split startsWith subSequence substring toCharArray

Функции


scala> def sqr(x: Int): Int = {
x * x
}
sqr: (x: Int)Int

scala> sqr(3)
res0: Int = 9

scala> def sqr(x: Int) = { 
x * x
}
sqr: (x: Int)Int

scala> def sqr(x: Int) = x*x
sqr: (x: Int)Int

scala> def foo(x: Int) {
println("x = " + x)
}
foo: (x: Int)Unit

Функции высшего порядка


// lambda
scala> val increment = (x: Int) => x + 1
increment: Int => Int = <function1>

scala> increment(9)
res0: Int = 10

// closure
scala> val base = 10
base: Int = 10

scala> val closure = (x: Int) => x + base
closure: Int => Int = <function1>

scala> closure(4)
res1: Int = 14






 Немного математики


def add(a: Double, b: Double) = a + b
add: (a: Double, b: Double)Double

def multiply(a: Double, b: Double) = a * b
multiply: (a: Double, b: Double)Double

def pifagor(a: Double, b: Double) = Math.sqrt(a*a + b*b)
pifagor: (a: Double, b: Double)Double
 
def math(a: Double, b: Double, op: (Double, Double) => Double):Double = op(a, b)
math: (a: Double, b: Double, op: (Double, Double) => Double)Double

scala> math(1,2,add)
res0: Double = 3.0

scala> math(1,2,multiply)
res1: Double = 2.0

scala> math(1,2,pifagor)
res2: Double = 2.23606797749979




Curring

def math(a: Double, b: Double)(op: (Double, Double) => Double): Double = op(a,b)
math: (a: Double, b: Double)(op: (Double, Double) => Double)Double

scala> math(1,2)(add)
res0: Double = 3.0

scala> def math(op: (Double, Double) => Double)(a: Double, b: Double) = op(a,b)
math: (op: (Double, Double) => Double)(a: Double, b: Double)Double

scala> val m = math(multiply)_
m: (Double, Double) => Double = <function2>

scala> m(3,3)
res1: Double = 9.0

scala> val hip = math(pifagor)_
hip: (Double, Double) => Double = <function2>

scala> hip(3,4)
res2: Double = 5.0

//
scala> val hip = math{(a: Double, b: Double) => Math.sqrt(a*a + b*b)}_
hip: (Double, Double) => Double = <function2>

scala> hip(3,4)
res3: Double = 5.0

Циклы

scala> var a = 3
a: Int = 3

scala> while(a > 0) { println(a); a -= 1 }
3
2
1

scala> for (i <- 1 to 3) { println(i) }
1
2
3

scala> val a = for (i <- 1 to 3) yield i
a: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3)

Что под капотом for?


scala> 1 to 3
res12: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)

scala> 1 to 3 map (i => i)

res16: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3)

scala> (1 to 3).map(i => i)
res21: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3)

Условия

scala> val str = "hello"

scala> def notEmpty = if(str.isEmpty) false else true
isEmpty: Boolean = false

scala> def notEmpty = str.isEmpty
isEmpty: Boolean = false

scala> val v = if(str.isEmpty) 1 else "2"
v: Any = 1

scala> val v: Int = if(str.isEmpty) 1 else "2"
<console>:7: error: type mismatch;
found : String("2")
required: Int
val v: Int = if(str.isEmpty) 1 else "2"

Классы


class Foo(val bar: String, baz: String)
defined class Foo

val foo = new Foo("one", "two")
foo: Foo = Foo@4356367f

scala> foo.
asInstanceOf bar isInstanceOf toString


scala> class Foo(val bar: String, baz: String) {
| override def toString = s"Foo(" + bar + ", "+ baz+")"
| }
defined class Foo

scala> val foo = new Foo("one", "two")
foo: Foo = Foo(one, two)

Объекты

scala> :paste
// Entering paste mode (ctrl-D to finish)

object Foo {
def apply(bar: String, baz: String) = new Foo(bar, baz)
}

class Foo(val bar: String, baz: String) {
override def toString = s"Foo(" + bar + ", "+ baz+")"
}
<ctrl>+<d>

// Exiting paste mode, now interpreting.

defined module Foo
defined class Foo

scala> Foo("one", "two")
res0: Foo = Foo(one, two)

Еще объекты


scala> :paste
// Entering paste mode (ctrl-D to finish)

object Foo {
private var baz: Int = 0

def apply(bar: String) = new Foo(bar)

private def getNextBaz:Int = { baz += 1; baz }
}

class Foo(val bar: String) {
private val baz: Int = Foo.getNextBaz
override def toString = s"Foo(" + bar + ", "+ baz+")"
}

// Exiting paste mode, now interpreting.

defined module Foo
defined class Foo

scala> Foo("one")
res0: Foo = Foo(one, 1)

scala> Foo("one")
res1: Foo = Foo(one, 2)


Case class

scala> case class Foo(bar: String)
defined class Foo

scala> Foo("one")
res0: Foo = Foo(one)

scala> res0.
asInstanceOf bar canEqual copy isInstanceOf productArity productElement productIterator productPrefix
toString

 Наследование

abstract class Animal 

trait Legs {
def walk = ???
}

trait Flippers{
def swim = ???
}

class Cat extends Animal with Legs
class Dog extends Animal with Legs
class Whale extends Animal with Flippers

trait Undead
class Zombie extends Undead with Legs

class Monster extends Flippers with Undead


Type check


scala> val lassie = new Dog
lassie: Dog = Dog@4a55ac8e

scala> val frank = new Monster
frank: Monster = Monster@5c47f49a

scala> def breaths(creature: Animal) = true
breaths: (creature: Animal)Boolean

scala> breaths(lassie)
res0: Boolean = true

scala> breaths(frank)
<console>:14: error: type mismatch;
found : Monster
required: Animal
breaths(frank)







scala> val mobyDick = new Whale
mobyDick: Whale = Whale@25963401

scala> val lassie = new Dog
lassie: Dog = Dog@4acaa278

scala> def canSwim(creature: Flippers) = true
canSwim: (creature: Flippers)Boolean


scala> canSwim(lassie)
<console>:14: error: type mismatch;
found : Dog
required: Flippers
canSwim(lassie)
^

scala> canSwim(mobyDick)
res0: Boolean = true

val vs lazy val vs def


class Foo {
val a = { println("a"); 1 }
lazy val b = { println("b"); 2 }
def c = { println("c"); 3 }
}

scala> val foo = new Foo
a
foo: Foo = Foo@22cc1ba7

scala> foo.b
b
res0: Int = 2

scala> foo.b
res1: Int = 2

scala> foo.c
c
res2: Int = 3

scala> foo.c
c
res3: Int = 3

 Коллекции


Idexed Sequenses


val l = List(1,2,3)
l: List[Int] = List(1, 2, 3)

scala> l(1)
res0: Int = 2

scala> val v = Vector(1,2,3)
v: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)

scala> v(2)
res1: Int = 3

scala> v.apply(2)
res2: Int = 3

val a = Array("one","two")
a: Array[String] = Array(one, two)

scala> a(0)
res3: String = one

Maps


scala> val capitals = Map("Russia" -> "Moscow", "France" -> "Paris")
capitals: scala.collection.immutable.Map[String,String] = Map(Russia -> Moscow, France -> Paris)

scala> capitals("Russia")
res0: String = Moscow

scala> capitals("russia")
java.util.NoSuchElementException: key not found: russia
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:58)
at scala.collection.MapLike$class.apply(MapLike.scala:141)
at scala.collection.AbstractMap.apply(Map.scala:58)
at .<init>(<console>:9)
at .<clinit>(<console>)
at .<init>(<console>:7)
at .<clinit>(<console>)
at $print(<console>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:734)
at scala.tools.nsc.interpreter.IMain$Request.loadAndRun(IMain.scala:983)
at scala.tools.nsc.interpreter.IMain.loadAndRunReq$1(IMain.scala:573)
at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:604)
at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:568)
at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:756)
at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:801)
at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:713)
at scala.tools.nsc.interpreter.ILoop.processLine$1(ILoop.scala:577)
at scala.tools.nsc.interpreter.ILoop.innerLoop$1(ILoop.scala:584)
at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:587)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:878)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:833)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:833)
at scala.tools.nsc.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:135)
at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:833)
at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83)
at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

scala> capitals.get("russia")
res1: Option[String] = None

scala> capitals.get("Russia")
res2: Option[String] = Some(Moscow)

scala> capitals.getOrElse("russia", "not found")
res14: String = not found

scala> capitals.getOrElse("Russia", "not found")
res15: String = Moscow

Pattern Matching

val capitals = Map("Russia" -> "Moscow", "France" -> "Paris")
capitals: scala.collection.immutable.Map[String,String] = Map(Russia -> Moscow, France -> Paris)


def showCapital(country: String):String = {
capitals.get(country) match {
case Some(city: String) => s"The capital of $country is $city"
case None => s"I don't know such country: $country"
}
}
showCapital: (country: String)String

scala> showCapital("Russia")
res0: String = The capital of Russia is Moscow

scala> showCapital("Great Britain")
res1: String = I don't know such country: Great Britain


Tuples

val a = ("one", 2, Foo)
a: (String, Int, Foo.type) = (one,2,Foo$@8c96c01)

scala> val (b,c,d) = a
b: String = one
c: Int = 2
d: Foo.type = Foo$@8c96c01

scala> val (b,c,_) = a
b: String = one
c: Int = 2





 Создаем тест

// build.sbt
name := "unit test"

libraryDependencies ++= Seq(
"org.specs2" %% "specs2" % "2.3.10" % "test"
)
// src/main/scala/MyMath.scala 
package util

class MyMath {
def add(a: Double, b: Double) = a + b
}
// src/test/scala/MyMathSpec.scala
import org.specs2.mutable._
import utils.MyMath

class MyMathSpec extends Specification {
"method add" should {
"return addition result" in {
val math = new MyMath()
math.add(1, 2) should equalTo(3)
}
}
}

Запускаем тест


$ sbt
[info] Set current project to unit test (in build file:/Users/heoh/scala/unittest/)
> test
[info] MyMathSpec
[info]
[info] method add should
[info] + return addition result
[info]
[info] Total for specification MyMathSpec
[info] Finished in 31 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 3 s, completed 06.04.2014 13:24:44
>

Scala Check

// build.sbt
name := "unit test"

libraryDependencies ++= Seq(
"org.specs2" %% "specs2" % "2.3.10" % "test",
"org.scalatest" %% "scalatest" % "1.9.1" % "test",
"org.scalacheck" %% "scalacheck" % "1.11.3" % "test"

)
// src/test/scala/CheckMyMath.scala
import org.scalacheck._
import utils.MyMath

object CheckMyMath extends Properties("Int") {

import Prop.forAll

val a = Gen.choose(0,1000)
val b = Gen.choose(0,1000)

val math = new MyMath

property("addition") = forAll(a,b) { (a: Int, b: Int) =>
math.add(a, b) == a+b
}

}

Rerun tests

$ sbt test
[info] Set current project to unit test (in build file:/Users/heoh/scala/unittest/)
[info] + Int.addition: OK, passed 100 tests.
[info] MyMathSpec
[info]
[info] method add should
[info] + return addition result
[info]
[info] Total for specification MyMathSpec
[info] Finished in 41 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: Total 2, Failed 0, Errors 0, Passed 2
[success] Total time: 3 s, completed 06.04.2014 13:54:51

Acceptance tests
// src/test/scala/MyMathATSpec.scala
import org.specs2._
import utils.MyMath

class MyMathATSpec extends Specification {
def is = s2"""

Эта спецификация должна проверять корректность класса MyMath

Метод add должен складывать 2 числа типа double
при сложении 2+2 мы должны получать 4 $e2
"""
val math = new MyMath
def e2 = math.add(2, 2) must equalTo(4)
}
$ sbt "testOnly MyMathATSpec"
[info] Set current project to unit test (in build file:/Users/heoh/scala/unittest/)
[info] Compiling 1 Scala source to /Users/heoh/scala/unittest/target/scala-2.10/test-classes...
[info] MyMathATSpec
[info]
[info] Эта спецификация должна проверять корректность класса MyMath
[info]
[info] Метод add должен складывать 2 числа типа double
[info] + при сложении 2+2 мы должны получать 4
[info]
[info] Total for specification MyMathATSpec
[info] Finished in 29 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 11 s, completed 06.04.2014 14:13:45

Работа с XML

scala> val xhtmlResponse = 
<html>
<header>
<title>Some Title</title>
</header>
<body>
<h1>header</h1>
<p>Lorem ipsum</p>
</body>
</html>
xhtmlResponse: scala.xml.Elem =
<html>
<header>
<title>Some Title</title>
</header>
<body>
<h1>header</h1>
<p>Lorem ipsum</p>
</body>
</html>

(xhtmlResponse \\ "header").text
res0: String =
"
Some Title
"

scala> xhtmlResponse \ "body"
res1: scala.xml.NodeSeq =
NodeSeq(<body>
<h1>header</h1>
<p>Lorem ipsum</p>
</body>)


Frameworks


http://www.playframework.com/

http://liftweb.net/

http://spray.io/

http://akka.io/


Libraries

http://slick.typesafe.com/


IDEs

http://www.jetbrains.com/idea/
http://typesafe.com/activator
http://scala-ide.org/
https://github.com/sublimescala/sublime-ensime
http://www.emacswiki.org/emacs/ScalaMode

Ссылки


  1. https://www.coursera.org/course/reactive
  2. http://danielwestheide.com/scala/neophytes.html
  3. https://github.com/anton-k/ru-neophyte-guide-to-scala
  4. https://github.com/garu/scala-for-perl5-programmers
  5. http://twitter.github.io/scala_school/ru/

Введение в scala

By Sergey Lobin

Введение в scala

  • 385