'스칼라'에 해당하는 글 31건

스칼라에서의 for - comprehensions


1. 개념 

스칼라 Doc 에서는 이렇게 말합니다. (http://docs.scala-lang.org/tutorials/FAQ/yield.html)

파이썬,루비등에 있는 yield 처럼 스칼라도 yield 를 가지고 있지만 좀 다릅니다. 스칼라의 yield 는 for comprehensions 의 일부분으로 사용되며, 다른 언어의 list-comprehensions 의 일반화 입니다. 스칼라의 "for comprehensions" 는 하스켈의 "do" 와 동등하며  멀티플모나딕 연산을 위한 사용편의 정도일 뿐입니다.

사실 for - comprehensions 같은것들은  syntactic sugar 라고 합니다. 번역하면 사용자가 편하게 사용하기 위한 사탕발림 쯤 되는데요. 일단 외우는세요.  자바나 C++에서 스칼라로 넘어 왔을때 사실 모든게 syntatic sugar 로 보이긴하죠 ㅎㅎ

스칼라 for - comprehensions 를 다양한곳에서 가져온 예제를 통해서 살펴보겠습니다. (개발자는 코드로 말합니다. 이해안가면  코드만 계속 반복해서 보세요. 각성 하실 겁니다. ) 

혹시 python 의 yield 를 사용했던 분이라면 그것은 순회하면서 하나씩 바깥으로 던지는데 반해, scala yield 는 모아뒀다가 컬렉션으로 던저 주는 느낌으로 받아 드리면 될 거 같습니다.


예제 0) flatMap  이란?

scala> val nestedNumbers = List(List(1, 2), List(3, 4)) scala> nestedNumbers.flatMap(x => x.map(_ * 2)) res0: List[Int] = List(2, 4, 6, 8)

하나의 리스트로 '합쳐' 준다는 것을 유념하세요. 그냥 map 을 적용시키면 List(2,4) List(6,8) 이렇게 나오겠죠.
flatMap 이 스칼라나 함수형 프로그래밍에서 굉장히 중요합니다.

F[A]에 대한 Monad는 아래 함수를 가진다

  • (F[A], A => F[B]) => F[B] 타입을 가진 flatMap 메소드
    (즉 A를 감싼F타입에서 A를 빼내어 B로 만든후 F 타입으로 다시 감싼다) 
  • A => F[A] 타입을 가진 pure 메소드 (A 를 감싼 타입으로 변경) 

모나드는 3개의 법칙을 따라야 한다.

  1. Left identity: (pure(a) flatMap f) == f(a)
  2. Right identity: (m flatMap pure) == m
  3. Associativity: (m flatMap f flatMap g) == (m flatMap (x => f(x) flatMap g))

모나드는 pure flatMap을 가지며 결합법칙 항등법칙 만족하도록 만든 구현이다. 즉 이 두 함수들을 이용해서 변수의 도입과 결속 그리고 변수 치환수행을 위한 문맥을 제공한다고 볼수 있으며 이 두 함수를 이용해서 많은 함수를 파생 시킬 수 있으며 코드를 줄이는 것도 가능하다


http://www.bench87.com/content/24 발췌 (COMET)

이런것들이 이해 안가면 예제만 반복해서 봅니다. 그럼 이해갑니다. 인간의 능력이란 반복에서 나옵니다.

예제 1)

  1. for(x <- c1; y <- c2; z <-c3) {...}

위의 문장은 아래 처럼 사용 될 수 있습니다.

  1. c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))

c1의 요소가 3개 , c2 가 3개,  c3 가 3개라면 27번의 행위가 발생되겠군요.


예제 2)

  1. for(x <- c1; y <- c2; z <- c3) yield {...}

위의 문장은 아래 처럼 사용 될 수 있습니다. 

  1. c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))

예제 3)

  1. for(x <- c; if cond) yield {...}

위의 문장은 아래 처럼 사용 될 수 있습니다.

  1. c.withFilter(x => cond).map(x => {...})

cond 에 합당하는 x 들을 이용하여 어떤 행위를 한 후에 다시 c 타입을 갖게 합니다.


예제 4)

  1. for(x <- c; y = ...) yield {...}

위의 문장은 아래 처럼 사용 될 수 있습니다.

  1. c.map(x => (x, ...)).map((x,y) => {...})

map 을 대신해서 사용 할 수 있지만 아래 예를 보시면 느껴지다 시피 무엇이 더 이해하기 쉽습니까?
괄호가 연속된 map 보다는 for yield 를 사용한게 훨씬 명료하네요.


  1. l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))

or

  1. for{
  2. sl <- l
  3. el <- sl
  4. if el > 0
  5. } yield el.toString.length


예제 5) 

scala> val capNames = for (e <- names) yield e.capitalize
capNames: Array[String] = Array(Chris, Ed, Maurice)

for 문을 통해서 하나씩 실행한 후에 yield 를 통해서 names 의 capitalize 만 따로 추출해서 새로운 배열로 만들어집니다.

scala> val lengths = for (e <- names) yield {
     |   // imagine that this required multiple lines of code
     |   e.length
     | }
lengths: Array[Int] = Array(5, 2, 7)

이번에는 숫자를 추출해서 그것으로 배열이 만들어 졌습니다.


예제 6) 

scala> val s = for {
     |  x <- 1 to Integer.MAX_VALUE
     |  if(x <= 10)
     | } yield 2*x

s: (2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

모든 숫자중에서 10보다 작은 숫자를 2배해서 set s 에 담아줬습니다.


예제 7)

  1. def eitherExample(num: Some(Int)): Either[String, Int] = num match {
  2.   case Some(n) => Right(n)
  3.   case None => Left("Error! Number is missing!")
  4. }

입력이 합당하면 Either 의 오른쪽에 값(n) 을 넣고 아니면 왼쪽에 에러를 출력 합니다.
match case 를 이용하였습니다.

  1. val x: Either[String, Long] = Right(7)
  2. x.right.map(num => println(num))
  3. x.left.map(msg => println(msg))


Either 의 오른쪽에 값이 있는 x 를 정의하고 오른쪽 왼쪽을 사용합니다. 위에서 map 은 값이 없는 경우라면 무시할 것입니다. 안전하게 사용 할 수 있겠네요.

  1. val x: Either[String, Int] = Right(7)
  2. val result1 = for( res <- x.right ) { res }
  3. val result2 = for( res <- x.left) { res }


이렇게 for 를 사용할 수도 있습니다. 값이 있는곳 만 처리 됩니다.

  1. for {
  2.   id <- x.right
  3.   name <- y.right
  4. } yield {
  5.   val level = lookupLevel(id)
  6.   Member(id, name, level)
  7. }.fold(
  8.   err => NotAuthorized(err),
  9.   member => member
  10. )

마지막으로 for - yield 문을 보시면 Either 의 x, y 값을 id , name 으로 담습니다.
해당 값들에 문제가 없을 경우에 yield 가 실현됩니다. 여기선
 Member 객체를 만들죠. 그 후에 fold 를 통해서
문제가 있다면 첫번째 함수를 따르고 문제가 없다면 두번째 member => member 를 실행해서 결국 member 가 리턴됩니다. 


예제 8)

case class Postcard(msg: String)

def sendPostcards: List[Postcard] = {
    val states = List("Arizona", "New Mexico",
                          "Texas", "Louisiana",
                          "Mississippi", "Virginia",
                          "New York", "Ohio",
                          "Illinois")
    val relatives = List("Grandma", "Grandpa", "Aunt Dottie", "Dad")
    val travellers = List("Kelsey", "DJ")

    var postcardList: List[Postcard] = List()

    for (h <- 0 until travellers.length) {
        val sender = travellers(h)

        for (i <- 0 until relatives.length) {
            val recipient = relatives(i)

            for (j <- 0 until states.length) {
                val theState = states(j)
                postcardList ::=
                    new Postcard("Dear " + recipient + ", " +
                                  "Wish you were here in " +
                                  theState + "! " +
                                  "Love, " + sender)
            }
        }
    }

    postcardList
}


위의 예제를 아래처럼 for - yield 를 사용해서 바꿔 줄 수 있습니다. (정확히 동일하지 않습니다) 

def sendPostcards3: List[Postcard] = {
    val states = List("Arizona", "New Mexico",
                          "Texas", "Louisiana",
                          "Mississippi", "Virginia",
                          "New York", "Ohio",
                          "Illinois")
    val relatives = List("Grandma", "Grandpa", "Aunt Dottie", "Dad")
    val travellers = List("Kelsey", "DJ")

    for {
        traveller <- travellers
        sender = traveller + " (your favorite)"
        relative <- relatives
        theState <- states
        if (relative.startsWith("G"))
    } yield {
        new Postcard("Dear " + relative + ", " +
                        "Wish you were here in " +
                         theState + "! " +
                         "Love, " + sender)
    }
}

각각의 리스트들을 for 문 안에서 하나를 가져와서 if 문을 통해 데이터를 솎아내고, 그 데이터들을 이용하여   yield 를 통해서 하나의 객체를 만든 후에 다시 순회하면서 객체의 List 를 리턴해 주고 있습니다.

각각의 리스트가 5,5,5 개라면 최대 5*5*5 만큼의 객체가 생성될것입니다.


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


스칼라에서의 커링 (currying)


1. 개념 

스칼라 Doc 에서는 이렇게 말합니다.  (http://docs.scala-lang.org/ko/tutorials/tour/currying)

메소드에는 파라미터 목록을 여럿 정의할 수 있다. 파라미터 목록의 수 보다 적은 파라미터로 메소드가 호출되면, 해당 함수는 누락된 파라미터 목록을 인수로 받는 새로운 함수를 만든다.

중요한 포인트를 뽑아내어 쉽게 설명해보면 


매우 쉬운 설명 

 - 메소드라는건 파라미터를 가지고 있지요?  예를들어  add( int x , int y)  이렇게 2개를 가지고 있을때
 - add 라는 메소드를 호출 할 때 인자 2개를 넣어주지 않으면 보통 에러 나잖습니까?
 - 근데 하나만 넣어도 에러가 나지 않는데다가 ~~   즉 add (10)  이렇게 해두 되고 
 - 누락된 파라미터 만을 사용하는 새로운 메소드를 만들어 준 답니다.~ 즉 add(int y) 라는 새 메소드말이죠.
 - add (int y) 라는 새 메소드는 나중에 호출 할 수 있는데 그 때 이전에 넣어 둔 10이 암시적으로 작용됩니다.

 슈도코드로 살짝 보면 

 int add (int x, int y) {
   return x + y;
 }

원래 이런 함수인데
 add (10) 이렇게 호출하면 add(int y) 라는 메소드가 만들어지며 , 10은 새 메소드에 저장되어 있게 됩니다.

int add( int y) {
    return 10 + y;
}

즉 이런 함수가 자동으로 만들어 진 다는 말이지요.
이 함수를 add(5) 호출하면 결과로 이전에 입력된 10을 더하여 15가 리턴됩니다.


2. 스칼라 예제

  1. object CurryTest extends App {
  2. def filter(xs: List[Int], p: Int => Boolean): List[Int] =
  3. if (xs.isEmpty) xs
  4. else if (p(xs.head)) xs.head :: filter(xs.tail, p)
  5. else filter(xs.tail, p)
  6. def modN(n: Int)(x: Int) = ((x % n) == 0)
  7. val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
  8. println(filter(nums, modN(2)))
  9. println(filter(nums, modN(3)))
  10. }

위의 스칼라 예제의 modN(n: Int)(x: Int) 가 커링되는 함수입니다.
보시다시피 modN(n: Int)(x: Int) 는 2개의 파라미터를 넣어야하는 함수입죠..

위에서 설명했다시피 이걸 파라미터 하나만 넣어서 호출 할 수 도 있으며 그렇게 하면 새로운 함수가 만들어진답니다..  즉 modN(2) 이렇게 호출 했더니 에러는 안나고 대신 새로운 함수를 만들어서

def filter(xs: List[Int], p: Int => Boolean)  filter 파라미터 p로 넣어주고 있습니다.

위의 p 파라미터 자리에는  modN(2) 를 호출해서 만들어진 새로운 함수가 들어 간다는 말입니다.
새로운 함수는 이렇게 생겼겠죠?  (이미 2를 n 자리에 넣었다는것을 기억하시구요) 

def modN(x: Int) = ((x % 2) == 0)


3. HOW? 

여기에 하나의 함수를 파라미터로 받아서 , 그 함수를 내부에서 사용하고 2개의 Int 형을 받는 함수를 리턴하는 함수가 있습니다.

def sum(f:Int=>Int):(Int,Int)=>Int={
   def sumF(a:Int,b:Int):Int= 
      if(a>b) 0 else f(a)+ sumF(a+1,b)
   sumF
  }

이런 경우 내부에 함수를 선언하지 않고 , 함수 파라미터 한개와 2개의 Int 형 파라미터 , 즉 3개의 파라미터를 받는 커링 함수를 아래처럼 만들수 도 있습니다.

def sum(f:Int=>Int)(a:Int,b:Int):Int=
  if(a>b) 0 else f(a)+sum(f)(a+1,b)


4. {} 의 사용 

스칼라에서는 함수호출시  ()  대신 {} 중괄호를 사용 할 수 있다.  단 !!! 인재가 하나일 경우 !!

println ("Hello World") 대신해서 println { "Hello World" } 를 사용 할 수 있다는 의미이다.


스칼라에서 이렇게 한 이유는 클라이언트 프로그래머가 중괄호 내부에 함수 리터럴을 사용하도록 하기 위해서이다. 이렇게 작성한 메소드는 호출 시 좀 더 제어 추상화 구문과 비슷해진다.

def withPrintWriter(file: File, op: PrintWriter => Unit ) {

......
}

이런 함수를 호출 할 때는 

withPrintWriter ( new File ("date.txt") , 

writer => writer.println(new java.util.Date) ) 


이렇게 할 수 있는데 , 인자가 2개이기 때문에 { } 를 사용할 수 없다.


하지만 !!!!

def withPrintWriter(file: File) (op: PrintWriter => Unit ) {

val writer = new PrintWriter(file)

try {

    op (writer)

} finally {

    writer.close()

}

}

이러한 함수라면 {} 를 사용가능하다.

val file = new File("date.txt")
withPrintWriter (file) {
writer => writer.println(new java.util.Date)
}

왜 그러냐면 커링을 통해서 file 을 매개변수로 먼저 넣은 새로운 함수가 생겨졌고, 

그 새로운 함수는  매개변수 하나를 필요로 하는 함수이기 때문이다.


보통 (변수,함수) 매개변수 조합일 때 많이 사용된다. 

def foldLeft[B](z: B)(op: (B, A) => B): B = {
  var result = z
  this foreach (x => result = op(result, x))
  result
}
Vector(1, 2, 3).foldLeft(Vector[Int]()) { (is, i) =>
  is :+ i * i
}


5. 추가 읽을거리 : Partial function 과의 차이 


함수 인자를 생략하는 문법들에는 

- 디폴트파라미터
- implicit 
- partial applied function
- currying 

등 이 있는데 그 중에 헤깔리기 쉬운 currying vs partial function 에 대해 살펴보자. 아래 링크를 읽으심이

http://www.vasinov.com/blog/on-currying-and-partial-function-application/


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


- https://github.com/enshahar/BasicFPinScala/blob/master/Intermediate/Monad.md

- https://github.com/enshahar/BasicFPinScala/blob/master/Intermediate/Monad_Rules.md

Monad Programming with Scala Future

http://www.haruair.com/blog/2986

http://blog.seulgi.kim/2015/07/what-is-monad.html

- 자바언어로 서술한 Functor 와 모나드 

 제 능력이 부족한 관계로 위의 글로써 대신합니다. (모나드 관련 한글 블로그 모음) 

 아참! 위의 글을 읽기 전에 반드시 모나드 괴담!을 읽어보세요. ^^


모나드가 무엇인지 이해함으로써 하스켈 프로그래머가 되겠다는 것은 "악기란 무엇인가?"를 이해하면 모든 악기의 연주자가 될 수 있으리라는 믿음과 같다. 즉 모나드를 이해하는것과 프로그래밍을 잘하는것과는 별개의 문제.
Fallacy (2015)


모나드 간단 정리
(코딩하다 보면 아 대략 이러면 모나드다 정도면 될 거 같습니다. 완벽한 모나드의 법칙을 이해 할 필요까지는 없을듯) 

1.모나드는 다른 타입을 인자로 받는 타입이다. 

case class Boxed[T](value:T);

2.모나드 타입의 값을 생성하는 함수가 있어야 한다.

def initBoxed(x:Int):Boxed[Int] = Boxed(x)
def initLogged(x:Int):Logged[Int] = Logged(x, List())
def initMyOption(x:Int):MyOption[Int] = MySome(x)

3.다른 모나드 타입으로 진행하는 함수가 있어야 한다. (또한 감싼 값에 대해서 꺼낼 수 있는 방법을 제공하며 꺼낸 값을 가지고 원하는 형식으로 변형하고 다시 감싸서 반환)

session => Future[User]
=> flatMap => (User => Future[Order])
=> flatMap => (Order => Future[List[Item]])

위의 3가지 성질을 모두 가지고 있을때 모나드라 칭한다.

* 스칼라 Future 는 모나드가 아니다.=> https://stackoverflow.com/questions/27454798/is-future-in-scala-a-monad


아래의  자바스크립트의 예를 보면 대략 모두 가지고 있음을 알 수 있다. 

// 타입 스크립트로 Monad의 인터페이스를 설계함
interface MStatic<T> {
    // constructor that wraps value
    new(value: T): M<T>;
}

interface M<T> {
    // bind as an instance method
    bind<U>(transform: (value: T) => M<U>): M<U>;
}


 


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


스칼라에서 JSON 데이터 다루기

* scala 기존제공하는것보다 json4s 나 spray-json 이 더 나은듯하다.


1. 디펜던시 추가 

import scala.util.parsing.json._


2. 문자열에서 Json 객체 (Map 타입) 로 변경  - (1)


def main(arg : Array[String]): Unit ={


val result = JSON.parseFull("""
{"name": "Naoki", "lang": ["Java", "Scala"]}
""")

result match {
case Some(e) => println(e)
case None => println("Failed.")
}

} //print 결과 : Map(name -> Naoki, lang -> List(Java, Scala)


3. 문자열에서 Json 객체 (Map 타입) 로 변경  - (2)

def main(arg : Array[String]): Unit ={


val msg = """

{ "callid" : 1 ,
"gatewayid" : "gatewayid-1" ,
"switchid" : "switch-id" ,
"content" : "hi"
}
"""

val result = JSON.parseFull(msg)

result match {
case Some(e) => val res1 = e.asInstanceOf[Map[String,Any]];
print(res1.get("callid"))
case None => println("Failed.")
}

} //print 결과 : Some(1.0)

asInstanceOf 를 통해서 형변환을 수행 하였다.


case Some(map: Map[String,Any]) => print(map.get("callid"))

Some 내부에서 형변환 할 수 도 있다.


4. Json 객체 (Map 타입) 에서 문자열로 변경 


의존성 추가 ( json4s 를 이용함) 

libraryDependencies += "org.json4s" %% "json4s-native" % "3.2.10"
libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.2.10"

import 

import org.json4s.jackson.Serialization

map 을  String 으로 변경

implicit val formats = org.json4s.DefaultFormats

Serialization.write(mutableMap)



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

 

Partially applied function 과 Partial function 

먼저 아래 링크 참고 하세요. (아웃사이더님이 번역하신  글이 있습니다)

https://blog.outsider.ne.kr/953  


아마 저 글을 읽어도 Partial function에 관해 조금 헤깔리실텐데요. 간략하게 다시 설명해 드리겠습니다.
함수란 모든 x 에 대해서 y 라고 할때 부분 함수란 일부 x 에 대해서 y 인 함수를 말 합니다. 즉 제한을 둔겁니다.

예를들어

function  A (int x ) {
   return x * x 
}

라는 함수에 대해서 아래 함수는 부분함수입니다.

function A'  (int x) {
  if x > 10 
     return x * x 
}

모든 것을 처리하지 않고 싶을 때 부분 함수를 만들게 됩니다.
그 모든 것 말고 예외인 항목을 입력하면 에러/예외가 발생하게 말이죠.

그 예외 항목이 먼지 알려주는 함수가 isDefinedAt 입니다.
따라서 이 함수를 먼저 이용해서 해당 파라미터가 그 부분함수에서 잘 작동하는지 확인한 후에 
파라미터를 넘긴다는 야그죠. 

아래 예제를 봅시다.

scala> List(41, "cat") map { case i: Int ⇒ i + 1 }
scala.MatchError: cat (of class java.lang.String)

 "cat" 이 map 으로 전달되면 case 에 매칭되지 않기 때문에 에러가 나는데 

scala> List(41, "cat") collect { case i: Int ⇒ i + 1 }
res1: List[Int] = List(42)

Collect 경우는 괜찮네요? 

네 Collect 경우는 isDefinedAt 으로 부분함수에 대한 검증을 합니다. 

마지막으로 PartialFunction 은 아래와 같이 만들며 자동으로 isDefinedAt 이 만들어지는게 특징입니다.

[Int, Int] 는 인자로 Int 리턴값으로 Int 를 말합니다.

val fraction: PartialFunction[Int, Int] =
  { case d: Int if d != 042 / d }

이 경우 isDefindAt 에 0 이 들어가면 false 를 내보내겠지요?

마지막 예제를 보면서 생각해 보는 시간을 가지세요 ^^

scala> val liar: PartialFunction[Any, Int] =
  { case i: Int ⇒ i; case s: String ⇒ s.toInt }

liar: PartialFunction[Any,Int] = <function1>

scala> liar.isDefinedAt(42)
res10: Boolean = true

scala> liar.isDefinedAt("cat")
res11: Boolean = true

scala > liar ("3")
res12: Int = 3 

scala> liar("cat")
java.lang.NumberFormatException: For input string: "cat"

scala> val honest: PartialFunction[Any, Int] = { case i: Int ⇒ i; case s: String if isParsableAsInt(s) ⇒ s.toInt } honest: PartialFunction[Any,Int] = <function1> scala> honest.isDefinedAt("cat") res12: Boolean = false



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


언더스코어  _  는 스칼라에서 어떻게 사용되나?


1. Import 


import scala.concurrent._

import 에서 _ 는 모든을 뜻한다. 자바에서 * 


2. 디폴트 초기화 

class Foo {
var i : Int = _ // i = 0
var s: String = _ // s = null

}

Int  는 0 으로 초기화 되고 String 을 대입하면 null 로 초기화 됩니다.


3. 고계 함수에서 익명 파라미터


( 1 to 10) map { _ + 1 }


4. 부분 함수에서 익명 파라미터

좀 더 정확히 얘기하면 partially applied function.   partial function 과 구분된다. 바로가기 

def f(i : Int) : String = i.toString
def g = (x: Int) => f(x)
def g2 = f _
def u(i: Int) (d: Double) : String = i.toString + d.toString

def w = u(4) _ // (y: Double) => u(4)(y) 와 동일
println(w(5)) // 45.0 출력


5. 패턴 매칭

expr match {
case List(1,_,_) => "첫번째 요소는 1 이며 3개의 요소를 가진 리스트"
case List(_*) => "0 개나 혹은 더 많은 요소를 가진 리스트"
case Map[_,_] => "아무 타입의 키와 아무 타입의 값을 갖는 맵"
case _ => "나머지 아무거나"
}


6. 제네릭에서 와일드카드 

def size(objs: List[_]) : Int = {
objs.size
}

def loggedIn(req: RequestWithAttributes[_]): User = {

...

}


7. 프로퍼티에서 세터 

class Test {
private var a = 0
def age = a
def age_=(n:Int) = {
require(n>0)
a = n
}
}
val t = new Test
t.age = 5
println(t.age)


참고 :

 http://ananthakumaran.in/2010/03/29/scala-underscore-magic.html

http://www.slideshare.net/normation/scala-dreaded



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

Null 과 친구들 

Null  –  Trait 이다. 모든 참조 타입(AnyRef를 상속한 모든 클래스) 의 서브클래스이다. 값 타입과는 호환성이 없다. 

null  –  Null 의 인스턴스이고 자바의 null 가 비슷. val i : Int = null; 불가능 (값 타입과 호환성 없다) 

NothingTrait 이며 모든것들의 서브타입이다.기술적으로 예외는 Nothing 타입을 갖는다. 이 타입은 값이 존재하지 않는다. 값에 대입이 가능하다. 즉  리턴타입이 Int 인 메소드에서 리턴을 Nothing 타입인 예외를 던질 수 있다. 

Nil    –  아무것도 없는 List 를 나타낸다.

None – 아무것도 없다는 리턴 값을 표현하기 위해 사용된다. null 포인트 예외를 회피하기 위해 Option[T] 의 자식클래스로 사용된다. 

Unit  – 아무 값도 리턴 하지 않는 메소드의 리턴타입으로 사용. 



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


Apply

자바를 하다가 스칼라로 넘어오면 희안한거 많이 보는데 apply 라는 것도  분명히 마주치게 되는데 이게 여러군데에서  다른 의미로 사용되서 좀 헤깔리죠. 그래서 이 참에 정리 한번 하고 넘어 갑니다. 


기본 

해당 객체 뒤에 하나 이상의 값을 괄호{}로 둘러싸서 호출하면 그 객체의 apply() 메소드를 자동으로 호출해 줍니다. 스칼라에서는 apply 라는 메소드가 기본적으로 이 객체(타입) 저 객체(타입) 에 각자 정의되어 있습니다.함수타입에서 apply 하면 이렇게 작동하고 , 리스트에서  apply 하면 요렇게 작동하고 그렇습니다. 중구난방이라는 말 그래서 어느정도는 외워야합니다.

예를 보시죠

예1)

class applyTest {
def apply(n : Int): Unit = {
print ("hello world")
}
}

object test {

def main(arg : Array[String]): Unit ={

val at = new applyTest
at{10}
}
}

그냥 at{10} 로 호출하면 hello world 가 찍힙니다. 참고로 매개변수가 아예 없이는 안됩니다.

결국 {..} 사이에는 apply 의 인자가 들어가게 됩니다. 위에는 Int 형이 들어가네요.

예2)

class applyTest{

def apply( f : => Int): Unit ={
val n = f + 3
println("n = %d".format(n))
}
}


object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
println("apply test")
10
}
}
}

이게 참 골때리는 겁니다. 편의성을 높힌 Syntactic sugar 라고 하지만 여러언어를 쓰다보면 꽤 헥깔립니다.

먼저 applyTest 클래스에 apply 함수를 만들어 두었습니다. 이 apply 는  => Int 같은 람다식 (입력인자가 없고 리턴이 Int 형인) 함수타입을 인자로 받습니다. 여기까진 OK~

근데 10은 멀까요 -.-a

def apply( f : => Int)

여기에 f 에 대응되는 함수가 

println("apply test") 

return 10 

란 야그죠.

결국 f + 3 은 10 + 3 과 같습니다.


예3) 함수 자체를 넘기기도 합니다.

class applyTest{

def apply( f : String => Unit): Unit ={
f("hello world")
}
}


object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
println
}
}
}


예4) 함수의 리턴값을 넘기기도 하고 

object obj1{
def getWord: String = {
return "hello world"
}

}

class applyTest{

def apply( str : String): Unit ={
println(str)
}
}

object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
obj1.getWord
}
}
}

예4) 역시 이렇게 함수 자체를 넘길 수도 있습니다.


object obj1{
def getWord: String = {
return "hello world"
}

}

class applyTest{

def apply( block: => String): Unit ={
print(block)
}
}

object test {

def main(arg : Array[String]): Unit ={
val t = new applyTest()
t{
obj1.getWord
}
}
}

Array 에서의 apply 

예 1)  Array 에서의 값 가져오기

val a = new  Array[String](2)  // String 타입이 2개 담길수 있는 배열의 객체를 만들었네요.

a(0)  이렇게 하면 첫번째  값을 가져 올 수 있는데요. 객체에 바로 () 를 붙혔습니다. 어색하죠? 

즉 여기선  a.apply(0) 이렇게 자동으로 변경 됩니다.

그리고 Array 에서의 apply 메소드의 역할은 해당 인덱스에 해당하는 값을 꺼내오는 겁니다.


예 2Array 생성하기 

val a = Array("hello", "world") 


이렇게 만들 수 있습니다. new 가 생략되었는데 이게 가능한 이유는 

암시적으로 apply()  라는 이름의 팩토리 메소드를 호출해서 객체를 만들어줍니다.

Array.apply("hello", "world") 이렇게 되는거죠 여기서는 ..




함수타입에서 사용

val f1 = (x: Int, y: Int) => x + y
f1(2,3) // f1.apply(2,3) 와 같다. 결과 : Int = 5

저렇게 호출하면 알아서 apply 해준다.
참고로 저 함수 리터럴의 타입은 <function2> 이며 스칼라에선 모든게 다 객체이다.



Set 에서의 apply

val fruit = Set("apple", "orange", "peach")
fruit("peach")

위에서 호출되는 apply 는 contains 와 동일하다.




Play2 에서 Action 호출에서의 사용 

스칼라언어 기반 Play 프레임워크 웹개발에서는 

def doSomething = Action{
Ok.apply(views.html.index("Hi there"))
}

이런 모양새로 이루어지는데 여기서 Action 이 무엇일까요? 

위의 예는 다음과 같이 풀어지게 되는데요

def doSomething: Action[AnyContent] = Action.apply(
    Ok.apply(views.html.index("Hi there"))
)

위의 apply 메소드의 정의는  apply(block: => Result) 이라 익명의 함수가 들어가게 됩니다.  

이 말인 즉,  Ok.apply 함수는 Result 를 반환하겠군요.

Action.apply 함수는 Result 를 사용한 후에 Action[AnyContent] 를 반환하구요.




WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

Option

값이 있거나 또는 없거나 한 상태를 나타낼 수 있는 타입이다.
값이 담겨져 있는 Option 의 하위 타입은 Some[T] 이며, 값이 없으면 None  이다.
Option 은 Try, Future 등과 함께 대표적인  모나딕컬렉션 이다. "컬렉션" 이다.

보통 Option 을 떠올리면 2가지를 생각해야한다.

1. null 을 안전하게 대체하기 위해 만들어진 것. 
-> 사용자에게 주의를 다시 한번 당부하는 것으로, null 예외가 발생할 확률을 없앤다.

2. 연속체인에서 안정적으로 사용하기 위한 것
-> 연속으로 계산되는 상황에서 안정적으로 실행된다. 즉 중간에 문제가 생기는것을 방어한다. 주의 할 것은 방어가 되는 함수는 따로 있다는 것이며 아래 표에서 자세히 설명된다. 


Option 이 사용되는 경우 

val numbers = Map("one" -> 1, "two" -> 2)
val h2 : Option[Int] = numbers.get("two")

이 경우 h1 에는 값이 담겨져 있는 Option 을 받을 것이다. 즉 Some 타입을 받을 것이고

val numbers = Map("one" -> 1, "two" -> 2)
val h3 : Option[Int] = numbers.get("three")

이 경우 h3 에 해당 되는 값이 없기 때문에 Option 은 None 이 될 것이다. 


Option 에서 값 가져오기 (get 과 isDefined 사용하기)

val h3 : Option[Int] = numbers.get("three")

이 경우 h3 이 Some 인지 None 인지 어떻게 알까? 확인하고 사용해야지 않을가? 
그때 isDefined 메소드를 사용한다. 
(반대 개념의  isEmpty 도 있다.)

if (h3.isDefined)
println (h3.get)

요렇게  확인해서 값을 사용 할 수 있다.  Option 타입에서 실제 값을 가져오는 함수는 get 이다. 


getOrElse 로 사용하기

def testOption(o : Option[Int]) = {

println(o.getOrElse("nothing"))
}

getOrElse 를 사용하여 값이 있으면 그 값을 사용하고 , 없으면 인자로 넘긴 "nothing" 을 사용하게 한다. 


패턴 매칭으로 로 사용하기

def testOption(o : Option[Int]) = {

val result = o match {
case Some(n) => n
case None => "nothing" }
}

match 를 사용하여 값이 있으면 즉 Some(n) 으로 매칭되면 값 사용 , 없으면 "nothing" 을 사용하게 한다. 


Option 타입의 파라미터 사용

def testOption(o : Option[Int]) = {
    ...
}

testOption(Some(10))

옵션타입을 인자로 받는 함수에 값을 넘길때는 값을 직접 넘길 수 없다. Some 타입으로 만들어서 넘긴다.



Option 타입을 map 으로 연결 해보자.

case class Employee(name: String, department: String)

def lookupByName(name: String) : Option[Employee] = ...

val joeDepartment: Option[String] = lookupByName("joe").map(_.department)

이렇게 map 으로 연결 할 수 있다. 즉 map 은 Option 타입을 스스로 변경하여 적용한다.
근데 여기서 None 이 맵으로 들어 가면 어떻게 될까? 그냥 나머지 계산이 취소되어서 아무것도 안한다.

Option 은 스칼라에서의 가장 기본적인 모나드라고 한다.
추가적으로 Try, Future 등도 대표 모나딕컬렉션이다. 


안전한 Option 추출 정리 

 fold

nextOption.fold(-1)(x=>x) 

Some(이 경우 내장값)인 경우 주어진 함수로부터 추출한 값을,None인 경우 시작값을 반환함,foldLeft,foldRight,reduceXXX 메소드로도 Option 을 그 내장된 값 아니면 계산된 값으로 축소할 수 있음   

 getOrElse

nextOption getOrElse 5 또는
nextOption getOrElse {
   println("error!"); -1 }

Some의 값을 반환하거나 아니면 이름 매개변수의 결과를 반환함 

 orElse

nextOption orElse nextOption 

실제로 값을 추출하지는 않지만,None인 경우 값을 채우려함. Option이 비어 있지 않으면 이 Option을 반환하고, 그렇지 않은 경우 주어진 이름 매개변수로부터 Option 을 반환함 

match 표현식 

nextOption match { case Some(x) =>x;
                       case None => -1 } 

값이 있는 경우 매치 표현식을 사용하여 그 값을 처리함, Some(x) 표현식은 그 데이터를 추출하여 매치 표현식의 결과값으로 사용되거나 또 다른 변환에 재사용할 수 있는 지정된 값'x' 에 넣음 

* 러닝스칼라 (제이슨스와츠,제이펍 발생) 에서 발췌


Either 

둘 중 하나의 값을 가질 수 있는 타입이다.

Either 에 값 담기 

def eitherTest(num: Option[Int]): Either[String, Int] = num match {
case Some(n) => Right(n)
case None => Left("Error! Number is missing!")
}


eitherTest(Some(7))

eitherTest 메소드를 살펴보자. 
인자로 제대로 된 값이 들어오면 Right 즉 Either 의 오른쪽에 담고
인자에 제대로 된 값이 안 들어오면 Left 즉 Either 의 왼쪽에 값을 담는다.  


Either 값 사용하기 

val res = eitherTest(Some(7))
if( res.isRight )     println("Number is %s".format(res.right.get))
else if( res.isLeft)     println("Error message => %s".format(res.left.get))

eitherTest 메소드를 호출해서 받는 값 res 는  Either[String, Int] 타입인데  어느쪽에 제대로 된 값이 들어 있는 지 확인하기 위해서 res.isRight / res.isLeft 를 사용했다.


패턴 매칭으로 사용하기 

val result = eitherExample(Some(7)) match {
case Right(num) => num
case Left(err) => err
}

다음 처럼 패턴 매칭을 사용 할 수 도 있다. 



레퍼런스:

러닝 스칼라
프로그래밍 인 스칼라


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


List 활용


*  List 는 스칼라에서 가장 많이 사용되는 데이터 구조일 겁니다.

* 스칼라의 리스트는 디폴트로 불변형입니다.

* 스칼라의 리스트 타입은 공변적입니다.


길이 구하기

val a = List (1,2,3) 

a.length  // 3 


*배열과 달리 리스트의 length 는 비교적 비싼 연산입니다. 

a.isEmpty 를 a.length == 0 으로 사용하지 마세요. 


양 끝에 접근하기

val a = List ('a','b','c','d')

a.head     // 'a'  처음

a.last       //  'd'  마지막

a.tail       // List('b', 'c','d')  처음 제외 나머지  

a.init      // List('a','b','c')    마지막 제외 나머지 


리스트 뒤집기 

val a = List ('a',b','c','d')

a.reverse    // List ('d','c','b','a')  * 주의할것은 새로운 리스트가 생긴다는 점이다. 리스트는 불변이니깐~ 


drop / take / splitAt

val a = List ('a',b','c','d')

a.take(2)    // List ('a','b') 
a.drop(2)   // List ('c',d')

a.splitAt(2)  // (List('a','b'), List('c','d'))


apply / indices

val a = List ('a',b','c','d')

a.apply(2)   // 'c'      * 배열에 비해 불리하다.

a.indices    // Range (0,1,2,3)   * 리스트에서 유효한 모든 인덱스의 리스트를 반환 


flatten

val a = List ( List(1,2), List(3), List(4,5) )

a.flatten    // List (1,2,3,4,5)   * 리스트의 리스트일 경우에 한 해 하나로 반듯하게 펼친다.


zip / zipWithIndex

val a = List (1,2,3)  

val b = List('a','b','c') 

a.zip(b)     // List( (1,'a'), (2,'b') , (3,'c') )   * 두 리스트를 순서쌍으로 묶는다.

b.zipWidhIndex   // List ( ('a',0), ('b',2), ('c', 3) )  * 리스트의 원소를 인덱스와 함께 순서쌍으로 묶는다.


toString / mkString

val a = List ('a','b','c','d')

a.toString   // List(a, b, c, d)  * 리스트 자체를 표준 문자열로 만든다. 

a.mkSring( "[" , "," , "]" )   // [a,b,c,d,e]    * 리스트의 앞 , 중간, 뒤에 들어갈 문자를 정해서 출력한다.



toArray / copyToArray

val a = List ('a','b','c','d')

val arr = a.toArray   // Array(a,b,c,d)   * array로 변환

arr.toList   // List (a,b,c,d)   * List 로 변환


List  클래스의 고차 메소드


map 

val a = List (1,2, 3)

a.map(_ + 1)   //   List (2,3,4) 


flatMap

val words = List ( "the" , "quick" , "brown" , "fox" )

words.map(_.toList)      // List (List (t,h,e) , List (q,u,i,  .........

words.flatMap(_.toList)   //  List (t,h,e,q,u,i ...........



map  과 flatMap의 차이

scala> val fruits = Seq("apple", "banana", "orange")
fruits: Seq[java.lang.String] = List(apple, banana, orange)

scala> fruits.map(_.toUpperCase)
res0: Seq[java.lang.String] = List(APPLE, BANANA, ORANGE)

scala> fruits.flatMap(_.toUpperCase)
res1: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)

map  과 flatMap의 차이2  (이 특성은 너무너무너무 중요하다. 별표3개)

def toInt(s: String): Option[Int] = {
    try {
        Some(Integer.parseInt(s.trim))
    } catch {
        // catch Exception to catch null 's'
        case e: Exception => None
    }
}

scala> val strings = Seq("1", "2", "foo", "3", "bar")
strings: Seq[java.lang.String] = List(1, 2, foo, 3, bar)

scala> strings.map(toInt)
res0: Seq[Option[Int]] = List(Some(1), Some(2), None, Some(3), None)

scala> strings.flatMap(toInt)
res1: Seq[Int] = List(1, 2, 3)

scala> strings.flatMap(toInt).sum
res2: Int = 6

flatMap의 추가 특징1

scala> val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
map: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two, 3 -> three)

scala> 1 to map.size flatMap(map.get)
res0: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(one, two, three)


flatMap의 추가 특징2

flatMap 은 map 이랑 flatten 을 합해놓것과 같다. 예를들어 List[List[Int]] 를 List[Int] 로 바꿀려면 밑에 처럼 flatten 을 쓰면 된다.

  1. val ints = List(List(1,2,3), List(4,5))
  2. ints.flatten // => this returns List(1,2,3,4,5)

그렇다면 List[List[Int]] 를 List[String] 으로 바꿀려면 어떻해야 할까? flatMap 을 쓰면된다.

  1. scala> val ints = List(List(1,2,3), List(4,5))
  2. ints: List[List[Int]] = List(List(1, 2, 3), List(4, 5))

  3. scala> ints.flatMap(_.map(_.toString))
  4. res0: List[java.lang.String] = List(1, 2, 3, 4, 5)

flatMap은 모나드 이해라든지 함수형에서 매우 중요하므로 아래 2개의 글도 읽어볼것 


여러개의 Future를 반환하는 함수를 List[Future[Item]] 이런식으로 얻는게 아니라, 엮어서 1개의 Future 로 모두 계산될 수 있는 Future[List[Item]]을 얻을 수 있는 거 같은 ~~ 느낌적인 느낌?? 오시나요? 



foreach

val n = List ( 1,2,3,4 )

var sum = 0

n.foreach( sum += _)    //  sum 은 15   * 결과 자체는 Unit이다.


filter

val n = List ( 1,2,3,4 )

n.filter( _ % 2 == 0)    // List (2,4)    * 걸러서 버린다.


partition

val n = List ( 1,2,3,4 )

n.partition( _ % 2 == 0)    // ( List (2,4) , List(1,3) )   * 걸러서 나눈다.


find

val n = List ( 1,2,3,4 )

n.find( _ % 2 == 0)    // Some(2)    * 만족하는 첫번째 원소 반환한다


sortWith

val n = List ( 1,-3,4,2,6 )

n.sortWith ( _ < _ )     // List (-3,1,2,4,5)      *원소를 정렬한


Reducing / Folding / Scanning 

맵이 요소들에 어떤 변화를 가하여 전체 요소들을 개별적으로 바꾸어 주는 역할이라면 
이것은 어떤 요소롤 가득찬 자료구조의 합쳐서 (더하거나 빼거나 등등)  단일 결과를 얻고 싶을때 사용한다. 

reduceLeft

List(1,7,2,9).reduceLeft (_ - _) 

는  ((1 - 7 ) - 2 ) - 9  = -17   이다. 

(_ - _) 여기의 왼쪽 _  이 누적값이고 오른쪽 _ 은 각 요소들이다. 

reduceRight

List(1,7,2,9).reduceRight(_ - _) 는 컬렉션의 마지막에서 시작한다.
즉 1 - ( 7 - ( 2 - 9) ) = -13

(_ - _) 여기의 오른쪽 _  이 누적값이고 왼쪽 _ 은 각 요소들이다. 


foldLeft


folding  은 reducing 하고 비슷하며 , 단지 처음 시작할 원소를 따로 추가해준다.

val numbers = List(1,7,2,9)

scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n) res0: Int = 19

0은 시작 값이고 m은 값을 누적하는 변수 역할을 한다. n 은 물론 리스트의 요소이다. 
0 + 1 + 7 + 2 + 9  이고

(0 /: List(1,7,2,9)) (_+_) 이렇게 쓸 수도  있다. 


사실 이런 예제라면  그냥 sum 을 사용하면 된다.







WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret


암시적 변환과 암시적 파라미터 


스칼라에서 implict 는 편하기도 하지만 코드가독성을 엄청 떨어뜨릴 수도 있기 때문에 논란이 되곤합니다.
왜 그런지 한번 살펴 볼까요?


암시규칙 

 x + y 라는 표현식에서 타입 오류가 있다면 컴파일러는 convert(x) + y 를 시도 해봅니다. 
여기서 자동으로 가져다 사용되는 convert 는 무엇일까요? 
 convert 는 그냥 단순 함수이나 
여기서는 암시적으로 적용되는 변환에 사용됩니다. 아래의 규칙들을 갖는다면 말이지요.


1. 표시규칙 : implicit 로 표시한 정의만 검토 대상이 된다.

즉 implicit def intToString(x : Int) = x.toString 과 같이 implict 를 붙여주면 컴파일러가 암시적 변환에 사용할 후보에 넣는다. 변수,함수,객체정의에 implict 표시를 달 수 있다. 


2. 스코프 규칙 : 삽입할 implict 변환은 스코프 내에 단일 식별자로만 존재하거나, 변환의 결과나 원래 타입과 연관이 있어야 한다.

즉 someVariable.convert 같이는 안되며, 외부에서 가져올 경우 import Preamble._ 를 이용해서 단일 식별자로 가리킬 수 있게 한다. 그리고 원타입과 변환 결과 타입의 동반 객체에 있는 암시적 정의도 가능하다. 

Dollar 에서 Euro 로 변환 하고자 할때, 
object Dollar {

implict def dollarToEuro (x : Dollar) : Euro = ....

}

class Dollar { ... } 

이렇게 하면된다. implict 변환은 프로그램  전체에 영향을 미치지 않는다는 것을 기억하라. 그렇다면 가독성에 크게 문제가 생길 것이다.


3. 한번에 하나만 규칙 : 오직 하나의 암시적 선언만 사용한다.
convert1(convert2 (x) ) + y  이렇게 안됨.

4. 명시성 우선 규칙: 코드가 그 상태 그대로 타입 검사를 통과 한다면  암시를 통한 변환을 시도치 않음



암시적 변환 (implicit conversion) 


개념 

버튼이라는 클래스를 가지고  생각해 보죠.

버튼이라는 클래스는 이미 라이브러리로 제공되고  있는 상황 (우리가 임의로 못고치는) 에서 버튼이 

눌려졌을때 어떤 행동이 일어날지는 개발자가 정해서 버튼에게 넘겨주게 됩니다. 

일단 우리는 왼쪽 버튼이 눌려지면 "삐약" 이라는 소리를 내게 하고 싶다고 해보죠.


val button = new OkkyButton 

버튼 클래스는  버튼이 눌려졌을때 어떤 행동을 할 지에 대해서  제공 받아야 합니다.


button.addLButtonDownAction( ...어떤 행동... ) 

보는바와 같이 버튼은 메소드를 제공하고 우리는 파라미터에   어떤 "행동" 을 제공합니다.

선이 그려질 수 도 있고, 파일이 저장될 수 도 있고 등등


이때 행동을 버튼 클래스에 추가 시키는 방법은 아래와 같을 수 있습니다.

button.addLButtonDownAction(

new ActionListener {

def actionPerformed ( event : ActionEvent ) {

println (" 삐약 ")

}

}

}

 "삐약" 이라는 행동을 넣었습니다.  완성!!

근데 좀 껄쩍지근한게  달랑 "삐약" 을 넣고 싶었는데 너무 많은 코드가 사용 되었네요. 

(이런 코드는 행사코드/얼개코드 등으로 불립니다. 해당 언어의 문법상 어쩔 수 없이 구현해야하는 코드지요.)

ActionListener  클래스도 사용되었으며 actionPerformed   메소드도 사용되었습니다.

그냥  println (" 삐약 ")  만 사용하면 안될까요?  왜 복잡한 코드를  나중에 또 봐야하는가요? 

(복잡함이라는게 코드가 길다고 복잡한건 아니긴 합니다. 즉 좀 길더라도 왜 그렇게 해야하는지에 대한 납득의 과정이 빠르면 문제되지 않으며, 코드가 짧더라도 그 납득의 과정이 불분명하면 오히려 더 코드가독성이 어려워 지곤 합니다. 이것이 바로 스칼라 언어의 딜레마 같습니다. 즉 웬만큼 익숙해 지기 전까지는 자바보다 분명 짧지만 코드가독성이 나아지지 않는? ) 


뭐시~ 중한디??


버튼의 addLButtonDownAction 메소드는 특정 인터페이스를 상속한 클래스를 파라미터로 강제하고 

있는거 같구요.. 그래서 어쩔 수 없이  저렇게 inner 클래스를 파라미터로 던져 주었습니다.

클래스 말고 그냥 함수를 던저 주면 안될까요? 외부 라이브러리라 바꿀 수 가 없습니다.


button.addLButtonDownAction { (_: ActionEvent) => printn(" 삐약 ") }

요로코롬 말이죠~   이렇게 해 보았지만  역시나 애초에~~ 

OkkyButton 버튼은  클래스를 매개변수로 받기 때문에 아쉽게도 요거는 꽝입니다. 

(여기서 java 8 스윙은 람다를 지원해서 어쩌구 저쩌구.. 입이 간질거리겠지만  잠시 잊어버리시고.. ) 


자 여기서 개념이 나오니깐 정신집중!!!!! 


저렇게 우아하게 처리해봤는데 아쉽게도 타입 불일치로 "꽝" 이 되버렸네요. 그냥 포기할까요?

우리 포기하지 말자구요 -.-V   스칼라는 암시적 변환이 있습니다.

자 다음 코드를 보세요.

implicit def functionToActionClass(f : ActionEvent => Unit ) = 

new ActionListener {

def actionPerformed (event : ActionEvent) = f (event)

}


implicit 로 처음에 시작하구요. 매개변수를 함수로 받아서 클래스를 리턴해 주고 있습니다.

이 코드를 사용해보죠.

button.addLButtonDownAction(

functionToActionClass( (_: ActionEvent) => println (" 삐약 ") 

}

이렇게 사용되었습니다. 애초에 내부 클래스 코드보다는 조금 간단해 졌습니다. !! (어떤 면으로는  더 복잡해졌습니다.) 

음.. 근데 이건 implicit 가 먼지모르겠지만 그거 상관없이 되는 거 잖아? 그렇죠.. --;; 

좀 만 기다려주세요.

이제 implicit 의 파워가 나옵니다.

 

button.addLButtonDownAction( (_: ActionEvent) => println (" 삐약 ") }


이렇게 functionToActionClass 를 빼도 잘 작동합니다~ 정말 너무나 간단해 졌습니다.

이게 어떻게 가능하냐면

일단 저 코드가 컴파일되면 처음엔 타입오류가 발생하긴 합니다.근데 여기서 그냥 나 못해~ 하고 끝나는게 

아니라 우리의 컴파일러군은 포기하지 안고서 "암시적 변환" 을 통해서 해결 할 수 있지 않나 살펴봅니다.

살펴보다가   implicit 로 선언된 함수 functionToActionClass  를 찾았습니다. 이걸 가지고 시도해보니 잘됩니다.


네 

암시적 변환이란 컴파일러가 열심히 일해서 개발자가 성가신 일들을 하지 않게 도와주는 녀석이었습니다.

이제 클래스를 넣어도 잘되고 저렇게 람다식으로 넣어도 잘 될 것입니다. 



암시적 파라미터 (implicit parameter) 


개념


이것도 암시적 변환하고 매우 비슷합니다. 이름처럼 파라미터를 암시적으로 넣어 준다는것인데요.

즉 내가 넣지 않아도 자동으로 들어간다는 거겠죠? 


예를들어 

object Greeter {

def greet (name : String ) (implicit prompt : MyPrompt) {

println (name)

println (prompt.preference)

}

}

보시는 바와 같이 greet 는 두개의 파라미터를 갖습니다.  뒤에 것이 암시적 파라미터 인데요.


이렇게 호출 해 보겠습니다.

val prompt = new MyPrompt("ubuntu> ")

Greeter.greet("삐약") (prompt)

이건 직접적으로 prompt 객체를 만들어서 넣어주고 있습니다. 네 이건 명시적입니다. 당연히 작동하겠죠.  


그럼 암시적은 무엇인가??


Greeter.greet("삐약")   

이렇게 해도 잘 된다는 말인데요. 즉  뒤에 파라미터를 명시적으로 안 넣어 줘도 잘 된다는 거죠. 

이게 어떻게 되는거냐면요.
암시적으로 들어갈 파라미터는 다음과 같이  어딘가에 미리 만들어 놓습니다.

object HamaPrefs{

implicit val prompt = new MyPrompt (" ubuntu> ")

}


저게  있어야  컴파일러는 이 변수를 빠진 파라미터 목록에서 찾아서 적용해 주거든요.

정리하면

- 미리 변수 정의   : implicit val b = new B;
- 함수를 인자 없이 호출 :   test (a)
- 호출 당하는 함수쪽에 인자를 implicit 로 설정  : 
def  test(a :A , implicit b : B)  { }

입니다.  이렇게 DI 를 해놓으니깐 유연성이 커질거 같네요. 그쵸? 


마지막으로 implicit 키워드는 개별 파라미터가 아니라 전체 파라미터 목록을  범위로 합니다.

다음을 보시죠.

class MyTest1(val  tell : String)

class MyTest2(val tell : String)


object Greeter {

def greet (name : String) ( implicit  test  : MyTest1 ,  test2 : MyTest2 ) {

println(name)

println(test.tell)

println(test2.tell)  

    }

}

object HamaPrefs {

implicit val test = new MyTest1 ("삐약")

implicit val test2 = new MyTest2 ("개굴")

}


위 코드에서 test 와 test2 파라미터 모두 implicit 에 영향 받는다는 겁니다.  Greeter.greet ("HAMA")  이렇게 단일 파라미터를 던져도 나머지 암시적 파라미터를 찾아서 작동하지요. 

프레임워크등을 사용하다가 implicit 가 나오면 해당 프레임워크에서 그 인자에 대해서는 미리 만들어 뒀구나 라고 생각하면 편할거 같습니다.


implicit  와 폴리모피즘 

trait Factory[T] {
  def create: T
}

object Factory {
  implicit def stringFactory: Factory[String] = new Factory[String] {
    def create = "foo"
  }

  implicit def intFactory: Factory[Int] = new Factory[Int] {
    def create = 1
  }
}

object Main {
  def create[T](implicit factory: Factory[T]): T = factory.create
 
  // or using a context bound
 
  // def create[T : Factory]: T = implicitly[Factory[T]].create


  def main(args: Array[String]): Unit = {
    val s = create[String]
    val i = create[Int]

    println(s) // "foo"
    println(i) // 1
  }
}


Play2 웹 프레임워크에서의 implicit  

 

웹어플리케이션을 빠르고 즐겁게 만들 수 있는 cool 한 Play2 에서 implicit 는 적극적으로 사용됩니다.

반복을 줄이기 위해서 사용되는데요 예를 보면서 알아 보시죠. 

예를들어 쇼핑몰 싸이트에서 방문자의 장바구니에 상품이 몇개가 담겨져 있는지 보여주도록 모든 페이지의 

 상단에 쇼핑 카트를 유지 하길 원한다고 해보죠.  예) Shoping Cart : 3 Items (Show Cart) 



그러려면 모든 웹페이지 마다 cart 정보를 넘겨주어야합니다. 다음 소스를 보시죠.

* Shop.scala 

object Shop extends Controller {

def catalog() = Action { implicit request =>
val products = ProductDAO.list
Ok(views.html.products.catalog(products, cart(request))
}

def cart(request: Request) = {


// Get Cart from session

}


Shop 컨트롤러의 catalog 호출을 하는 세션으로 부터 Cart 정보를 얻은후에 

catalog 뷰 템플릿의 인자로 products  리스트와 함께 넘겨주고 있습니다.


* catalog.scala.html

@(products: Seq[Product], cart: Cart)

@main(cart) {

<h2>Catalog</h2>
<ul>
@for(product <- products) {
<li>
<h3>@product.name</h3>
<p class="description">@product.description</p>
</li>
}
</ul>

}

catalog 뷰 템플릿에서는 매개변수로 products  와 cart 를 받아서 , products 는 자신이 드로잉 하고 
cart 는 명시적으로  메인 뷰 템플릿으로 보내주네요.


* main.scala.html 

@(content: Html)(cart: Cart)
<!DOCTYPE html>
<html>
<head>
... 생략 ...
<span id="cartSummary" class="label label-info">
@cart.productCount match {
case 0 => {
Your shopping cart is empty.
}

case n => {
You have @n items in your shopping cart.
}
}
</span>

<div class="container">
@content
</div>

마지막으로 메인뷰 템플릿에서는 넘어온  catalog 뷰 템플릿과 cart 를 이용하여  페이지를 완성합니다.
cart 에 아무것도 없을 경우와 있을 경우를 case 로 분리해서 처리 하고 있군요.



위에 보다시피 이런 페이지가 많으면 많을 수록 일일이 객체를 넘겨줘야하는데요.

implicit 를 활용하면 그러지 않아도 됩니다. implicit  가 적용된 다음  소스를 보시죠.


* Shop.scala 


object Shop extends Controller with WithCart {

def catalog() = Action { implicit request =>
val products = ProductDAO.list
Ok(views.html.products.catalog(products))
}

}

cart 를 넘겨주는 코드가 사라 졌습니다. 대신 with WithCart 가 생겼군요. WithCart 는 제일 마지막에 살펴보겠습니다.


* catalog.scala.html

@(products: Seq[Product])(implicit cart: Cart)

@main() {

<h2>Catalog</h2>
<ul>
@for(product <- products) {
<li>
<h3>@product.name</h3>
<p class="description">@product.description</p>
</li>
}
</ul>

}

cart 매개변수 앞에 implicit 가 생겼으며 , 역시 main 뷰 템플릿으로 cart 를 명시적으로 넘겨주지 않습니다.


* main.scala.html 

@(content: Html)(implicit cart: Cart)
<!DOCTYPE html>
<html>
... 생략 ...


<span id="cartSummary" class="label label-info">
@cart.productCount match {
case 0 => {
Your shopping cart is empty.
}

case n => {
You have @n items in your shopping cart.
}
}
</span>

<div class="container">
@content
</div>

역시 cart 매개변수 앞에 implicit 가 생겼습니다. 

이렇게 cart 를 모든 페이지에서 보여주기 원할때 컨트롤러에서 cart 를 매번 세션에서 직접가져오는 반복이 사라졌습니다.


그럼 cart 는 어디로 사라졌을까요? 매직인가요?  아닙니다. Play2 와 스칼라는 이해하기 불가능한 마술같은
일이 거의 없습니다.
개발자 친화적이라고 할까요?


trait WithCart {

implicit def cart(implicit request: RequestHeader) = {
// Get a fake cart. In a real app, you'd get it from the session here.
Cart.demoCart()
}

}

 이런 trait 가 만들어져 있습니다. trait 는  자바 인터페이스 + 추상클래스와 비슷하다고 일단 생각하시면 되는데요. 

 위에 Shop.scala 에서 이것을 믹싱하고 있습니다.

object Shop extends Controller with WithCart {

결국 WitdCart 만 믹싱해주고  매개변수들을 implicit 로 선언해주기만하면 컴파일러가 알아서 적용시켜 주는 것이죠.


웹개발경험+Play2+Scala에 대한 기초지식이 없다면 어려울 수 도 있을거 같네요. :-| 


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

trait


*  Scala 는 interface 가 없으며 대신  trait 을 사용한다. 

*  Scala 의 trait 는 자바의 interface 와 달리 구현 가능하다. (자바8 부터는 자바인터페이스도 디폴트메소드등 구현)

*  하나의 부모클래스를 갖는 클래스의 상속과 달리 트레이트는 몇개라도 조합해 사용 가능하다. 

 * Scala 의 trait 는 자바의  인터페이스와 추상클래스의 장점을 섞었다. 

 * 트레이트를 믹스인 할때는 extends 키워드를 이용한다. 

 * extends 를 사용하면 trait 의 슈퍼클래스를 암시적으로 상속하고 , 본래 trait 를 믹스인한다. 

*  trait 는 어떤 슈퍼클래스를 명시적으로 상속한 클래스에 혼합할 수 있다. 

   그때 슈퍼클래스는 extends 를 사용하고, trait 는 with 로 믹스인한다. 


예1) 


trait Car {
  val brand: String
}

trait Shiny {
  val shineRefraction: Int
}
class BMW extends Car {
  val brand = "BMW"
}

클래스는 여러 트레잇를 with 키워드를 사용해 확장할 수 있다.

class BMW extends Car with Shiny {
  val brand = "BMW"
  val shineRefraction = 12
}


예2)



 trait Philosophical {
      def philosophize() {
        println("I consume memory, therefore I am!")
      }
    }

Listing 12.1 - The definition of trait Philosophical.


 class Frog extends Philosophical {

    override def toString = "green"
  }

 Listing 12.2 - Mixing in a trait using extends.


 scala> val frog = new Frog
  frog: Frog = green
  
 scala> frog.philosophize()
  I consume memory, therefore I am!

 scala> val phil: Philosophical = frog  
  phil: Philosophical = green
  
 scala> phil.philosophize()
  I consume memory, therefore I am!

 class Animal
  
 class Frog extends Animal with Philosophical {
   override def toString = "green"
 }

Listing 12.3 - Mixing in a trait using with.


class Animal
trait HasLegs
  
class Frog extends Animal with Philosophical with HasLegs {
  override def toString = "green"
}

Listing 12.4 - Mixing in multiple traits.

class Animal
  
class Frog extends Animal with Philosophical {
  override def toString = "green"
  override def philosophize() {
    println("It ain't easy being "+ toString +"!")
  }
 }

 scala> val phrog: Philosophical = new Frog
  phrog: Philosophical = green
  
 scala> phrog.philosophize()
  It ain't easy being green!

trait 는 클래스를 정의하면서 할 수 있는 모든것을 할 수 있다. 
문법 경우 2가지를 제외하고 정확히 같다.

첫째. 트레이트는 '클래스' 파라미터를 가질 수 없다.
둘째 클래스는 super 호출을 정적으로 바인딩하지만, 트레이는 동적으로 바인딩한다.

class Point(x: Int, y: Int)
trait NoPoint(x: Int, y: Int) // trait 는 클래스 파라미터를 가질수 없다. 


trait   vs  추상클래스 


스택 오버플로우에서 아래를 참고 하라. 

트레잇과 추상 클래스의 비교
추상 클래스와 트레잇의 차이
스칼라 프로그래밍: 트레잇냐 아니냐 그것이 문제로다?  

자바8에서 인터페이스 기능이 대폭 증가한것을 기점으로  스칼라  trait vs 자바 interface 비교를 해야 할 듯싶다.


Orderd trait 


  class Rational(n: Int, d: Int) {
    // ...
    def < (that: Rational) = 
      this.numer * that.denom > that.numer * this.denom
    def > (that: Rational) = that < this
    def <= (that: Rational) = (this < that) || (this == that)
    def >= (that: Rational) = (this > that) || (this == that)
  }


  class Rational(n: Int, d: Intextends Ordered[Rational] {
    // ...
    def compare(that: Rational) =
      (this.numer * that.denom) - (that.numer * this.denom)
  }


  scala> val half = new Rational(12)
  half: Rational = 1/2
  
scala> val third = new Rational(13) third: Rational = 1/3
scala> half < third res5: Boolean = false
scala> half > third res6: Boolean = true


  trait Ordered[T] {
    def compare(that: T): Int
  
  def <(that: T): Boolean = (this compare that) < 0   def >(that: T): Boolean = (this compare that) > 0   def <=(that: T): Boolean = (this compare that) <= 0   def >=(that: T): Boolean = (this compare that) >= 0 }



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

case class


case 클래스 예제 

abstract class Expr

case class Var(Name: String) extends Expr

case class Number(num: Double) extends Expr 

case class BinOp(operator: String, left:Expr , right:Expr) extends Expr


* 스칼라에서 클래스 본문이 비어 있으면 중괄호를 생략할 수 있다. 


case 클래스 특징 


* 컴파일러는  클래스 이름과 같은 이름의 팩토리 메소드를 추가한다.

new Number(3.0) 대신해서 Number(3.0) 가능하다.

* 케이스 클래스의 파라미터의 목록을 val 접두사를 붙인다. 

val n = Number(3.0) 

n.num // 3.0 

* toString, hashCode,equals 메소드들의 자동으로 추가된다. 

val v = Var("x") 

val op = BindOp("+", Number(1), v) 

op.right == Var("x") // true 

* 컴파일러는 케이스 클래스에서 일부를 변경한 복사본을 생성하는 copy 메소드를 추가한다. 

    operator 만 바꾸고 op 같은 연산자를 만드는 법 

    op.copy(operator = "-")   // BindOp = BindOp(-, Number(1.0), Var(x)) 


패턴 매치

    * switch { case } 가 스칼라에서는 셀렉터 match { case }  와 같다.

    * 스칼라의 match 는 표현식이다.

    * 스칼라의 case 는 다음 case 로 fall through 하지 않는다.

    * 매치에 성공하지 않는 경우 MatchError 예외가 발생한다. 따라서 디폴트 케이스를 반드시 추가해야한다. 


    expr match {

case BinOp ( op, left, right) =>

println( ... ) 

case _ =>

    }


와일드 카드 패턴  
  expr match {
    case BinOp(op, left, right) =>
      println(expr +"is a binary operation")
    case _ =>
  }
 expr match {
    case BinOp(_, _, _) => println(expr +"is a binary operation")
    case _ => println("It's something else")
  }

상수  패턴  

 def describe(x: Any) = x match {
      case 5 => "five"
      case true => "truth"
      case "hello" => "hi!"
      case Nil => "the empty list"
      case _ => "something else"
    }
 scala> describe(5)
  res5: java.lang.String = five
  
scala> describe(true) res6: java.lang.String = truth
scala> describe("hello") res7: java.lang.String = hi!
scala> describe(Nil) res8: java.lang.String = the empty list
scala> describe(List(1,2,3)) res9: java.lang.String = something else

변수 패턴 

expr match {
      case 0 => "zero"
      case somethingElse => "not zero: "+ somethingElse
    }
 scala> import Math.{E, Pi}
  import Math.{E, Pi}
  
scala> E match {          case Pi => "strange math? Pi = "Pi          case _ => "OK"        } res10: java.lang.String = OK
 scala> val pi = Math.Pi
  pi: Double = 3.141592653589793
  
scala> E match {          case pi => "strange math? Pi = "+ pi        } res11: java.lang.String = strange math? Pi = 2.7182818...
 scala> E match {
           case pi => "strange math? Pi = "+ pi
           case _ => "OK"  
         }
  <console>:9: error: unreachable code
           case _ => "OK"  
                     ^
 scala> E match {
           case `pi` => "strange math? Pi = "+ pi
           case _ => "OK"
         }
  res13: java.lang.String = OK

생성자 패턴 

expr match {
      case BinOp("+", e, Number(0)) => println("a deep match")
      case _ =>
    }

시퀀스 패턴 

 expr match {
      case List(0, _, _) => println("found it")
      case _ =>
    }
  expr match {
      case List(0, _*) => println("found it")
      case _ =>
    }

튜플 패턴 

def tupleDemo(expr: Any) =
      expr match {
        case (a, b, c)  =>  println("matched "+ a + b + c)
        case _ =>
      }

타입지정 패턴 

def generalSize(x: Any) = x match {
      case s: String => s.length
      case m: Map[_, _] => m.size
      case _ => -1
    }
Listing 15.11 - A pattern match with typed patterns.

Here are a few examples of using the generalSize method in the interpreter:

  scala> generalSize("abc")
  res14: Int = 3
  
scala> generalSize(Map(1 -> 'a'2 -> 'b')) res15: Int = 2
scala> generalSize(Math.Pi) res16: Int = -1

Sealed class

  sealed abstract class Expr
    case class Var(name: Stringextends Expr
    case class Number(num: Doubleextends Expr
    case class UnOp(operator: String, arg: Exprextends Expr
    case class BinOp(operator: String, 
        left: Expr, right: Exprextends Expr
Listing 15.16 - A sealed hierarchy of case classes.

Now define a pattern match where some of the possible cases are left out:

  def describe(e: Expr): String = e match {
    case Number(_) => "a number"
    case Var(_)    => "a variable"
  }

You will get a compiler warning like the following:

  warning: match is not exhaustive!
  missing combination           UnOp
  missing combination          BinOp



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret



Scala 에서 Enumeration 사용하기

http://alvinalexander.com/scala/how-to-use-scala-enums-enumeration-examples 참조


문제 )  스칼라에서 enumeration 을 사용하고 싶습니다.(상수로서 사용되는 문자열 ) 

해결책 1 )  Enumeration 클래스를 사용해서 열거형 만들기 

scala.Enumeration 클래스를 확장하세요.

package com.acme.app {

    object Margin extends Enumeration {
        type Margin = Value
        val TOP, BOTTOM, LEFT, RIGHT = Value
    }

}

그리고 이것을 import 로 가져가서 사용하심 됩니다

object Main extends App {

    import com.acme.app.Margin._

    // use an enumeration value in a test
    var currentMargin = TOP
    
    // later in the code ...
    if (currentMargin == TOP) println("working on Top")

    // print all the enumeration values
    import com.acme.app.Margin
    Margin.values foreach println

}

해결책 2 )  traits / case 를 사용해서 열거형 만들기  

// a "heavier" approach
package com.acme.app {
    trait Margin
    case object TOP extends Margin
    case object RIGHT extends Margin
    case object BOTTOM extends Margin
    case object LEFT extends Margin
}

Enumeration  의 문제점 - 1


메소드 오버로딩에 문제가 생긴다. 아래를 보자.
/**
* Created by brad on 2016-09-30.
*/

import scala.collection._
import scala.util.parsing.json._


object Colours extends Enumeration {
val Red, Amber, Green = Value
}

object WeekDays extends Enumeration {
val Mon,Tue,Wed,Thu,Fri = Value
}

object Functions {
def f(x: Colours.Value) = println("That's a colour")
def f(x: WeekDays.Value) = println("That's a weekday")
}


object test {

import Colours._

def main(arg : Array[String]): Unit ={
Functions.f(Red)
}

}

Error:(19, 7) double definition:

method f:(x: WeekDays.Value)Unit and

method f:(x: Colours.Value)Unit at line 18

have same type after erasure: (x: Enumeration#Value)Unit

  def f(x: WeekDays.Value) = println("That's a weekday")

이 경우에 에러가 발생한다.  Red 라고 명시했지만 구분 할 수 없다.




WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

Getter 와 Setter 


객체지향 프로그래밍에서 게터와 세터는 의도치 않게 이제 기본이 된  내용들 중 하나이지만 ( getter / setter 자체를 그냥 public 으로 변수 선언하는것과 마찬가지로 나쁘게 보는 시각도 있습니다. 객체지향은 외부 노출을 줄여야 한다고 보는데 게터,세터는 절차지향 마인드의 산물 ) 때로는 쓰기 귀찮아질 때 도 있긴합니다. 대부분의 게터와 세터는 매우 비슷하기 때문에 같은 기능을 하는 더 나은 방법이 있을거란 생각은 매우 타당 할 것이며 C# 에서는 그것을 위해 특별히 "프로퍼티"라는 것을 만들어서  아래와 같이 사용됩니다.

    private String strName;
 
    public String StrName
    {
        get { return this.strName; }
        set 
        {
               ... 제어 .. 
              this.strName = value; 
        }
    }

자바에서는 언어차원에서 편하게 쓰게 하기위한 장치는 없고(Java 1.7 에 @property  같은거 만들거란 말은 있었는데 안한듯?)  IDE 차원이나 Lombok 같은 툴의 도움을 받습니다. 

관련 읽을 거리:

왜 자바에서 getter / setter 는 악마인가  (영어)

* 왜 getter / setter 를 사용하는가  (영어)

* 정보은닉과 캡슐화의 차이   (한글)

* 홀럽 아저씨의 실용주의 디자인패턴 책에  왜 getter/setter 가 안 좋은지 나온다. ( 위 블로그의 한글버전)   

 게터,세터는 객체지향과 거리가 있으며  데이터의 흐름이 적을 수록 유지보수에 좋다라고 말 하고 있습니다. 즉 pull  해서 쓰지 말고  push  해서 쓰라는 지침. 즉 가져와서 하지 말고 맡겨라!  데이터베이스와 접점 부분등 몇몇 예외상황에 대해서도 말 합니다. 


스칼라에서의  Getter / Setter 

다음 첫번째 예를 보면 :  

class Person() {  
 var name = "" 
 var age = 0 
} 

첫눈에 보기에 이 클래스가 단지 사람 객체를 만들고 그 이상 아무것도 없어 보이겠지만 
사실 인스턴스화 되면 다음과 같이 get / set 을 할 수 있게 됩니다.

GET 

//person 객체 만들고 
person = new Person() 

// age 과 name 속성을 출력한다. 
println(person.age)  
println(person.name)  

SET

// Set the properties to different values 
person.age = 34  
person.name = "Dustin Martin"  

매우 간단한데  이 예를 보면 이렇게 생각 하실 듯 합니다.  이것은  getters 와 setters 가 아니잖아! 


자동으로 생성되는 Getter / Setter 

스칼라에서는 어떤 객체의 멤버 중 비공개가 아닌 모든 var 멤버에 게터와 세터 메소드를 자동으로 정의해 줍니다. 이때 자동으로 정의해 주는 이름은 자바의 관례와 다르며 age 의 게터는 그냥 age 이고 세터는 age_= 입니다. 이때 필드에는 자동적으로 private[this] 가 붙는데 그 필드를 포함하는 객체에서만 접근할 수 있다는 의미이고 자동으로 만들어진 게터와 세터는 원래의 age 와 같은 가시성이됩니다. 즉 age 가 공개이기 때문에 공개가 된다는 뜻 


 자신만의 Getter / Setter  정의 하기 - (1)

여기서 만약 age 속성에 0살~200살까지의 범위를 확인하는 절차가 필요할때 어떤 일이 발생 할까?
name 에 특정한 방식으로 포맷을 정하고 싶을때 어떻게 할까?  

스칼라에서 다루는 방법을 알아보겠습니다.

class Person() {  
 // age 속성을 private 로 바꾸고 , _age 으로 이름을 바꾸자.  
 private var _age = 0 
 var name = "" 

 // Getter 
 def age = _age 

 // Setter 
 def age_= (value:Int):Unit = _age = value 
} 

매우 간단한 코드인데. 첫번째로 "age" 를 "_age" 로 이름을 바꾸고 private 를 적용하였습니다. 

GET 

def age = _age  

변수를 리턴하는 age 메소드를 정의하였습니다.  스칼라는 return 키워드를 요구하지 않습니다. 

또한 Scala 는 메소드의 몸체에 괄호를 사용하는것이 의무가 아닙니다.

SET

다음엔 setter 를 살펴보죠.

def age_= (value:Int):Unit = _age = value  

-  메소드 이름은  "age_="  입니다.
- :Unit 는 void 를 리턴하다는 것과 동등하며 생략가능합니다.  
- priavate 변수인 _age 에 새 값인 value 를 대입한다는 뜻입니다.

아직까지 먼가 제한을 두는 코드가 들어 있지는 않았습니다. 이제 제한을 넣어 보죠. 


 자신만의 Getter / Setter  정의 하기 - (2)

class Time {

 private [this] var h = 12

 private [this] var m = 0

 def hour : Int = h  // getter

 def hour_= (x : Int) { //setter

   require (0 <= x && x < 24) 

   h = x

 }


 def minute = m  // getter

 def minute_= (x: Int) {  // setter 

   require(0 <= x && x < 60)

   m=x

 }
}
 이제 Set 을 할 때 값을 점검합니다. 여기서는 시간과 분이 특정 값 내에 있어야한다고 제한하고 있네요.

 

자신만의 Getter / Setter  정의 하기 - (3)

class Termometer {

   var celsius : Float = _   // celsius 의 getter/setter 는 디폴트구현을 사용

   def fahrenheit = celsius * 9 / 5 + 32  //fahrenheit 는 메소드만 존재해도 됨

   def fahrenheit_= (f : Float) {    

     celsius = ( f - 32 ) * 5 / 9

   }

}
--------------------------------------------

var t  = new Termometer

t.celsius = 100

t.fahrenheit = -40

 fahrenheit 같이 변수를 명시적으로 선언하지 않고도 getter / setter 로 사용할 수 있습니다.

celsius 를 var 로 정의하며 초기화 값으로 _ 를 주었는데 이것은 디폴트값으로 영을 할당합니다.
영은 숫자에서는 0 이고 불리언에서는 false 이며 레퍼런스에서는 null 입니다자바에서 초기화 하지 않았을때 할당되는 동일한 규칙 이며 스칼라에서는 초기화를 생략할 수 없습니다. 만약 초기화 하지 않는다면 그건 추상변수를 선언해버립니다.  추상변수는 다음에 알아보시죠.

Getter / Setter  애노테이션

위에 보다시피 보통 스칼라 코드에서는 명시적인 필드 get,set 메소드가 필요 없습니다. 그냥 디폴트 구현을 사용하다가 제한이 필요하거나 할때 재정의해서 사용할 수 가 있게 되죠. 그때 모든 코드들을 찾아다니며 변경할 필요가 없게 됩니다.  하지만 플랫폼에 따라 몇몇 프레임워크는 get,set 메소드를 꼭 필요로 하는 경우가 있기때문에 이를 위해 스칼라는 @scala.reflect.BeanProperty 애노테이션을 제공합니다.

필드에 이 애노데이션을 추가하면, 컴파일러가 자동으로 get,set 메소드를 만들어 줍니다.  예를 들어 crazy 필드를 애노테이션하면, 자바처럼  get메소드는 getCrazy가, set 메소드는 setCrazy 가 됩니다.




WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

함수와 메소드

이번 포스트 내용은 지금까지 그리고 앞으로 나올 강좌중에서 가장 중요한 포스트라고 생각합니다.   
스칼라에서 함수/메소드는 그 만큼 중요합니다.  본 포스트의 내용이 불분명하거나 모자르면 반드시 
다른 곳에서 보충하시길 바랍니다.


스칼라에서 함수종류 

* 객체의 멤버로 있는 함수인 메소드
* 함수안에 정의한 내포 함수
* 함수 리터럴
* 함수 값 


메소드  


특성 

스칼라 메소드 파라미터에서 중요한 점은 이들이 val 이라는것이다.

  

  def add(b : Byte) : Unit = {

b = 1  // 파라미터가 val 이라 불가능 !

sum += b

   }


 * return 을 명시적으로 사용하지 말라. 

   스칼라는 메소드가 한 값을 계산하는 표현식인 것 처럼 생각하는걸 권장한다.

 * 메소드가 오직 하나의 표현식만 계산하는 경우 {} 중괄호를 없앨 수 있다.

   def add(b:Byte) : Unit = sum += b


 * 메소드의 결과 타입이 Unit 인 경우는 부수 효과를 위해 사용한다는 뜻이다. 

 

 * Unit 생략하고 = 를 없애서 {} 로 메소드를 나타낼 수 있는데, 이건 프로시저 처럼 보이게 하는데 
   프로시저란 오직 부수 효과를 얻기 위해서만 사용하는 메소드를 의미한다. 


   즉 def add(b: Byte) { sum += b}  처럼 사용한다는 뜻이데, 여기서 함정이 있는게 


   def add2 () { " test string " }  이건 Unit 를 리턴하며 
   def add3 () = { " test string " }  이건 String 을 리턴한다는 것이다. 


   즉 프로시저 처럼 작성하면 리턴이 무조건 Unit 이 된다는점~ 


  * 표현식 블럭 즉 {} 를 이용해서 호출도 가능하다. 블럭에서 실행된 값이 매개변수로 들어감
    (스칼라에서 이 문법 익숙해지기 전까지 매우 헤깔림) 


def formatEuro (amt : Double) = f"$amt%.2f" 

formatEuro { val rate = 1.32; 0.235 + 0.7123 + rate * 5.32 }   <-- 이런식으로 '호출'


예제 1


import scala.io.Source

object LongLines {

def processFile(filename : String, width : Int ) {
  val source = Source.fromFile(filename)
  for (line <- source.getLines())
  processLine(filename, width, line)
}

private def processLine(filename: String, width: Int, line: String)  {
  if (line.length > width)
  println(filename + ": " + line.trim)
}
}


두개의 메소드로 이루어져 있다.



지역 함수

특성 

함수안에 함수를 정의 할 수 있다.


예제 2 (예제 1 의 변형 ) 

 

import scala.io.Source

object LongLines {

def processFile(filename : String, width : Int ) {

   def processLine(filename: String, width: Int, line: String)  {
      if (line.length > width)
      println(filename + ": " + line.trim)
    }

    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(filename, width, line)
  }

}


도우미 함수들은 프로그램의 네임스페이스를 오염 시킬 수 있기 때문에  자바에서는 주로 비공개 메소드를 이용해 이런 목적을 달성하지만 스칼라에서는 함수안에 함수를 정의해서 처리 하기도 한다. 

함수안의 함수는 그 외부 함수안에서만 접근 할 수 있다. 


예제 3 (예제 2 의 개선 ) 


import scala.io.Source

object LongLines {

  def processFile(filename : String, width : Int ) {

   def processLine(line: String)  {
     if (line.length > width)
      println(filename + ": " + line.trim)
   }

   val source = Source.fromFile(filename)
   for (line <- source.getLines())
     processLine(filename, width, line)
  }

}



내부의 함수에서 바깥 함수의 인자를 사용한다.



1급 계층 함수

특성 

스칼라는 1급 함수 (first class function) 를 제공한다. 1급이란 말처럼 상위계층 즉 모든게 가능한.. 할당도 가능하고 , 인자로도 넘길 수 있고, 반환도 가능한 애들 말한다. 


이들은 이름 없이 리터럴로 표기해 값 처럼 주고 받을 수 있다.

함수 리터럴은 클래스로 컴파일하는데, 해당 클래스를 실행 시점에 인스턴스화하면 함수 값이 된다.  

모든 함수 값은 FunctionN 트레이트 중 하나를 확장한 클래스로부터 만든 인스턴스이다.

예를들어 Function() 은 인자가 없는 함수고, Function1 은 인자가 1개인 식이다. 

여기에는 함수 호출시 사용하는 apply 메소드가 들어 있다.


다음 예와 같다.



1
2
3
4
5
trait Function1[ T1, R] extends AnyRef {
    ...
    def apply( v1 :T1) : R
    ...
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Last( init : Int ) extends Function1[Int,Int]{
    var last = init
 
    def apply( x :Int ) : Int = {
        val retval = last
        last = x
        retval
    }
}
 
val last : Int => Int   =   new Last( 1 )
 
val x = last( 5 )     // x == 1
val y = last( 5 )     // y == 5
val z = last( 8 )     // z == 5
 ...
val a = last( 5 )     // a == 8



함수 리터럴  (간단 예제)  


(x: Int) => x + 1


정수 x 를 x + 1 로 매핑하는 함수이다. 

함수 값은 객체이기 때문에 원하면 변수에 저장 할 수 있다.

동시에 함수 값은 엄연히 함수이기도 하다. ( 괄호를 이용해 실행 할 수 있다는 야그) 


var increase = (x : Int) => x + 1     //  함수를 increase 변수에 저장가능!

increase(10)  // 호출 가능 !  내부적으론 apply 메소드가 사용됨. 



함수 리터럴 ( 둘 이상의 문장 예제) 


increate = (x: Int ) => {

println("...")

println("...")

x + 1

}


둘 이상의 문장이 필요하면 {} 중괄호로 감싸서 블록으로 만든다. 



함수 리터럴 ( 매개변수로 사용) 


val numbers = List (1,2,3,4,5)

numbers .foreach((x:Int) => println(x)) 


다음처럼 함수의 매개변수로 들어가서 사용된다. 

이제는 대부분의 언어에서 상식이 되버린 map, filter 함수에서도 사용된다. 


scala> numbers.map((i: Int) => i * 2)
res0: List[Int] = List(2, 4, 6, 8, 10)
scala> numbers.filter((i: Int) => i % 2 == 0)
res0: List[Int] = List(2, 4)


간단한 형태의 함수 리터럴 

scala> numbers.filter( i => i % 2 == 0)
res0: List[Int] = List(2, 4)


애초에 컬렉션에서 i 는 정수라는 하실을 알기 때문에 굳이 Int 라고 하지 않아도 된다.

타깃 타이핑이라고 하며 () 도 생략 가능하다. 


더 간단한 형태의 함수 리터럴 

scala> numbers.filter( _ > 4)
res0: List[Int] = List(5)

_ 는 함수가 호출 시 전달받은 인자로 하나씩 채워져서 계산될 것이다.  




기타 특성  

반복 파라미터 

def echo (args: String*) = for (arg <-args) println(arg)    //   매개변수에 * 를 표시함으로써 

echo("hello", "world")  // 요렇게 인자의 갯수를 자유롭게 할 수 있다. 


이름 있는 인자 

def speed(distance: Float, time: Flot) : Flot = distance / time 

speed(100,10) // 일반적 사용

speed(time = 10, distance = 100) // 이름을 함께 사용, 순서를 바꾸어서 사용해도 된다.


디폴트 값

def printTime(out: java.io.PrintStream = Console.out) = out.println("time = " + System.currentTimeMillis()) 

printTime() //  이렇게 호출하면 자동적으로 디폴트로 out 은 Console.out 이 된다. 


클로저

 ( 나중에 따로 다루겠습니다 )




def 와 val 의 차이 

def even: Int => Boolean = _ % 2 == 0
val even: Int => Boolean = _ % 2 == 0

even(10) // 둘다 이렇게 호출 가능


위에서 배운 바와 같이 함수는 이렇게 표현 가능한데요. 둘의 차이는 멀까요? 


메소드 def even 은 호출 되었을때 평가되며 새로운 Function1 객체를 매 호출시마다 생성합니다.

반면 val 은 정의 되었을때 평가됩니다. 

val test: () => Int = {  // 이미 평가됨  (리턴값이 항상 같을 경우 사용 할 수 있다) 
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1049057402
test()
// Int = -1049057402 - 동일한 값 

def test: () => Int = {   // 호출할때마다 평가가 달라짐 
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -240885810
test()
// Int = -1002157461 - 값이 달라 졌다. 

* lazy val 로 선언하면 이름에서 느껴지는것 처럼 정의 되었을대 평가되는게 아니라 처음 호출시 평가됩니다.


스칼라에서의 함수 호출


스칼라에서는 함수호출시 ()  대신 {} 중괄호를 사용 할 수 있다.  단 !!! 인자가 하나일 경우 !!


def formatEuro (amt : Double) = {.......}   <-- 와 같은 Double 형의 매개변수 하나를 받는 함수가 있을 시

formatEuro { val rate = 1.32; 0.235 + 0.7123 + rate * 5.32 }   <-- 이렇게 호출 할 수 있는데, {} 블럭 안에서 평가된 rate 가 매개변수로 들어가게 된다.


더 간단하게 예를 들면


println ("Hello World") 대신해서 println { "Hello World" } 를 사용 할 수 있다는 의미이다.


스칼라에서 이렇게 한 이유는 프로그래머가 중괄호{} 내부에서 함수 리터럴을 직접 사용하도록 하기 위해서인데, 

이렇게 작성한 메소드는 호출 시 좀 더 제어 추상화 구문과 비슷해진다. 좀 더 수준높은 예제를 들어 보면 


def withPrintWriter(file: File, op: PrintWriter => Unit ) {   


val writer = new PrintWriter(file)

try {

    op (writer)

} finally {

    writer.close()

}


}


이런 함수를 호출 할 때는 


withPrintWriter ( new File ("date.txt") , writer => writer.println(new java.util.Date) ) 


이렇게 호출 하며, 여기서는 인자가 2개이기 때문에 { } 를 사용할 수 없다. 하지만 !!!!


def withPrintWriter(file: File) (op: PrintWriter => Unit ) { ...


}


이러한 함수라면 {} 를 사용 할 수 있다.


val file = new File("date.txt")

withPrintWriter  (file) { writer => writer.println(new java.util.Date) }


왜 이게 되냐면  커링을 통해서 file 을 매개변수로 먼저 넣어진 새로운 함수가 생겨졌고, 

그 새로운 함수는  매개변수 하나를 필요로 하는 함수이기 때문이다.!! 

보통 (변수,함수) 매개변수 조합일 때 많이 사용된다.


def foldLeft[B](z: B)(op: (B, A) => B): B = {


  var result = z


  this foreach (x => result = op(result, x))


  result


}


Vector(1, 2, 3).foldLeft(Vector[Int]()) { (is, i) =>


  is :+ i * i


}


(curry vs partially applying 이라는 헥깔림 주제도 있다)





웹 MVC 컨트롤러에서의 예  (Play for Scala)  


스프링에서 컨트롤러 함수의 예가  다음과 같은 모습을 취하는 반면 

 @RequestMapping(method = RequestMethod.GET)
   public String printHello(ModelMap model) {
      model.addAttribute("message", "Hello Spring MVC Framework!");
      return "hello";
   }

스칼라언어 기반 Play 프레임워크 웹개발에서는 (위의 자바 예와 동일한 내용의 예가 아닙니다) 

def doSomething = Action{
                          Ok.apply(views.html.index("Hi there"))
                  }

이런 모양새로 이루어지는데 여기서 Action 이 무엇일까요? 


위의 예는 다음과 같이 풀어지게 되는데요

def doSomething: Action[AnyContent] = Action.apply({
  Ok.apply(views.html.index("Hi there"))
})

위의 apply 메소드의 정의는  apply(block: => Result) 이라 익명의 함수가 들어가게 됩니다.  

Action.apply 함수의 () 는 생략가능이고 apply 함수내에는 인자가 없고 Result 를 리턴하는 함수가 들어가네요.
이 함수 말이죠. 

{
  Ok.apply(views.html.index("Hi there"))
}

그리고 doSomething 메소드는  Action[AnyContent]  클래스의 인스턴스를 리턴합니다.

항상 같은 값을 리턴하기때문에 def 을 빼도 됩니다.  즉 메소드가 아니라 val 이어도 된다는 말.


Action { request =>
  Ok("Got request [" + request + "]")
}

처럼 인자가 있는 함수가 들어가기도 합니다.


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

클래스와 객체 

   


 클래스,필드,메소드
  
 객체 생성
  클래스는 객체의 청사진이다.
  class ChecksumAccumulator {
  } 

  이런 클래스 가 있을때  

  new ChecksumAccumulator  // 이렇게 해주면 객체를 만들어 진다.
  클래스 안에는 필드와 메소드를 넣을 수 있다. 이 둘을 합쳐 멤버라고 한다.

  

  필드는 var 이나 val 로 정의하며

  메소드는 def 로 정의 한다.


  class ChecksumAccumulator {
       var sum = 0 

  } 

 위의 클래스를 가지고 객체를 2개 만들면 


 val a = new ChecksumAccumulator 

 val b = new ChecksumAccumulator

 

 해당 객체안의 sum 필드는 다른 메모리를 참조 할 것이고 같은 0 을 바라 볼 것이다.


 sum 이 var 이기 때문에, 나중에 다른 Int 값을 재할당 할 수 있다.

 a.sum = 3 // 요렇게 


 객체가 val 인데도 불구하고 내부의 값을 바꿀 수 있다는 점을 체크하라. 

 다만 처음 생성한 그 객체를 바라보고 있다는 확신은 가질 수 있다. 


 필드 접근성  

* 필드를 비공개로 만들어서 직접 접근하지 못하게 하라. private 을 사용한다. 

    - 아무것도 안붙히면 전체 공개. (자바와 다르다) 



 메소드 특성 

스칼라 메소드 파라미터에서 중요한 점은 이들이 val 이라는것이다.

  def add(b : Byte) : Unit = {

b = 1  // 파라미터가 val 이라 불가능 !

sum += b

   }


 * return 을 명시적으로 사용하지 말라. 

   스칼라는 메소드가 한 값을 계산하는 표현식인 것 처럼 생각하는걸 권장한다.

 * 메소드가 오직 하나의 표현식만 계산하는 경우 {} 중괄호를 없앨 수 있다.

   def add(b:Byte) : Unit = sum += b


 * 메소드의 결과 타입이 Unit 인 경우는 부수 효과를 위해 사용한다는 뜻이다. 

 

 * Unit 생략하고 = 를 없애서 {} 로 메소드를 나타낼 수 있는데, 이건 프로시저 처럼 보이게 하는데 
   프로시저란 오직 부수 효과를 얻기 위해서만 사용하는 메소드를 의미한다.