Promise

이전 블로그글에서 Future 에 대해서 먼저 읽고 오자. (안읽으셨다면..) 
스칼라의 Promise 는 Future 의 일반화라고 볼 수 있다. (스칼라의 Promise 다. 다른 언어 라이브러리들이 모두 Future, Promise 등에 대한 정의/구현이 조금씩 다를 수 있다. 요즘 처럼 동시성 이슈가 많은 시절에는 원할한 의사 소통을 위해 구분 지어야 할 것이다)  따라서 Future 에 대해서 학습했다면 쉽게 이해 가능하다. (여기서 일반화라는 말이 너무추상적이라 헤깔릴수 있을 텐데 좀 참아보자.)

Future 에서는 보통 위임 행동이 강결합 되있었다. 즉 future.( 행동 )  
하지만 Promise 는 Promise 만 먼저 선언해두고 나중에 success 를 호출해서 완료된것을 알려준다.
(자바의 CompletableFuture라는 작명이 오히려 더 잘 설명해 주는거 같다. Promise는 완료 시점을 스스로 알려주는 Future라고 볼 수 있다.) 


다음 예를보자.

Promise - 1

import scala.concurrent._
import ExecutionContext.Implicits.global

val p = Promise[String]

p.future foreach {
case text => log(s"Promise p succeeded with '$text'")
}

p success "kept"

- Promise 를 미리 만들어 두었다.
- Promise 가 완료되면 진행할 행동을 foreach 의 케이스 매칭으로 만들어 두었다.
- success 를 호출해서 완료 및 인자를 전달한다.

q failure new Exception("not kept")

q.future.failed foreach {
case t => log(s"Promise q failed with $t")
}

- p sucess 가 아니라 p failure 를 호출하여 실패 상황을 알려 줄 수도 있따.


Promise - 2

import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.util.control.NonFatal

def myFuture[T](body: =>T): Future[T] = {
val p = Promise[T]

global.execute(new Runnable {
def run() = try {
val result = body
p success result
} catch {
case NonFatal(e) =>
p failure e
}
})

p.future
}

val future = myFuture {
"naaa" + "na" * 8 + " Katamari Damacy!"
}

future foreach {
case text => log(text)
}

- 먼저 Promise 객체를 행동과 연결되는게 없이 만든다.
- 어떤 행동을 하게 하고 바로 Future 를 리턴시켜준다.
- 어떤 행동 (registerOnCompleteCallback) 안에서 어떤 행동이 완료되었다면 success 라는 메소드를 실행시켜서 먼저 리턴한 Future 에게 complete 신호를 보낸다. 

아직도 헥깔리실까봐 말하자면 Future 는 자신의 쓰레드를 생성하고 , 행위를 강결합해서 실행했다면  Promise 는 전혀 그러하지 않다. 그냥 다른 쓰레드 (위에서는 ForkjoinPool) 에 스며들어가서 완료시점과 전달값을 세팅만 했을뿐이다.


취소할 수 있는 Promise

퓨처 계산을 중간에 멈추고 싶을 경우가 있다. 사용자가 취소버튼을 눌렀다던가 .. 뭐 그럴 경우 중간에 멈추게 하고 거기 까지의 값을 원할 수 도 있고 등등 ..이럴때는 취소를 위한 퓨처를 다른 퓨처와 함께 조합해서 사용하는 방법인데 이게 꽤 헥깔리다. 

소스를 보자.

def cancellable[T](b: Future[Unit] => T): (Promise[Unit], Future[T]) = {
val p = Promise[Unit]
val f = Future {
val r = b(p.future)
if (!p.tryFailure(new Exception))
throw new CancellationException
r
}
(p, f)
}

val (cancel, value) = cancellable { cancel =>
var i = 0
while (i < 5) {
if (cancel.isCompleted) throw new CancellationException
Thread.sleep(500)
log(s"$i: working")
i += 1
}
"resulting value"
}

Thread.sleep(1500)

cancel trySuccess ()

log("computation cancelled!")

솔직히 이 쯤되면 그냥 저레벨 쓰레드 사용해서 대충 짜고 싶은 마음이 들기도 한다. 세상 살면서 이런 코드를 얼마나 짜겠다고.. 이걸 쉽게 이해하고 체득하려고 노력까지 해야하나 싶다.

이 코드는 말로 설명하긴 어렵겠다. 각자 생존하자. -.-;; (아래 레퍼런스의 책을 천천히 읽으시라 권유하고 싶다).  

힌트 하나만 주자면 Promise p 가 취소용으로 사용됬다는 것이다. 즉 그 동안의 코드와 다르게 사용자 측에서 Promise 을 제어한다. trySuccess() 를 사용해서~~


Await

어느 순간에는 비동기의 연속보다는 무엇인 종료될때까지 기다리고 싶을 때도 있다. 그때 사용된다.

Await.result

import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.io.Source

val urlSpecSizeFuture = Future { Source.fromURL("http://www.w3.org/Addressing/URL/url-spec.txt").size }
val urlSpecSize = Await.result(urlSpecSizeFuture, 10.seconds)

log(s"url spec contains $urlSpecSize characters")

Await.result 에서 웹싸이트를 다 긁어 올때까지 기다린다.  10초만 기다린다.


Await.result

val startTime = System.nanoTime

val futures = for (_ <- 0 until 16) yield Future {
Thread.sleep(1000)
}

for (f <- futures) Await.ready(f, Duration.Inf)

val endTime = System.nanoTime

log(s"Total execution time of the program = ${(endTime - startTime) / 1000000} ms")
log(s"Note: there are ${Runtime.getRuntime.availableProcessors} CPUs on this machine")

모든 퓨터가 완료 될 때까지 무한정 기다린다.
Await.ready 는 리턴값이 없다.

16개의 퓨쳐가 실행완료 될 때까지 얼마나 걸리는지 재는 코드이다.
저게 시퀀셜하게 동작했다면 16초이상 걸렸겠지만 내 PC에서는 아래와 같은 결과가 나왔다.

main: Total execution time of the program = 4451 ms (대략 4초)

main: Note: there are 4 CPUs on this machine ( 4코어) 



레퍼런스

스칼라 동시성 프로그래밍 - 오현석 옮김



* 이 글은 일단 발행은 하는데 오류가 있을 가능성도 있으며 수정&발전 될 것입니다. 


동시성을 위한 스칼라 Observable 

스칼라의 Observable 을 배우기 전에 여러가지 것들에 대해서 편하게 읽어보자.

Play 의 Iteratee & Enumerator 

Play 에서 Iteratee / Enumerator / Enumeratee을 간단하게 말하면 :

이름설명
Iteratee [E, A]

 Iteratee [E, A]는 함수형 프로그래밍에서 iteration 컨셉의 일반화. E 입력,A 출력 (소비자역할)

Enumerator [E]컬렉션을 일반화 한 것으로 형태 E를 열거한다. 무한 열거 (Streaming) 할 수도있다. (생산자역할)
Enumeratee [E, A]거의 사용하지 않기 때문에 지금은 생각하지 좋다.

과 같다.

먼저 Iteratee 에 대해서 알아보자. Iteratee 는 보다시피 Iterator 와 먼가 관련이 있어 보이는데 자바에서 Iterator 가 어떻게 활용되는지 살펴보면 

val l = List(1, 234, 455, 987)

var total = 0 // will contain the final total
var it = l.iterator
while( it.hasNext ) {
total += it.next
}

total
=> resXXX : Int = 1677

짧게 정리하면  (1,234,455,987).iterating( .... total += it.next ....)  이렇게 되는데 
앞에 데이터가 있고 그것을 순회하면서 어떤 행동 하게 된다. 

즉 데이터 + 순회 + 행동 으로 이루어져있는데  데이터가 정해져있다. 강결합이다. 

여기서 이것을 느슨하게 만들어 줄 방법에 대해서 생각해보면 iterator 를 일반화 하는것에 미치게 되는데 앞에 데이터가 무엇이 오건간에 순회하면서 행동을 하게 하자는 것이다. 즉 데이터는 주입받자는 것이다. 제어역전!! 

iteratee = 순회(iterating) + 행동(react) 
Enumerator  = 데이터 

Enumerator 는 정해진 콜렉션의 데이터가 아니라 비동기적으로 데이터를 생산하는 것으로 또 일반화된다.  


자바의 Observable

자바의 Observable 에 대한 정말 쉽고 자세히 설명하는 토비님의 동영상 을 먼저 참고하자
토비의 봄 스프링 Reactive programming


에릭마이어의 Duality 

위의 Play 에서 생산자 (Enumerator ) 와 소비자 (Iteratee)가 있듯이, 다른 언어,라이브러리에서도 이와 비슷한 것들이 다양한 이름으로 만들어지고 있다. 범람하고 있다는 표현이 더 어울리겠다.

데이터를 보내주는이 <---> 데이터를 받는이 

이렇게 상반되는 개념을 지칭하는 개념을 "duality"  라고도 부르는 모양이다.
더 정확한 정보는 동영상도 참고하시고..

에릭마이어가 설명하는 Duality 


Gof 의 옵저버 패턴 

이렇게 이벤트를 주고 받는 모습은 기존에 Gof 패턴에서 Observer 패턴이 비슷한 형태를 보여줬고, 예를들어 이 패턴을 설명해보면 어떤 매니져 객체가 있고 , 이 객체에 어떤 이벤트를 받길 원하는 옵저버들이 매니져 객체에 등록한다. 
매니져에서 어떤 이벤트를 감지하면 , 자신에게 등록된 옵저버들에게 이벤트를 notify 해주는게 골자이다.

GUI 프로그래밍에서 특히 당연하게 사용되고 있다. 
어떤 데이터를 다루는 다양한 View 가 있을때, 어떤 하나의 View를 통해 데이터에 변화를 사용자가 주면, 나머지 View에게도 그 변화를 전파하기 위한 설계에 사용되는 것이다. 

이미지에서 Observer 는 알림을 받는 주체이다.
Subject 는 알림을 주는 매니져이다.
Observer 는 자신을 Subject 에게 attach 를 통해 알려주고
Subject 는 어떤 이벤트가 생겼을때 Observer 들에게 update 를 해준다. 


POSA2 의 생산자-소비자 패턴 (멀티쓰레드 패턴중 하나) 

멀티쓰레드 디자인패턴의 꽃이라고 한다면 단연코 "생산자-소비자" 패턴이라고 할 수 있다. 멀티쓰레드/서버코드를 작성할때 거의 무조건 "생산자-소비자" 패턴이 사용되기 마련이며, 다른 고차원 패턴들 (예를들면 node.js 의 기반패턴인 react 패턴) 의 기반이 되면서 동시에 멀티쓰레드 코어패턴을 포함하고 있는 , 즉  "허리" 역할을 제대로 하고 있는 패턴이라고 볼수 있기 때문에 아주 중요하다고 볼 수 있다.

이것도 역시 이벤트(데이터) 를 주는 놈이 있고 받는 놈이 있는데 


보다시피 매우 단순하다. Thread 1이 생산자 Thread 2가 소비자가 된다.


Scala 에서 Observable 사용하기 

이제 스칼라에서  Observable 를 사용하는 방식에 대해서 알아보자.
주거니 받거니 (Polling 이 아니라 Push 를 통해)  하는 기본적인 매커니즘은 같다. 다만 기능이 추가되었고 비동기로 사용가능하도록 확장 되었다. 


강결합된 데이터를 이미 가지고 있는 생산자 (Observable)


object ObservablesItems extends App {
import rx.lang.scala._

val o = Observable.items("Pascal", "Java", "Scala")
o.subscribe(name => log(s"learned the $name language"))
o.subscribe(name => log(s"forgot the $name language"))

Thread.sleep(1000)
}

코드를 보면 Observable 은 어떤 데이터(이벤트) 를 가지고 있다. 생산자 역할을 한다.
소비자는 ?? 그렇다 name => log(s"learned the $name language")  이 함수리터럴이 담당한다.

(옵저버 패턴으로 비교하면, 생산자: Subject , 소비자: Observer 이며, 따라서 위의 subscribe 는 
 옵저버패턴에서는 addObserver 나 addListener 메소드와 매칭된다.

생산자(Observable) 은 자신이 보낼(Push) 할 이벤트를 처리할 친구들을 subscribe 메소드로 모집하고 있다.

* 위의 코드를 실행하려면 아래 rxjava 를 가져와야한다. 

libraryDependencies += "com.netflix.rxjava" % "rxjava-scala" % "0.19.1"


정해진 시간 이후에 알림을 주는 생산자 (Observable)

val o = Observable.timer(5.second)
o.subscribe(_ => log(s"Timeout!"))
o.subscribe(_ => log(s"Another timeout!"))

5초 이후에 로그를 찍는다.


에러가 발생했음을 알려주는 생산자 (Observable)

val o = Observable.items(1, 2) ++ Observable.error(new RuntimeException) ++ Observable.items(3, 4)
o.subscribe(
x => log(s"number $x"),
t => log(s"an error occurred: $t")
)

subscribe 는 2개의  매개변수로 받는다. 두번째는 예러 발생시 핸들링할 함수이다.
에러(예외) 이 후의 데이터 처리는 하지 않는다.

지금까지는 subscribe 메소드에 

def subscribe(onNext: T => Unit): Subscription = {
asJavaObservable.subscribe(scalaFunction1ProducingUnitToAction1(onNext))
}

onNext 에 해당하는 함수 리터럴을 넣었지만

이제 Observer 객체를 넣어 보도록하자.


Observer (소비자) 에게 데이터를 건네주는 생산자 (Observable)

val classics = List("Il buono", "Big", "Die Hard")
val o = Observable.from(classics)

o.subscribe(new Observer[String] {
override def onNext(m: String) = log(s"Movies Watchlist - $m")
override def onError(e: Throwable) = log(s"Ooops - $e!")
override def onCompleted() = log(s"No more movies.")
})

생산자(Observable) 에 강결합된 리스트를 넣어주었다.
소비자에 Observer 객체를 만들어서 넣어준다.

onNext : 생산자에서 주는 데이터(이벤트) 를 처리하는 메소드
onError : 생산자에서 발생한 예외를 처리하는 메소드
onCompleted : 생산자에서 이제 모든 데이터를 처리하였다고 알려주는 메소드 


Observable 과 Future 

val f = Future {
Thread.sleep(500)
"Back to the Future(s)"
}

val o = Observable.create[String] { obs =>
f foreach {
case s =>
obs.onNext(s)
obs.onCompleted()
}
f.failed foreach {
case t => obs.onError(t)
}
Subscription()
}

o.subscribe(log _)

- Future 를 하나 만들었다. 3초 있다가 문자열 하나를 리턴해주는 행동을 하는 ~
- Observable 에는 기존처럼 컬렉션의  강결합된 데이터가 엮여있는게 아니라 , Future 가 실행되고 그 리턴값들이 source 데이터로 활용된다.
- 그 데이터 (Future 가 리턴한 값) 를 소비자에게 전달한다. 
- 소비자는 log _   즉 로그를 찍는 함수이다 

val o = Observable.from(Future {
Thread.sleep(500)
"Back to the Future(s)"
})

o.subscribe(log _)

다음처럼 Future 로 부터 직접 생산될 수도 있다.


Observable 과 Combinator

val roles = Observable.items("The Good", "The Bad", "The Ugly")
val names = Observable.items("Clint Eastwood", "Lee Van Cleef", "Eli Wallach")
val zipped = names.zip(roles).map { case (name, role) => s"$name - $role" }

zipped.subscribe(log _)

함수형 개발의 특징 답게 두개의 생산자로 부터 데이터를 결합하여 소비자에게 전달할 수도 있다.

main: Clint Eastwood - The Good
main: Lee Van Cleef - The Bad
main: Eli Wallach - The Ugly

이렇게 출력된다.

* zip 은 2개의 컬렉션에서 하나씩 가져와서 튜플을 만든다. zipWithIndex 는 하나를 1,2 등 숫자로 매핑 
* map 은 컬렉션을 변화시켜서 새로운 컬렉션을 만든다. 그 자신을 변형시키진 않는다. 


Observable 과 Subscription

import rx.lang.scala._
import org.apache.commons.io.monitor._

def modifiedFiles(directory: String): Observable[String] = {
Observable.create { observer =>
val fileMonitor = new FileAlterationMonitor(1000)
val fileObs = new FileAlterationObserver(directory)
val fileLis = new FileAlterationListenerAdaptor {
override def onFileChange(file: java.io.File) {
observer.onNext(file.getName)
}
}
fileObs.addListener(fileLis)
fileMonitor.addObserver(fileObs)
fileMonitor.start()

Subscription { fileMonitor.stop() }
}
}

log(s"starting to monitor files")
val subscription = modifiedFiles(".").subscribe(filename => log(s"$filename modified!"))
log(s"please modify and save a file")

Thread.sleep(10000)

subscription.unsubscribe()
log(s"monitoring done")

- apache.commons 는 스칼라 사용자에게도 보물창고이다. 
- monitor 는 파일의 변화를 감지해서 우리의 생산자에게 알려준다.
- 우리의 생산자는 파일의 이름을 자연스럽게 (논블럭) 소비자에게 보내준다. 



레퍼런스:

Learning Concurrent Programming in Scala by Aleksandar Prokopec



스칼라에서의 가변인자 


1. 간단 예제  

object test {

def func[T](xs:T*) = xs.foreach(x => println(x))

def main(arg : Array[String]): Unit ={
func(0,1,2)
}
}

(xs : T*)   처럼 타입뒤에 * 를 붙혀주면 됩니다.


2. 컬렉션은  _* 를 붙여줍니다.

object test {

def sum(xs:Int*):Int = if (xs.isEmpty) 0 else xs.head + sum(xs.tail:_*)

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

println(sum(0,1,2,3)) // OK !

val ns = List(1, 3, 5)
println(sum(ns:_*)) // OK!

println(sum(List(1, 3, 5))) // Not Good
}
}

 : _* 를 붙여줌으로써 컴파일러에게 가변인자라는것을 알려줍니다.

Null 과 친구들 

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

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

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

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

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

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



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] 를 반환하구요.



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 가 됩니다.



함수와 메소드

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

 

스칼라에서 함수종류 

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

메소드  

 

특성 

스칼라 메소드 파라미터에서 중요한 점은 이들이 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 + "]") }

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

+ Recent posts