관리 메뉴

nkdk의 세상

Scala 기초 부터 중급 :) #1 간단한 소개, simple펑션프로그래밍, case classes 본문

My Programing/Scala&Lift

Scala 기초 부터 중급 :) #1 간단한 소개, simple펑션프로그래밍, case classes

nkdk 2010. 7. 3. 21:36
지금부터 스칼라에 기초 부터 중급까지 실제 사용하는 함수를 정리 하려 한다. 실제 전체적 구조에 대해서도 다루어 보려 합니다.

일단 이 글을 쓰게 된 이유는 리프트 부터 할려니까 한계가 보이더군요. 그래서 일단은 스칼라 부터 한 후에 lift를 가는 것이 맞다고 생각하게 되었습니다. 내용 상으로는 어렵거나 수학적 개념이 필요한 부분에 대해서는 과감히 빼겠습니다. 일단은 전체적인 부분에 대해 다루도록 하겠습니다.

스칼라라는 것은 무엇인가 일단 JVM에서 돌아가는 훌륭한 언어 입니다.
어느 정도로 훌륭하냐 하면 그루브를 만든 사람이 내가 스칼라라는 것이 있는 줄 알았다면 그루브를 안 만들었다고 이야기 할 정도이니 말입니다.

일단 기본적인 설치 및 설명에 대해서는 스칼라기본설치를 참고 해 주세요.
scala-2.7.7.final.zip (md5) <- 전 일단 이 버전으로 하려 합니다. 현재 최신 버전입니다.

일단 기본적인 경로를 잡으셨고 실행이 가능한 상태라면.. 바로 시작해 봅시다.

가장 간단한 헬로우 월드!

object HelloWorld {
    def main(args : Array[String]) : Unit = {
        System.out.println("Hello, Scala!");
    }
}

컴파일 : scalac HelloWorld.scala
실행 : scala HelloWorld

컴파일 하개 되면 2개의 파일이 생성됩니다 . :) 이 부분에 대해서는 생략 :) 실제 움직이게 하는 건 이 클래스 파일이 되겠죠?

일단 어그레시브한 프로그래밍을 위해서 콘솔에서 간단 간단 하게 확인이 가능한 방법을 보겠습니다.

scala 콘솔에서
object HelloWorld {
    def main(args : Array[String]) : Unit = {
        System.out.println("Hello, Scala!");
    }
}

이렇게 넣으시면
defined module HelloWorld
라고 나오는데 이 상태에서
HelloWorld.main(null) 이라고 치시면 간단히 실행이 가능합니다.

object HelloWorld {
def main(args: Array[String]) {
println("Hello, world! " + args.toList)
}
}
HelloWorld.main(args)

이렇게 하면 인수도 넣을 수 있네요.

-------------------------------------- 간단한 소스 하나 소개 합니다.
class 부터 어떤 식으로 값을 주고 받는지 볼 수 있겠네요.
-------------------------------------------

object Persons {

/** A list of persons. To create a list, we use Predef.List
* which takes a variable number of arguments and constructs
* a list out of them.
*/
val persons = List(
new Person("Bob", 17),
new Person("John", 40),
new Person("Richard", 68)
)

/** A Person class. 'val' constructor parameters become
* public members of the class.
*/
class Person(val name: String, val age: Int)

/** Return an iterator over persons that are older than 20.
*/
def olderThan20(xs: Seq[Person]): Iterator[String] =
olderThan20(xs.elements)

/** Return an iterator over persons older than 20, given
* an iterator over persons.
*/
def olderThan20(xs: Iterator[Person]): Iterator[String] = {

// The first expression is called a 'generator' and makes
// 'p' take values from 'xs'. The second expression is
// called a 'filter' and it is a boolean expression which
// selects only persons older than 20. There can be more than
// one generator and filter. The 'yield' expression is evaluated
// for each 'p' which satisfies the filters and used to assemble
// the resulting iterator
for (p <- xs if p.age > 20) yield p.name
}
}


/** Some functions over lists of numbers which demonstrate
* the use of for comprehensions.
*/
object Numeric {

/** Return the divisors of n. */
def divisors(n: Int): List[Int] =
for (i <- List.range(1, n+1) if n % i == 0) yield i

/** Is 'n' a prime number? */
def isPrime(n: Int) = divisors(n).length == 2

/** Return pairs of numbers whose sum is prime. */
def findNums(n: Int): Iterable[(Int, Int)] = {

// a for comprehension using two generators
for (i <- 1 until n;
j <- 1 until (i-1);
if isPrime(i + j)) yield (i, j)
}

/** Return the sum of the elements of 'xs'. */
def sum(xs: List[Double]): Double =
xs.foldLeft(0.0) { (x, y) => x + y }

/** Return the sum of pairwise product of the two lists. */
def scalProd(xs: List[Double], ys: List[Double]) =
sum(for((x, y) <- xs zip ys) yield x * y);

/** Remove duplicate elements in 'xs'. */
def removeDuplicates[A](xs: List[A]): List[A] =
if (xs.isEmpty)
xs
else
xs.head :: removeDuplicates(for (x <- xs.tail if x != xs.head) yield x)
}


/** The main class, the entry point of this program.
*/
object Fors {

def main(args: Array[String]) {
// import all members of object 'persons' in the current scope
import Persons._

print("Persons over 20:")
olderThan20(persons) foreach { x => print(" " + x) }
println

import Numeric._

println("divisors(34) = " + divisors(34))

print("findNums(15) =")
findNums(15) foreach { x => print(" " + x) }
println

val xs = List(3.5, 5.0, 4.5)
println("average(" + xs + ") = " + sum(xs) / xs.length)

val ys = List(2.0, 1.0, 3.0)
println("scalProd(" + xs + ", " + ys +") = " + scalProd(xs, ys))
}

}
역시 콘솔창에서 실행 가능하고요.
scala> Fors.main(null) 이렇게 실행하면

Persons over 20: John Richard
divisors(34) = List(1, 2, 17, 34)
findNums(15) = (4,1) (5,2) (6,1) (7,4) (8,3) (8,5) (9,2) (9,4) (10,1) (10,3) (10
,7) (11,2) (11,6) (11,8) (12,1) (12,5) (12,7) (13,4) (13,6) (13,10) (14,3) (14,5
) (14,9)
average(List(3.5, 5.0, 4.5)) = 4.333333333333333
scalProd(List(3.5, 5.0, 4.5), List(2.0, 1.0, 3.0)) = 25.5

이런 결과가 나옵니다. 여기서 엿 볼 수 있는 것이 각 3개의 Object가 있고 거기서 메인이 되는 것이 한개가 있어서 각 오브젝트를 불러서 사용 하는 방법들이라던지, 여기서 참 재미있는게 하나 있는데
class Person(val name: String, val age: Int) 이 부분인데요 바로 바로 값이 적용되서 들어 갑니다. 꼭 예전에 bean 썻던것처럼 get, set없이 그냥 쏙쏙 들어가네요 :)

소스를 보면서 분석 해 보시기 바랍니다.

그럼 기초부터 설명 들어갑니다.

일단 간단히 콘솔창에서 놀아 볼까요
scala> 1 + 1
res18: Int = 2

scala> val x = "hellO"
x: java.lang.String = hellO

scala> print(x)
hellO
scala> val xl = x.length
xl: Int = 5

scala> res18 * 10
res21: Int = 20

이렇게 계속 뭔가를 가지고 있네요 :) 재미있습니다.
그리고 저 res라는 걸 보니까 아무래도 뭔가 함수를 날렸을 때의 갯수로 해서 하나씩 증가 하는 것 같습니다. 물론 변수로 보관이 되서 나중에라도 끄집어 낼 수 쓸 수 있네요. :) 이런 좋은 것이 :)

scala> import java.util._
import java.util._

scala> val d = new Date
d: java.util.Date = Sat Jul 03 20:28:05 JST 2010

이런 것도 됩니다. 즉 java.util.Date 라는 클래스를 가져와서 바로 쓸 수 있게끔 하는 것도 있고..

아 그리고 콘솔에서 작업을 할 때 보면,

defined module HelloScala
defined class HelloScala

이렇게 나오는게 있는데 말 그대로 입니다 :) 모듈 혹은 class를 정의 했을 때 나오는 거죠. 아마 그 이외에 더 있지 않을까 생각 되는데 일단 현재 하고 잇는 중에는 이 정도만 나오네요.
뭔가 더 재미있는게 슬슬 나올 것 같지 않나요?

그리고 컴파일러 중에 fsc라는 컴파일러가 있는데 아무래도 뜻이 fast scala compiler가 아닐까 하는 생가깅 든다. 이건 정말 빠른 컴파일러라고 하는데 scalac보다 빠르도가 합니다. 그러나 역시 중 대형 규모가 되기 시작하면 ant혹은 maven으로 컴파일러를 관리해야 하니까 그 부분에 대해서도 공부가 필요하다 생각되네요 :)

넘버 관련 형 을 한번 봐 볼까요?
scala> 1.+(2)
res27: Double = 3.0
그냥 바로 더블형으로 나와 주네요 :) 1. 이게 포인트입니다.

이번에는 시간 관련으로 해서 한번 봐 보죠 :)
object Timer {
  def oncePerSecond(callback: () => Unit) {
    while (true) { callback(); Thread sleep 1000 }
  }
  def timeFlies() {
    println("second 1...")
  }

  def main(args: Array[String]) {
    oncePerSecond(timeFlies)
  }
}

이렇게 해 주면 second 1...이게 1초마다 나옵니다.
여기서 timeFlies 이걸 뺄수도 있는데요 다음과 같습니다 :)

object TimerAnonymous {
  def oncePerSecond(callback: () => Unit) {
    while (true) { callback(); Thread sleep 1000 }
  }
  def main(args: Array[String]) {
    oncePerSecond(() =>
      println("second 1..."))
  }
}

역시나 저는 이게 좀 더 보기 좋네요. 이렇게 쓰는 방식을 가지고 무명 펑션(Anonymous functions) 방식이라고 하는 것 같습니다.

이번에는 복합(complex)클래스를 하나 만들어서 여러개의 값을 가지고 있는 클래스를 하나 만들어 볼까 합니다. 이건 예전에 get, set방식인 bean방식 같네요 :) 물론 더욱 더 편해졌고요 :)

scala> class Complex(str1: String, imaginary: Double) {
     | def st() = str1
     | def im() = imaginary
     | }
defined class Complex

scala> object ComplexNumbers {
     | def main(args: Array[String]) {
     | val c = new Complex("firstData", 3.4)
     | println("String part: " + c.st())
     | println("imaginary part: " + c.im())
     | }
     | }
defined module ComplexNumbers

scala> ComplexNumbers.main(null)
String part: firstData
imaginary part: 3.4

이런 결과가 나오게 됩니다 :) 이해는 바로 되셨을꺼라 생각되네요 :)

그리고 다음으로는 abstract 이건 뭐 :) 설명 없으셔도 알 듯 이전에 자바 하셨던 분이라면
abstract class Term
case class Var(name: String) extends Term
case class Fun(arg: String, body: Term) extends Term
case class App(f: Term, v: Term) extends Term
이런 식으로 사용하시면 됩니다 :) 그리고 이걸 어디에 이용하느냐 바로 case classes :)에 사용하려 합니다.
소스 쭉 나갑니다 메모 메모! :)
scala> val x = Var("x")
x: Var = Var(x)
scala> Console.println(x.name)
x
scala> val x1 = Var("x")
x1: Var = Var(x)
scala> val x2 = Var("x")
x2: Var = Var(x)
scala> val y1 = Var("y")
y1: Var = Var(y)
scala> println("" + x1 + " == " + x2 + " => " + (x1 == x2))
Var(x) == Var(x) => true
scala> println("" + x1 + " == " + y1 + " => " + (x1 == y1))
Var(x) == Var(y) => false

이런식으로 결과 값이 나오게 됩니다. 재미있지 않나요? 특히 저는 x.name 이 부분에 감동을 :)
이걸 응용해서 하나 더 짜보면,
scala> object TermTest extends Application {
     |   def printTerm(term: Term) {
     |     term match {
     |       case Var(n) =>
     |         print(n)
     |       case Fun(x, b) =>
     |         print("^" + x + ".")
     |         printTerm(b)
     |       case App(f, v) =>
     |         Console.print("(")
     |         printTerm(f)
     |         print(" ")
     |         printTerm(v)
     |         print(")")
     |     }
     |   }
     |   def isIdentityFun(term: Term): Boolean = term match {
     |     case Fun(x, Var(y)) if x == y => true
     |     case _ => false
     |   }
     |   val id = Fun("x", Var("x"))
     |   val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
     |   printTerm(t)
     |   println
     |   println(isIdentityFun(id))
     |   println(isIdentityFun(t))
     | }
defined module TermTest

scala> TermTest
^x.^y.(x y)
true
false
res14: TermTest.type = TermTest$@4b7b1

아주 꼬리에 꼬리를 무는 모듈입니다. :) 너무 너무 좋네요 :)

자 그럼 마지막으로 case classes의 최종본을 볼까요 :)
scala> abstract class Tree
defined class Tree
scala> case class Sum(l: Tree, r: Tree) extends Tree
defined class Sum
scala> case class Var(n: String) extends Tree
defined class Var
scala> case class Const(v: Int) extends Tree
defined class Const

scala> type Environment = String => Int
defined type alias Environment

scala> def eval(t: Tree, env: Environment): Int = t match {
     | case Sum(l, r) => eval(l, env) + eval(r, env)
     | case Var(n) => env(n)
     | case Const(v) => v
     | }
eval: (Tree,(String) => Int)Int
scala> def derive(t: Tree, v: String): Tree = t match {
     | case Sum(l, r) => Sum(derive(l, v), derive(r, v))
     | case Var(n) if (v == n) => Const(1)
     | case _ => Const(0)
     | }
derive: (Tree,String)Tree
scala> def main(args: Array[String]) {
     | val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y")))
     | val env: Environment = { case "x" => 5 case "y" => 7 }
     | println("Expression: " + exp)
     | println("Evaluation with x=5, y=7: " + eval(exp, env))
     | println("Derivative relative to x:\n " + derive(exp, "x"))
     | println("Derivative relative to y:\n " + derive(exp, "y"))
     | }
main: (Array[String])Unit
scala> main(null)
Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y)))
Evaluation with x=5, y=7: 24
Derivative relative to x:
 Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0)))
Derivative relative to y:
 Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))

이 정도가 되겠습니다 :)

일단 글이 길어 졌네요 다음편에는 if, for, foreach, switch 와 같은 뭔가 흐름을 제어하는 것들에 대해서 한번 알아 볼까 합니다. :)