Scala

작성자: http://ospace.tistory.com/,2014.10.25 (moc.lapme|411ecapso#moc.lapme|411ecapso)

Introduction

Hybrid language: Object oriented language(like java) + Functional language(like haskell)
이전 패러다임과 새로운 패러다임의 다리역할(예.scala)

특징

  • 자바 가상 머신에서 동작,그래서 기존 배포와 나란히 동작 가능
  • 자바 라이브러리는 집적 사용 가능하기에 기존 프레임워크와 과거코드를 활용 가능
  • 자바처럼 정적 타입형이어서 좀더 고차원적 결합을 공유 가능
  • 문법도 자바와 비슷하여 쉽고 빠르게 배울 수 있음
  • 객체지향화 함수형 프로그래밍 패러다임을 제공하기에 함수형 프로그래밍 아이디어를적용 가능

자바와 차이

  • Type inference
    • 자바에서는 모든 데이터형마다 타입을 선언해야 한다. 스칼라에서는 타입을 추측한다.
  • Function concepts
    • 자바에 중요한 함수형 개념을 소개. 다른 방법으로 새로운 방식을 사용 가능. 코드 블록, 고차원 함수, 고차원 컬렉션 라이브러을 소개.
  • Immutable variables
    • 자바도 immutable 변수를 허용하고 가끔씩 수정자를 사용. 스칼라는 강제로 명시적으로 변수가 mutable인지 여부를 정해야함
  • Advanced programming constructs
    • 병행 처리를 위한 actor와 루비 스타일의 고차원 함수를 가진 컬랙션과 first-class XML 처리를 소개

Basic

스칼라 타입

scala> println("Hello, surreal world")
Hello, surreal world

scala> 1+1
res0: Int = 2

scala> (1).+(1)
res1: Int = 2

scala> 5+4*3
res2: Int = 17

scala> 5.+(4.*(3))
res3: Int = 17

scala> 5.+((4).*(3))
res4: Int = 17

scala> 4.*(3)
res5: Int = 12

"scala>" 프롬프트 입력이 보임.
정수는 객체, 스칼라의 모든 것이 객체.
문자열 사용 예

scala> "abc".size

scala> "abc"+4
res8: String = abc4

scala> 4+"abc"
res9: String = 4abc

scala> 4+"1.0"
res11: String = 41.0

scala> 4*"abc"
<console>:8: error: overloaded method value * with alternatives:
(x: Double)Double <and>
(x: Float)Float <and>
(x: Long)Long <and>
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to (String)
4*"abc"
^

마지막 예제에서 스칼라가 strongly typed임을 알 수 있음.
스칼라는 컴파일 타임에 자료형을 확인. 콘솔에서는 라인별로 컴파일해 부분별로 실행.

비교와 조건

간단한 비교 예제. C형식의 비교 조건이라 익숙함.

scala> 5<6
res13: Boolean = true

scala> 5<=6
res14: Boolean = true

scala> 5<=2
res16: Boolean = false

scala> 5!=2
res18: Boolean = true

다음은 if문 예제로 변수에 값을 할당하고 비교함. 할당할때에는 별도 타입은 지정하지 않음.
scala> val a=1
a: Int = 1

scala> val b=2
b: Int = 2

scala> if (b<a){
     | println("true")
     | } else {
     | println("false")
     | }
false

스칼라에서 타입을 추측하기에 "val a:Int = 1"처럼 입력할 필요 없음.
val는 immutable이고 var는 아님
루비에서는 0을 true로 C에서는 false로 처리됨. 그리고 양쪽 언어에서 nil은 false로 처리됨.
그러면 스칼라에서는

scala> Nil
res20: scala.collection.immutable.Nil.type = List()

scala> if (0) { println("true") }
<console>:8: error: type mismatch;
 found   : Int(0)
 required: Boolean
              if (0) { println("true") }
                  ^

scala> if (Nil) { println("true") }
<console>:8: error: type mismatch;
 found   : scala.collection.immutable.Nil.type
 required: Boolean
              if (Nil) { println("true") }
                  ^

Nil은 빈 List이기에 테스트할 수 없음. 강하고 엄격한 타입으로 Nill과 숫자는 boolean이 될 수 없음.

반복

파일: for_loop.scala

def forLoop  {
  println("for loop using Java-style iteration")
  for (i <- 0 until args.length) {
    println(args(i))
  }
 }
forLoop

그냥 실행하면 args를 찾을 수 없다고 에러가 발생한다. 파일로 저장하고 콘솔에서 실행해야 한다.
d:\work\scala>scala for_loop.scala a b c d e
for loop using Java-style iteration
a
b
c
d
e

루비 스타일로 다른 형식의 foreach 형식
def rubyStyleForLoop {
  println("for loop using Ruby-style iteration")
   args.foreach{ arg =>
     println(arg)
  }
}
rubyStyleForLoop

이 예제도 파일로 실행해야 한다.

범위와 튜플

First-class로 range를 제공.
by 키워드에 의해서 증감값을 지정 가능
기본 증감값은 1이고 -1은 자동으로 안됨
문자도 범위로 지정 가능

scala> val range = 0 until 10
range: scala.collection.immutable.Range = Range(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> range.start
res1: Int = 0

scala> range.end
res2: Int = 10

scala> range.step
res3: Int = 1

scala> (0 to 10) by 5
res4: scala.collection.immutable.Range = Range(0, 5, 10)

scala> (0 until 10 by 5)
res5: scala.collection.immutable.Range = Range(0, 5)

scala> (10 until 0) by -1
res6: scala.collection.immutable.Range = Range(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

scala> (10 until 0)
res7: scala.collection.immutable.Range = Range()

scala> (0 to 10)
res8: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3, 4, 5, 6, 7
8, 9, 10)

scala> 'a' to 'e'
res9: scala.collection.immutable.NumericRange.Inclusive[Char] = NumericRange(a,
b, c, d, e)

프롤로그 처럼 스칼라도 튜플을 제공. 튜플은 고정된 길이의 객체 집합.
튜플 안에 데이터는 각자 다른 데이터 형을 가질 수 있음. 순수 함수형 언어에서는 자주 객체와 이들 속성을 튜플로 표현.
튜플은 여러 값을 할당할 때에도 리스트보다 자주 사용함.

scala> val person = ("Elvis", "Presley")
person: (String, String) = (Elvis,Presley)

scala> person._1
res10: String = Elvis

scala> person._2
res11: String = Presley

scala> person._3
<console>:9: error: value _3 is not a member of (String, String)
              person._3
                     ^

scala> val (x, y) = (1, 2)
x: Int = 1
y: Int = 2

scala> val (x, y) = (1, 2, 3)
<console>:9: error: constructor cannot be instantiated to expected type;
 found   : (T1, T2)
 required: (Int, Int, Int)
       val (x, y) = (1, 2, 3)
           ^

Classes

속성만 있고 메소드나 생성자가 없는 간단한 클래스 예제

scala> class Person(firstName: String, lastName: String)
defined class Person

scala> val gump = new Person("Forrest", "Gump")
gump: Person = Person@161837e

메소드를 추가해보자.
Compass 클래스로 기본 방향은 북쪽을 가리키며, 콤파스를 90도로 왼쪽 또는 오른쪽으로 토리고, 방향을 적절하게 변경.

class Compass {
    val directions = List("north", "eash", "south", "west")
    var bearing = 0
    print("Initial bearing: ")
    println(direction)
    def direction() = directions(bearing)
    def inform(turnDirection: String) {
      println("Turning " + turnDirection + ". Now bearing " + direction)
    }
    def turnRight() {
      bearing = (bearing + 1) % directions.size
      inform("right")
    }
    def turnLeft() {
      bearing = (bearing + (directions.size - 1)) % directions.size
      inform("left")
    }
}

scala> val myCompass = new Compass
Initial bearing: north
myCompass: Compass = Compass@1e7d0fe

scala> myCompass.turnRight
Turning right. Now bearing eash

scala> myCompass.turnRight
Turning right. Now bearing south

scala> myCompass.turnLeft
Turning left. Now bearing eash

scala> myCompass.turnLeft
Turning left. Now bearing north

scala> myCompass.turnLeft
Turning left. Now bearing west

위의 코드에서는 명시적으로 생성자는 보이지 않음. 처음 코드 실행되는 부분이 기본 생성자 역활.
direction 메소드는 한줄 메소드로 directions의 bearing 인덱스 위치의 값을 반환.

여분 생성자

대체 생성자에 대해서 보자. 2개의 생성자를 가진 Person 클래스 예제.

class Person(firstName: String) {
  println("Outer constructor")
  def this(firstName: String, lastName: String) {
    this(firstName)
    println("Inner constructor")
  }
  def talk() = println("Hi")
}
val bob = new Person("Bob")
val bobTate = new Person("Bob", "Tate")

한개의 매개변수를 가진 생성자와 두번째 생성자는 두 개의 매개변수를 가짐.
scala> val bob = new Person("Bob")
Outer constructor
bob: Person = Person@4d3de8ab

scala> val bobTate = new Person("Bob", "Tate")
Outer constructor
Inner constructor
bobTate: Person = Person@45cfaf37

Class 확장

자바와 루비에서는 클래스 메소드와 인스탄스 메소드가 같은 몸체에 만들 수 있음.
자바에서 클래스 메소드는 static 키워드를 가짐. 루비에서는 def self.class 메소드를 사용.

object TrueRing {
  def rule = println("To rule them all")
}

scala> TrueRing.rule
To rule them all

상속

간단한 상속 예를 보자. Person 클래스로 Employee로 확장하는 예이다.

class Person(val name: String) {
  def talk(message: String) = println(name + " says " + message)
  def id(): String = name
}

class Employee(override val name: String, val number: Int) extends Person(name) {
  override def talk(message: String) {
    println(name + " with number " + number + " says " + message)
  }
  override def id(): String = number.toString
}

scala> val employee = new Employee("Youda", 4)
employee: Employee = Employee@1a4e08e3

scala> employee.talk("Extend or extend not. There is no try.")
Youda with number 4 says Extend or extend not. There is no try.

Employee에서 새로운 속성인 number을 추가. 그리고 talk 메시지를 오버라이딩하고 몇가지 처리를 추가.
Person의 완벽한 매개변수 목록을 지정해야 함.
override 키워드는 생성자와 임의 메소드에 사용되는데 기반 클래스에서 확장을 원할 때 반드시 필요함.

특성

scala에서는 다중 상속, Java에서는 interface, 루비에서는 mixin, 스칼라는 traits을 사용.
다음은 Person에 Nice 특성을 추가한 예제.

class Person(val name: String)

trait Nice {
  def greet() = println("Howdily doodily.")
}

class Character(override val name: String) extends Person(name) with Nice

scala>val flanders = new Character("Ned")
flanders: Character = Character@5e4e90e1

scala> flanders.greet
Howdily doodily.

첫번째 클래스는 Person에서는 name이라는 속성만 가짐. 두 번째 요소로 Nice라는 특성으로 greet라는 단일 메소드를 가짐.

Functional 특성

간단한 함수

병행 구조에 기본이 되는 형태.

scala> def double(x:Int):Int = x * 2
double: (x: Int)Int

scala> double(5)
res0: Int = 10

이는 한줄 메소드 정의. 이를 블럭형태로도 가능.
scala> def double(x:Int):Int = {
  | x * 2
  | }
double: (x: Int)Int

Int는 반환형으로 뒤에 = 는 반드시 필요.

var과 val

스칼라는 자바 가상머신을 기반으로 밀접한 연관을 가짐.
이로 인해 제약점을 가지지만, 또한 20여년간 개발 경험에 대한 이점을 가짐.
병행 처리에 있어서 Mutable 상태는 나쁨. 만약 변수를 immutable로 만든다면 이런 상태가 충돌하는 것을 피함.
자바에서는 final 키워드를 사용한다는 의미이고 스칼라에서는 var대신에 val을 사용한다는 의미.

scala> var mutable = "I am mutable"
mutable: String = I am mutable

scala> mutable = "Touch me, change me..."
mutable: String = Touch me, change me...

scala> val immutable = "I am not mutable"
immutable: String = I am not mutable

scala> immutable = "Can't jtouch this"
<console>:8: error: reassignment to val
       immutable = "Can't jtouch this"
                 ^

var 값은 mutable이고 val 값은 그렇지 않음.
괜찮은 병형성을 원한다면 var 사용을 지양.

Collections

먼저 List를 살펴보자.

scala> List(1, 2, 3)
res3: List[Int] = List(1, 2, 3)

scala> List("one", "two", "three")
res4: List[String] = List(one, two, three)

scala> List("one", "two", 3)
res5: List[Any] = List(one, two, 3)

scala> List("one", "two", 3)(2)
res6: Any = 3

scala> List("one", "two", 3)(3)
java.lang.IndexOutOfBoundsException: 3
at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:51
)
at scala.collection.immutable.List.apply(List.scala:83)
... 33 elided

scala> List("one", "two", 3)(-1)
java.lang.IndexOutOfBoundsException: -1
at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:51
)
at scala.collection.immutable.List.apply(List.scala:83)
... 33 elided

이전 버전에서 음수인 인덱스로 조회하는 경우 첫번째 요소를 반환함. 이는 2.8 이후로 수정되어 IndexOutOfBoundsException이 반환됨.
Nil은 빈 List 값. 이는 기본 빌딩 블럭으로 코드 블럭을 다룰때 사용됨.

다음은 Set을 살펴보자.

scala> val animals = Set("lions", "tigers", "bears")
animals: scala.collection.immutable.Set[String] = Set(lions, tigers, bears)

scala> animals + "armadillos"
res11: scala.collection.immutable.Set[String] = Set(lions, tigers, bears, armadi
llos)

scala> animals - "tigers"
res12: scala.collection.immutable.Set[String] = Set(lions, bears)

scala> animals + Set("armadillos", "raccoons")
<console>:9: error: type mismatch;
 found   : scala.collection.immutable.Set[String]
 required: String
              animals + Set("armadillos", "raccoons")
                           ^

set 연산에서 소멸자가 없음. 각 연산자는 기존 것을 수정하기 보다는 새로 생성함.
기본적으로 set은 immutable.
새로운 단일 요소를 추가(+)나 삭제(-)는 쉬움. 그러나 set는 사용하지 못함.
Set끼리 연산은 ++와 —을 사용해서 합집합과 차집합을 계산.
scala> animals ++ Set("armadillos", "raccoons")
res14: scala.collection.immutable.Set[String] = Set(bears, tigers, lions, armadi
llos, raccoons)

scala> animals -- Set("lions", "bears")
res15: scala.collection.immutable.Set[String] = Set(tigers)

scala> animals  *Set("armadillos", "raccoons", "lions", "bears") *
 *<console>:9: error: value * is not a member of scala.collection.immutable.Set[S
tring]
animals  * Set("armadillos", "raccoons", "lions", "bears")
^

scala> animals & Set("armadillos", "raccoons", "lions", "bears")
res17: scala.collection.immutable.Set[String] = Set(lions, bears)

* 연산을 사용해 교집합을 구할 수 없고 & 연산자를 사용해야 함.
scala> Set(1,2,3) +  Set(3,2,1)
res18: Boolean = true

scala> List(1,2,3) +  List(3,2,1)
res19: Boolean = false

Set는 list와는 다르게 순서에 무관.

다음은 Map을 보자.

scala> val ordinals = Map(0 -> "zero", 1 -> "one", 2 -> "two")
ordinals: scala.collection.immutable.Map[Int,String] = Map(0 -> zero, 1 -> one,
2 -> two)

scala> ordinals(2)
res20: String = two

scala> val map = new HashMap
<console>:7: error: not found: type HashMap
       val map = new HashMap
                     ^

scala> import scala.collection.mutable.HashMap
import scala.collection.mutable.HashMap

scala> val map = new HashMap[Int, String]
map: scala.collection.mutable.HashMap[Int,String] = Map()

scala> map += 4 -> "four"
res21: map.type = Map(4 -> four)

scala> map += 8 -> "eight"
res22: map.type = Map(8 -> eight, 4 -> four)

scala> map
res23: scala.collection.mutable.HashMap[Int,String] = Map(8 -> eight, 4 -> four)

scala> map += "zero" -> 0
<console>:10: error: type mismatch;
 found   : (String, Int)
 required: (Int, String)
              map += "zero" -> 0
                            ^

Map은 키값의 쌍인 저장소. 루비의 Hash와 같음.
키값의 요소들은 -> 연산자를 사용해서 구분.
Mutable 저장소인 HashMap을 가져와서 사용. 이는 Map이 변경가능하다는 의미.
키는 정수형, 값은 문자열로 된 HashMap으로 += 연산자를 이용해서 요소를 추가.
잘못된 타입의 값 입력은 에러.

Any와 Nothing

스칼라 타입 중에 Any와 Nothing이 있음
Any은 루트 클래스로서 모든 객체는 여기서 상속됨
Nothing은 모든 타입의 서브 타입. 즉, 모든 것을 상속함.
nil 개념과는 미묘하게 다름. Null은 Trait로 null은 이 것의 인스탄스로 자비의 null과 비슷한 것으로 빈 값을 의미.
빈 저장소는 Nil. 반대로 Nothing은 모든 것의 서브 타입인 trait이다. 인스탄스는 없고 참조도 불가능.
이는 메소드가 예외를 발생할 때에 값이 없음을 의미하는 Nothing 형을 반환한다.

10622915_10152925408232900_8677896946409713518_n.jpg?oh=3b966163937477b347e75b6acda70ea1&oe=55337623&__gda__=1429139548_3c626087b4908573d779f2c3c5f3305c

출처: http://eyakcn.akara.tk/2012/10/scala-learning-note.html

저장소들와 함수들

다음은 저장소와 관련된 함수들을 살펴보겠다.
고차원 함수(higher-order function)로 루비에서 each를 Io에서는 foreach 같은 것.

먼저 foreach 함수를 보자.

scala> val list = List("frodo", "samwise", "pippin")
list: List[String] = List(frodo, samwise, pippin)

scala> list.foreach(hobbit => println(hobbit))
frodo
samwise
pippin

scala> val hobbits = Set("frodo", "samwise", "pippin")
hobbits: scala.collection.immutable.Set[String] = Set(frodo, samwise, pippin)

scala> hobbits.foreach(hobbit => println(hobbit))
frodo
samwise
pippin

scala> val hobbits = Map("frodo" -> "hobbit", "samwise" -> "hobbit", "pippin" ->
"hobbit")
hobbits: scala.collection.immutable.Map[String,String] = Map(frodo -> hobbit, sa
mwise -> hobbit, pippin -> hobbit)

scala> hobbits.foreach(hobbit => println(hobbit))
(frodo,hobbit)
(samwise,hobbit)
(pippin,hobbit)

scala> hobbits.foreach(hobbit => println(hobbit._1))
frodo
samwise
pippin

scala> hobbits.foreach(hobbit => println(hobbit._2))
hobbit
hobbit
hobbit

다른 List 메소드

다음은 List의 여러가지 메소드를 살펴보자.

scala> list
res30: List[String] = List(frodo, samwise, pippin)

scala> list.isEmpty
res31: Boolean = false

scala> Nil.isEmpty
res32: Boolean = true

scala> list.length
res33: Int = 3

scala> list.size
res34: Int = 3

scala> list.head
res35: String = frodo

scala> list.tail
res36: List[String] = List(samwise, pippin)

scala> list.last
res37: String = pippin

scala> list.init
res38: List[String] = List(frodo, samwise)

scala> list.reverse
res39: List[String] = List(pippin, samwise, frodo)

scala> list.drop(1)
res40: List[String] = List(samwise, pippin)

scala> list
res41: List[String] = List(frodo, samwise, pippin)

scala> list.drop(2)
res42: List[String] = List(pippin)

List의 다른 함수들(higher-order)

scala> val words = List("peg", "al", "bud", "kelly")
words: List[String] = List(peg, al, bud, kelly)

scala> words.count(word => word.size > 2)
res43: Int = 3

scala> words.filter(word => word.size > 2)
res44: List[String] = List(peg, bud, kelly)

scala> words.map(word => word.size)
res45: List[Int] = List(3, 2, 3, 5)

scala> words.forall(word => word.size > 1)
res46: Boolean = true

scala> words.exists(word => word.size > 4)
res47: Boolean = true

scala> words.exists(word => word.size > 5)
res48: Boolean = false

count는 조건 맞는 개수
filter는 조건 맞는 요소를 반환
map은 반환값을 사용해서 새로운 List 저장소 생성
forall은 모든 요소가 조건에 참인 경우 참을 반환
exists는 조건에 만족하는 하나라도 있으면 참을 반환
scala> words.sort((s,t) => s.charAt(0).toLowerCase < t.charAt(0).toLowerCase)
<console>:10: error: value sort is not a member of List[String]
              words.sort((s,t) => s.charAt(0).toLowerCase < t.charAt(0).toLowerC
ase)
                    ^

<console>:10: error: value toLowerCase is not a member of Char
              words.sortWith((s,t) => s.charAt(0).toLowerCase < t.charAt(0).toLo
werCase)
                                                                            ^

scala> words.sortWith((s,t) => s.toLowerCase < t.toLowerCase)
res51: List[String] = List(al, bud, kelly, peg)

scala> words.sortWith((s,t) => s.size < t.size)
res52: List[String] = List(al, peg, bud, kelly)

예제의 sort 함수는 없고 sortWith을 사용해야하고, charAt(0).toLowerCase에서 문자에 대해 toLowerCase도 지원하지 않음.
다음은 foldLeft 메소드로 루비에서 inject 메소드와 비슷.
scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)

scala> val sum +  (0 /: list) { (sum, i) > sum + i }
sum: Int = 6

scala> list.foldLeft(0)((sum, value) => sum + value)
res53: Int = 6

/: 연산자는 초기값 /: 코드블럭 형식이다. 첫번째 인자가 초기화된 값이고 두번째 인자가 리스트 요소가 입력됨.
연산자를 임의 값과 코드블럭으로 호출하고, 코드 블럭은 두개의 인자 sum과 i를 가짐
먼저 /: 은 초기값 0을 가지고 list의 첫번째 요소인 1를 코드 블럭으로 넘김. sum은 0, i는 1이 되고 결과는 0+1로 1이 됨
다음으로 /: 는 앞의 1의 값을 가지고 다시 sum을 계산하기위해 뒤로 가서 sum은 1, i는 다음 요소인 2을 가지고 코드 블록을 호출되고 결과는 3이 됨
마지막으로 /: 는 앞의 결과인 3을 가ㅣㅈ고 다시 sum으로 돌아가서 sum은 3, i는 다음 요소인 3을 가지고 코드 블록을 호출하고 결과는 6이 됨.

다른 버전으로 foldLeft 메소드로 처음 인자가 초기값이고 나머지에 나머지는 앞의 /:와 동일.

XML

스칼라에서는 직접 XML을 다룰 수 있음.

scala> val movies =
| <movies>
| <movie genre="action">Pirates of che Caribbean</movie>
| <movie genre="fairytail">Edward Scissorhands</movie>
| </movies>
movies: scala.xml.Elem =
<movies>
<movie genre="action">Pirates of che Caribbean</movie>
<movie genre="fairytail">Edward Scissorhands</movie>
</movies>

scala> movies.text
res56: String =
"
Pirates of che Caribbean
Edward Scissorhands
"

scala> val movieNodes = movies \ "movie"
movieNodes: scala.xml.NodeSeq +  NodeSeq(<movie genre"action">Pirates of che Ca
ribbean</movie>, <movie genre="fairytail">Edward Scissorhands</movie>)

scala> movieNodes(0)
res57: scala.xml.Node +  <movie genre"action">Pirates of che Caribbean</movie>

scala> movieNodes(0) \ "@genre"
res58: scala.xml.NodeSeq = action

내부 모든 텍스트를 볼려면 text 메소드 호출.
XPath처럼 검색하고 싶다면 // 키워드 대신에 \와 \\을 사용함.
맨 처음 레벨에서 검색하려면 한개의 역슬레쉬(\)을 사용.
찾은 결과 목록에서 맨 처음 요소를 접근하려면 () 연산자 사용.
다시 노드의 속성을 찾을 려면 역슬레쉬와 "@"를 사용해서 검색.

Pattern Matching

다음은 패턴 매칭으로 여거 조건에 따라 실행되는 예제들이다.

scala> def doChore(chore:String): String = chore match {
     | case "clean dishes" => "scrub, dry"
     | case "cook diner" => "chop, sizzle"
     | case _ => "whine, complain"
     | }
doChore: (chore: String)String

scala> println(doChore("clean dishes"))
scrub, dry

scala> println(doChore("mow lawn"))
whine, complain

scala> def fractoral(n:Int): Int = n match {
     | case 0 => 1
     | case x if x > 0 => fractoral(n-1)*n
     | }
fractoral: (n: Int)Int

scala> println(fractoral(3))
6

scala> println(fractoral(0))
1

doChore 메소드는 입력값 chore에 대해서 case 별로 매칭하는 예제.
마지막에 _은 wildcard 의미. 입력 값에 매칭되는 코드블록을 실행한 결과를 리턴.
fractoral 메소드에서 첫번째 case는 0인 경우, 두번째 case는 임의 x에 대해 0보다 큰 값인 경우를 실행.

다음은 스칼라 내에서 정규식을 사용하는 예제이다.
사용법은 간단하다. 문자열 뒤에 .r을 붙이면 Regex 객체가 반환된다.

scala> val reg = """^(F|f)\w*""".r
reg: scala.util.matching.Regex = ^(F|f)\w*

scala> reg.findFirstIn("Fantastic")
res63: Option[String] = Some(Fantastic)

scala> reg.findFirstIn("not Fantastic")
res64: Option[String] = None

scala> val reg = "the".r
reg: scala.util.matching.Regex = the

scala> reg.findAllIn("the way the scissor trim the hair and the shrubs")
res65: scala.util.matching.Regex.MatchIterator = non-empty iterator

findAllIn 메소드는 문자열에 the 단어가 있는 모든 것을 찾음. 필요하다면 반환 값을 foreach를 가지고 순회하면 됨.
이 것을 확장해서 XML과 같이 사용이 가능.

scala> val movies = <movies>
     |   <movie>The Incrediables</movie>
     |   <movie>WALL E</movie>
     |   <short>Jack Jack Attack</short>
     |   <short>Geri's Game</short>
     | </movies>
movies: scala.xml.Elem =
<movies>
  <movie>The Incrediables</movie>
  <movie>WALL E</movie>
  <short>Jack Jack Attack</short>
  <short>Geri's Game</short>
</movies>

scala> (movies \ "_").foreach { movie =>
     | movie match {
     |   case <movie>{ movieName }</movie> => println(movieName)
     |   case <short>{ shortName }</short> => println(shortName + " (short)")
     | }
     | }
The Incrediables
WALL E
Jack Jack Attack (short)
Geri's Game (short)

match에서 movie와 short 패턴을 매칭하여 처리.

병행처리

먼저 Actor와 메시지 처리를 생성.
Actor란 스레드풀과 큐를 가짐.

import scala.actors._
import scala.actors.Actor._

case object Poke
case object Feed

class Kid() extends Actor {
def act() {
loop {
react {
case Poke => {
println("Ow...")
println("Quit it...")
}
case Feed => {
println("Gurgle...")
println("Burp...")
}
}
}
}
}

val bart = new Kid().start
val lisa = new Kid().start
println("Ready to poke and feed...")
bart ! Poke
lisa ! Poke
bart ! Feed
lisa ! Feed

d:\work\scala>scala kids.scala
warning: there was one deprecation warning; re-run with -deprecation for details

one warning found
Ready to poke and feed...
Ow...
Quit it...
Ow...
Quit it...
Gurgle...
Gurgle...
Burp...
Burp...

d:\work\scala>scala kids.scala
warning: there was one deprecation warning; re-run with -deprecation for details

one warning found
Ready to poke and feed...
Ow...
Ow...
Quit it...
Quit it...
Gurgle...
Gurgle...
Burp...
Burp...

Actor로 메시지를 보내면(! 연산자) 메시지 객체는 큐에 위치하고 actor에서 메시지를 읽어서 수행함.
이를 수행하기 위해 actor은 종종 패턴 매칭에 의해서 실행됨.
react 구조가 actor의 메시지를 받아서 패턴 매칭에 의해서 적정할 코드 블럭을 실행(예. Poke, Feed)
실제 실행을 보면 같은 코드인데에서 결과가 다르게 나옴.

현재 Actor은 deprecated되고 변경이 필요함
http://docs.scala-lang.org/overviews/core/actors-migration-guide.html

d:\work\scala>scala -deprecation kids.scala
d:\work\scala\kids.scala:7: warning: trait Actor in package actors is deprecated
: Use the akka.actor package instead. For migration from the scala.actors packag
e refer to the Actors Migration Guide.
class Kid() extends Actor {
                    ^

다른 actor 예로 sizer라는 예제. 이는 웹 페이지 크기를 계산해서 결과를 출력하고 걸린 시간을 계산.

import scala.io._
import scala.actors._
import Actor._

object PageLoader {
def getPageSize(url: String) = Source.fromURL(url, "latin1").mkString.length
}

val urls = List("http://www.amazon.com/",
"http://www.twitter.com/",
"http://www.google.com/",
"http://www.cnn.com/"
)

def timeMethod(method: () + > Unit)  {
val start = System.nanoTime
method()
val end = System.nanoTime
println("Method took " + (end - start)/1000000000.0 + " seconds.")
}

def getPageSizeSequentially() = {
for(url <- urls) {
println("Size for " + url + ": " + PageLoader.getPageSize(url))
}
}

def getPageSizeConcurrently() = {
val caller = self
for (url <- urls) {
actor { caller ! (url, PageLoader.getPageSize(url)) }
}

for (i <- 1 to urls.size) {
receive {
case (url, size) => println("Size for " + url + ": " + size)
}
}
}

println("Sequential run:")
timeMethod { getPageSizeSequentially }
timeMethod { getPageSizeConcurrently }

e:\work\scala>scala sizer.scala
warning: there were three deprecation warnings; re-run with -deprecation for det
ails
one warning found
Sequential run:
http://www.amazon.com/
Size for http://www.amazon.com/: 151640
http://www.twitter.com/
Size for http://www.twitter.com/: 0
http://www.google.com/
Size for http://www.google.com/: 18173
http://www.cnn.com/
Size for http://www.cnn.com/: 121855
Method took 5.21153955 seconds.
Size for http://www.twitter.com/: 0
Size for http://www.google.com/: 18189
Size for http://www.cnn.com/: 121855
Size for http://www.amazon.com/: 151970
Method took 2.023010962 seconds.

중간에 fromURL 메소드에서 그냥 사용하면 UnmappableCharacterException 예외와 MalformedInputException예외가 발생함. 적절한 인코딩 값이 필요함.
위의 결과는 병행 처리의 효과를 보여주고 있음.

결론

장점

  • 병행처리
    • 병렬 프로그래밍에서 진보된 형태
    • actor 모델과 thread pool은 더 개선되고 mutable 상태 없는 설계 가능성은 대단히 큼
    • actor 패러다임은 이해하기 쉽고 학교에서도 공부하기 쉬움
    • 객체 상태를 공유하게 되면 immutable 값을 지향.
    • Immutability는 병행위한 코드 설계 향상에서 매우 중요
  • 기본 자바의 혁신
    • 자바 커뮤니티 사용자 기반.
    • 기본 자바 라이브러리와 필요시 프록시 객체를 통해 사용 가능.
    • 자바 커뮤니티에 새로운 특징들을 제안(ex. code block, mixin)
    • 훌륭한 프로그래밍 언어는 더 복잡한 생각을 몇 줄의 코드로 표현할 수 있어야 하는데, 스칼라가 이를 제공함.
  • 도메인 종속 언어
    • 유연한 구문과 연산자 오버로딩으로 루비 값은 도메인 종속 언어 형태로 만들 수 있음
    • 루비에서는 연산자를 단순 메소드 선언, 대부분의 경우 이를 오버라이드 가능.
    • 선택정 공배, 마침표, 세미콜론은 다른 형태의 구분을 만들 수 있음.
  • XML
    • 통합된 XML 지원.
    • 패턴 매칭으로 쉽게 XML 구조를 사용 가능.
    • XPath 구분 통합으로 복잡한 XML을 단순하고 읽기 쉬운 코드로 만들 수 있음
  • 다리역할
    • 새로운 패러다임이 출현으로 다리가 필요함. 스칼라가 훌륭한 역할.
    • 함수형 언어는 병행 처리에 있어서 중요하고 프로세서 설계을 계선.

단점

  • 정적 타입
    • 정작 타입은 함수형 언어에서는 자연스러운 것이지만 자바(OOP)에서는 악마처럼 취급
    • 때로 컴파일의 요구조건을 만족하기 위해 개발에게 많은 짐을 부과
    • 정적 타입이 바로 그 짐.
  • 구문
    • 스칼라 구문이 좀 실험적이고 익숙하지 않음.
    • 때로 자바 규약을 따름. 이는 Person.new 보다 new Persion을 사용.
    • 별도로 스칼라의 규약을 만듬. 자바에서는 setName(String name)이지만 스칼라에서는 setName(name:String)을 사용.
    • 문제는 스칼라와 자바 사이를 왔다갔다하면서 많은 노력이 필요
  • Mutability
    • 다리역할을 하는 언어를 만든다면 둘 간에 절충이 필요
    • 스칼라에서는 mutability로서 다양한 병행 처리 버그를 야기함.

참조

[1] Bruce Tate, 2010, "Seven Languages in Seven Weeks A Pragmatic Guide to Learning Programming Languages", Pragmatic Programmers. LLC.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License