Promise

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

Future 에서는 보통 위임 행동이 강결합되있었다. 즉 future.( 행동 )  
하지만 Promise 는 Promise 만 먼저 선언해두고 나중에 success 를 호출해서 완료된것을 알려준다.

즉 Future 는 아래와 같은 시나리오라면 
다른데서 하는 행위 - 그 행위에 대한 실제 결과 -  미리 받은 Future - 실제 결과를 이용한 행동

Promise 는 
다른데서 하는 행위가 빠졌고 - 실제 결과를 돌려주는 타이밍을 스스로 결정  - 실제 결과를 이용한 행동 
으로 볼 수 있다.

다음 예를보자.

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코어) 



레퍼런스

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



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

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


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


동시성을 위한 스칼라 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



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

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


스칼라에서의 가변인자 


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
}
}

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


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

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


타입 기초 





타입별칭(Type alias)

https://alvinalexander.com/scala/scala-type-aliases-syntax-examples


추상 타입

 http://docs.scala-lang.org/ko/tutorials/tour/abstract-types.html


매개변수된 타입 (제너릭)

http://docs.scala-lang.org/ko/tutorials/tour/generic-classes.html


상위 타입 경계

http://docs.scala-lang.org/ko/tutorials/tour/upper-type-bounds


하위 타입 경계

http://docs.scala-lang.org/ko/tutorials/tour/upper-type-bounds


공변성 

https://twitter.github.io/scala_school/ko/type-basics.html#variance






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

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


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


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


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


 세미콜론 추론 

  스칼라에서는 보통 문장 끝의 세미콜론(;) 을 생략 할 수 있다.

  다만 한 줄에 여러 문장이라면 사용해야한다. 


  가끔 헥깔리는것은


  x

  + y 


 스칼라는 위 문장을 x 와 +y로 파싱한다. 

 (x

 +y)  이렇게 하거나 


+ 를 줄의 끝에 넣으면 우리가 의도하는대로 된다. 

x + 

y  


싱글톤 객체

스칼라와 자바의 가장 큰 차이점 중 하나는 스칼라는 정적 멤버가 없다는 것이다.

대신 전용으로 사용할 수 있는 싱글톤 객체를 제공한다. 

class 대신 object 라는 키워드를 사용한다.

import scala.collection.mutable.Map 


object ChecksumAccumulator {

private val cache = Map[String, Int) ()


def calculate(s: String) : Int = 

if (cache.contains(s))

cache(s)

else {

val acc = new ChecksumAccumulator

for (c <- s)

acc.add(c.toByte)

val cs = acc.checksum()

cache += (s -> cs)    // s 키, cs 값 으로 연관관계를 만듬. 

cs

}

}


이 싱글톤 객체는 이름이 저 위에 있는 class ChecksumAccumulator 와 같다.

이렇게 싱글톤 객체는 동일하게 만들 수 있는데 이걸 동반 객체라고 하며 

같은 파일안에 있어야한다. 


이런 클래스와 동반객체는 상대방의 비공개 멤버에 접근 할 수 있게 된다.

즉 자바의 정적멤버가 외부에 따로 만들어져 있다고 보면 된다.

ChecksumAccumulator.calculate("hello") 이렇게 호출 할 수 있다. 


* 팁:  코드에서 맵 경우 WeakHashMap 같은 걸 사용하면 메모리가 부족할때 가비지콜렉터가 캐시의 원소를 수집.


- 싱글턴 객체는 파라미터를 받을 수 없고 클래스는 받을 수 있다.

- 동반 클래스가 없는 싱글톤 클래스를 독립 객체라고 한다. 

  독립객체로는 유틸리티 메소드를 모아두는곳 이나 진입점으로 사용할 수 있다. 


스칼라 애플리케이션 

import ChecksumAccumulator.calculate


object Sumer {

def main(args: Array[String]){

for (arg <- args)

println(arg : ": " + calculate(arg() )  // 메소드만 사용함!

}

}


 * 스칼라는 항상 java.lang 과 scala 패키지의 멤버를 암시적으로 임포트한다.

또한 Predef 이라는 싱글톤 객체도 임포트하는데 여기엔 유용한 메소드가 많이 있다.

println 이라든지 assert 라든지..


 * 스칼라의 import 는 자바의 static import 로 생각 할 수 있다. 스칼라에서 다른 점은 싱글톤 뿐 아니라 어느 객체에서라도 멤버를 임포트 할 수 있다는 것이다.


* 자바는 공개  클래스 이름과 파일 이름이 같아야 하지만 스칼라에서는 상관없다. 


* scala.Application 이라는 트레이트를 통해 더 간단히 main 을 작성 할 수 있다.


import ChecksumAccumulator.calculate


object Sumer extends Application  {


for (arg <- List("fall","winter"))

println(arg : ": " + calculate(arg() )  // 메소드만 사용함!


}



클래스 예제


1) 간단한 클래스  


# 한줄 만으로 class 완성! 
class Person(var name: String, var age: Int) // 기본 생성자가 class 이름과 나란히~ 

# class 사용 
val al = new Person("Al", 42) 
al.name // "Al" 
al.age // 42


2) 자바와 다른 특이한 모습


// class 정의 
class Person(val firstName: String, val lastName: String) {
 println("the constructor begins") // 희안하죠? 객체로 만들어지는 동시에 호출됩니다.
 val fullName = firstName + " " + lastName 
 val HOME = System.getProperty("user.home"); 
 def foo { println("foo") } 
 def printFullName { println(fullName) } 

 printFullName // 희안하죠? 객체로 만들어지는 동시에 호출됩니다. 
 println("still in the constructor") // 희안하죠? 객체로 만들어지는 동시에 호출됩니다. } 

// 다음과 같이 나옵니다. 클래스 내부의 것들이 생성됨과 동시에 실행되네요. 
scala> val p = new Person("Alvin", "Alexander") 
the constructor begins 
Alvin Alexander 
still in the constructor 
p: Person = Person@68f507d2


3) 보조 생성자 

 this 를 사용하여  보조적 생성자를 만들수 있네요. (클래스 이름가지고 만들지 않음) 


class Pizza { // 기본 생성자에는 매개변수를 받는게 없군요. 
  var crustSize = 12 var crustType = "Thin" 
  def this(crustSize: Int) { // 매개변수 하나 받는 생성자~ 
      this() 
      this.crustSize = crustSize 
  } 
  def this(crustSize: Int, crustType: String) { // 매개변수 2개 받는 생성자 
       this(crustSize) 
       this.crustType = crustType 
  } 
  override def toString = { "A %s inch pizza with %s crust.".format(crustSize, crustType) } }


  println(new Pizza)
  println(new Pizza(14))
  println(new Pizza(16, "Thick"))

4) 생성자 매개변수에게 디폴트 값을~


class Socket (var timeout: Int = 10000) // 디폴트 값을 사용함 
val s = new Socket s.timeout // Int = 10000
val s = new Socket(5000) s.timeout // Int = 5000


5) 추상 클래스를 사용할 경우는 언제? 


추상클래스는 생성자 매개변수를 가질수 있지만, trait 는 그럴 수 없다. 
 * trait 는 자바의 인터페이스 비슷한 것 으로 추후에 설명 예정.


// 컴파일 안됨 trait Animal(name: String) 
// 추상 클래스는 가능 ! abstract class Animal(name: String)


abstract class BaseController(db: Database) {
  def save { db.save }
  def update { db.update }
  def delete { db.delete }
  def connect                             // abstract since it has no implementation
  def getStatus: String                   // an abstract method that returns a String
  def setServerName(serverName: String)   // an abstract method that takes a parameter
}

6) 스칼라 case 클래스 

 case 클래스는  유용한 행사코드를 자동으로  생성할 수 있다.


case class Person(var name: String, var age: Int) // class 앞에 case 를 붙여주는것으로


case class Person(var name: String, var age: Int) // class 앞에 case 를 붙여주는것으로

case class 를 사용하면 다양한 혜택과 자동으로 다음을 만들어 준다:

  • toString, equals, 와 hashCode  메소드를 만들어 준다.
  • 생성자 파라미터들에게 게터와 세터 속성이 부여된다.
  • 더이상 인스턴스로 만드는데 new 를 사용할 필요가 없다.
  • match/case 문에서 편리하게 이용할 수 있다.

http://alvinalexander.com/scala/scala-class-examples-constructors-case-classes-parameters   발췌 


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

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

while


스칼라에서 While 문은 없다로 생각하는게 좋습니다. 
개인적으론 Java 8 이상에서도 while 문은 없다라고 생각하는게 좋지 않나 합니다. 

아래와 같이 다른 방법 


Stream API

LINQ 

STL algorithm

컬렉션의 고차함수 사용(map , flatmap,  filter, zip, fold, foreach, reduce, collect, partition, scan, groupBy 등) 


을 먼저 생각하는게 좋습니다


특징

* 스칼라의 while 은 다른 언어와 마찬가지로 동작합니다.

* if 나 for 가 표현"식" 인 반면에 while 은  "식" 이 아닙니다. 그냥 루프입니다.  즉 값을 내어 놓지 않습니다.

  예를들어 if 표현식의 경우 값을 내놓기 때문에 아래와 같이 코딩이 가능합니다.

  val filename = if (!args.isEmpty) args(0) else "default.txt"         


예제 

// 20 번 돌겠군요.   var a = 0; while( a < 20 ){ a = a + 1;

}

// 최대 공약수 구하기
 
def gcdLoop(x : Long, y:Long): Long = {

var a = x

var b = y

while( a != 0 ) {

val temp = a

a = b%a

b = temp 

}

b

}

 보통 while 문들 처럼  조건을 검사하고 조건이 참일때까지는 계속 반복 수행을 합니다. 

do while 루프도 있습니다.

var a = 0; do { a = a + 1; } while( a < 20 )

적어도 한번은 실행 할 수 있겠네요.


Unit 타입

루프의 결과는 그 타입이 Unit 입니다. 이 타입은 유니트 값 밖에 없으며 빈 괄호 () 로 표시합니다.

() 란 값이 존재한다는 점에서 자바의 void 와 다릅니다. 

def greet() { println ("hi"( } 를 실행하면 () 이며  var 변수에 대한 재 할당도 () 가 결과가 됩니다. 

var line = 0;


while ((line = readLine()) != "") // 작동하지 않음 !!

println ("Read: " + line)

( line = readLine() )  를 평가하면 () 으로  문자열 "" 과  != 로 비교하면 언제나 참입니다.

자바의 경우 할당받은 결과 값이겠지만,  스칼라에서 할당의 결과는 유니트 값인 () 이기 때문입니다.


따라서 

import scala.io.Source for(line <- Source.fromFile("myfile.txt").getLines()) println(line)

import java.io.File
import scala.io.Source

Source.fromFile(new File("myfile.txt")).getLines.foreach { line =>
    // Do something with the line, for example print it
    println(line)
}

이런 방법을 고려해야 합니다. 사고 방식이 바뀌어야 한다는 말입니다.


이런식의 해결은 지양하고 있습니다.

while( {line = reader.readLine();  line!= null} ) { .... }


While 에서 탈출하기 

 2.8 버전부터 Breaks 가 생겼네요.

import scala.util.control.Breaks._
var largest = 0
// pass a function to the breakable method
breakable { 
    for (i<-999 to 1  by -1; j <- i to 1 by -1) {
        val product = i * j
        if (largest > product) {
            break  // BREAK!!
        }
        else if (product.toString.equals(product.toString.reverse)) {
            largest = largest max product
        }
    }
}

순수 함수형 언어에서 while

순수 함수형 언어에서는 while 의 결과가 특정 값을 내보내는게 아니기 때문에 종종 제외합니다. 그런 언어에서 "루프" 는 없습니다.하지만  스칼라에서는 존재하는데 때로는 명령형의 해법이 가독성이 더 뛰어나다고 믿기 때문입니다. 


재귀를 통한 while의 대체 

재귀와 while 은 공통점이 몇가지 있습니다.

둘다 body 를 반복한다는 점이구요. 콘디션을 점검해서 순회의 종료를 알린다는 점입니다. 


위의 최대공약수 예제를 재귀로 바꾸어 보겠습니다.

// 최대 공약수 구하기
 

def gcd (x : Long, y :Long) : Long = 

if (y == 0) x else gcd (y, x%y) 

while 문의 컨디션 점검하는위치가 body 내부로 들어왔다는 점하고 

함수이름을 호출함으로써 순회가 돌아진다는 점이 조금 달라졌을 뿐입니다. 


* var 함수사용이 없어졌습니다.


일반적으로 while 루프틑 var 변수와 마찬가지로 적게 사용하기  위해 노력할것을 권장합니다.



while vs 재귀 vs 콜렉션 (고차함수) 


이 3가지에  관한 글을 링크했습니다. 

http://blog.richdougherty.com/2009/04/tail-calls-tailrec-and-trampolines.html

http://www.scala-lang.org/old/node/8113.html

http://alvinalexander.com/scala/scala-recursion-examples-recursive-programming

http://stackoverflow.com/questions/12496959/summing-values-in-a-list

http://stackoverflow.com/questions/18674743/is-there-any-advantage-to-avoiding-while-loops-in-scala

http://stackoverflow.com/questions/15138624/scala-recursion-vs-loop-performance-and-runtime-considerations

http://stackoverflow.com/questions/18645936/is-recursion-in-scala-very-necessary




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

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

Map


특성

* Map 은 변경 가능한 것 (mutable.Map) 과 변경 불가능한 것 (immutable.Map) 모두를 제공합니다.
import scala.collection.mutable.Map
  예를들어 put 이라든지 remove 메소드는 immutable.Map 에서는 사용 불가.
* Map 을 위한 기반 트레이트가있고 이를 상속한 변경 가능 집합, 변경 불가능 집합을 위한 2가지 트레이트가 
  있다.
 ( Set 과 비슷) 


생성 


val m = Map[Int,String]()   // 다른것들과 마찬가지로 팩토리 메소드를 이용해 만들 수 있다.

val m2 = Map (1 -> "one" , 2-> "two") 이렇게 초기화 할 수 있고
val m3 = Map ( (1,"one") , (2,"two") ) 이렇게도 할 수 있다. 

아래 처럼  List 를 이용해서도 다양하게 가능해요~
// 튜플로 이루어진 List 를 이용

val
myList = List("England" -> "London", "Germany" -> "Berlin") // List 에 요소들이 Tuple 로 val myMap = myList.groupBy(e => e._1).map(e => (e._1, e._2(0)._2)) val betterConversion = Map(myList:_*) val scala28Map = myList.toMap // scala 2.8 version 에선 이렇게도 가능 // map 메소드를 이용 val myList = List("England" , "London", "Germany" , "Berlin") val map: Map[String,Int] = myList.map{ s => (s,s.length) }.toMap // 두개의 리스트를 조합해서

val ndx = List(1,2)
val vals = List("one", "two")
val mymap =  for ( (i,v) <- ndx.zip(vals) ) yield { i -> v} > List((1,"one"), (2,"two"))



* 나중에 for문 / yield / zip 은 다시 공부해보아요.


추가



val m = mutable.Map[Int,String]()

m(0) = "zero"  //기존에 0 의  key 가 있을 경우 대체됩니다.  ( mutable.Map  일 경우만 ) 


아래 처럼도 가능 ( mutable.Map  일 경우만 ) 
m += ( 1 -> "one")
m += ( 2 -> "two")

+= 메소드를 이용하여 맵에 값을 담습니다.

* 위의 m 을 var 로 선언하면 immutable.Map 에서도 가능합니다.
 자신을 변경하는게 아니라 
변경된 맵을 새로운 m 에 재 할당하는 방식이 되니까요.


1->"one" 

이것은  1 은 key 이며 "one" 은 value 입니다.
1.->("one")  이것의 축약형 입니다. 

스칼라에서는 -> 메소드를 호출하면 해당객체를 key 로하고 인자를 value 로 하는 
"튜플" 을 만듭니다. 

put mutable.Map  일 경우만 ) 


val m2 = m.put(2, "three") 
m에는 새로운 value 가 담기게 되고, m2 에는 기존에 key 가 2 였던 value 가 담기게 됩니다.  

m.getOrElseUpdate(2, "three") 
키 2 가 이미 있다면 그에 해당하는 값을 가져오고 , 없으면 "three" 를 추가.


가져오기


val m = Map[Int,String]()
m += ( 1 -> "one")
m += ( 2 -> "two")

println(m(1)) //  "one" 출력,  key 가 없을 경우 예외 발생 ~!!

get

val m = Map (1 -> "one" , 2-> "two")
m.get(1) 

key 가 1 인 value 를 옵션에 넣어 반환합니다. 없으면 None 을 반환  

m.get(3) // None
m(3) // 예외 !!  

getorElse 

 None 이면 디폴트 값 (여기선 -1) 을 반환 

List(None, Some(107)).map( x => x.getOrElse(-1) )   res54: List[Int] = List(-1, 107)


제거 

m -= 1    // m 에서 key 가 1 인것을 제거 

remove  ( mutable.Map  일 경우만 ) 
val m2 = m.remove(1)  // m 에서 1을 key 로 가진 요소를 제거하고 그 값을 리턴

clear      mutable.Map  일 경우만 ) 
m.clear()  // m 의 모든 요소를 제거

retain     mutable.Map  일 경우만 ) 

m.retain((k,v) => k < 2)  // key 가 2 보다 작은 요소들만 남겨두고 나머지 제거




변환


스스로는 변화되지 않는데 , 변환된것을 리턴해준다.

filterKeys        ( immutable.Map  도 가능)

val m2 = m.filterKeys(_ > 1)    // key  가 1 보다 큰 요소들을 가지고  m2 를 만듬. 


mapValues     ( immutable.Map  도 가능)

val m2  = m.mapValues(_ * 5)  // 모든 value 에 * 5 를 한 요소를 가지고 m2 를 만듬 



변경 


transform   ( mutable.Map  일 경우만 ) 

m.transform( (k,v) => v * 5 )  // 모든 value 에 * 5 를 한 요소를 가지고 m2 를 만듬 

위의 mapValues 와 다른점은 transform 은 스스로도 변경된다는 점이다. 



Map -> mutable.Map 으로 변경 


val myImmutableMap = collection.immutable.Map(1->"one",2->"two")
val myMutableMap = collection.mutable.Map() ++ myImmutableMap


'Scala' 카테고리의 다른 글

스칼라 강좌 (8) - ArrayBuffer 와 공변성  (0) 2016.06.26
스칼라 강좌 (7) - 컬렉션과 자바  (0) 2016.06.26
스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15

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

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

Set


특성

* Set 은 변경 가능한 것과 변경 불가능한 것 모두를 제공합니다.
* Set 을 위한 기반 트레이트가있고  이를 상속한 변경 가능 집합, 변경 불가능 집합을 위한 2가지 트레이트가 있다.
   ( 트레이트는 자바 인터페이스와 비슷한 것으로 나중에 설명 ) 


생성 

val s  = Set ("Hi" , "There") 
리스트나 배열과 비슷하게 생성한다. 
변경 불가능한 Set 객체를 만들어서 변경 불가능한 s 에 대입한다.

변경


import scala.collection.immutable.set

var  s  = Set ("Hi" , "There")
s += "bye" 

이 코드에서 눈여겨 볼것은 var 로 선언된 s 이다.  이 코드는
기존 s 에 "bye" 를 더하면서 새로운 집합으로 재 할당 한것이다.

즉 s = s + "bye" 에서 두 s  가 가르키는 객체는 다른 것이다. 
새로 s 를 할당받기 위해서 var 로 선언했던것이다.

*  val 로 선언하면 += 에서 에러!!

근데 
import scala.collection.mutable.set 으로 선언을 해주면
변경 가능한 Set 를 사용할 수 있다.

이때는 
val s = Set("Hi", "There")
s += "bye" 
라고 해도 새로운 할당이 일어나지 않게 된다. 



'Scala' 카테고리의 다른 글

스칼라 강좌 (7) - 컬렉션과 자바  (0) 2016.06.26
스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15
스칼라 강좌 (2) - Array  (0) 2016.06.13

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

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

Tuple


특성

* 리스트와 마찬가지로 변경 불가능 하다.
* 리스트와 다른 점은 다른 타입의 원소를 넣을 수 있다는 점
메소드에서 여러 다양한 객체를 리턴해야 하는 경우 유용하다. 


생성 

val p = ( 99, "High") 
그냥 객체들을 콤마로 구분하여 () 사이에 넣어주면 된다. 
저것의 타입은 Tuple2[Int,String] 이다.

튜플의 타입은 원소의 개수와 각각의 타입에 따라 달라진다.  (22 개 까지 지원)

즉 
('u','r',"the") 의 타입은  Tuple3[Char,Char,String]이다.


원소에 접근 


println(p._1) 

 

튜플의 첫번째 값에 접근할수있다.




마지막 


왜 튜플은 원소접근을 p(0) 즉 p.apply(0) 처럼 할 수 없을까?

-> 리스트의 apply 메소드는 항상 동일한 타입의 객체를 반환하는 반면, 튜플의 각 원소들은 타입이 각기 다를 수 있기 때문


* 주의 사항

튜플은 각 원소의 접근을 0 이 아니라 1 부터 시작한다.


'Scala' 카테고리의 다른 글

스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15
스칼라 강좌 (2) - Array  (0) 2016.06.13
스칼라 강좌 (1 ) - 소개  (0) 2016.06.13

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

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

이 시리즈는 스칼라언어의 창시자인 마틴 오더스키가 직접 저술한   Programming in Scala (2판)   
을 참고로 하여 정리할 예정입니다.  잘못된 점이 있으면 지적해주시면 바로 수정하겠습니다.



List


특성

스칼라의 배열이 값을 변경 할 수 있는 순서가 정해진 시퀀스라면 
스칼라의 리스트는 기본적으로  값을 변경 할 수 없는 시퀀스입니다. 
함수형 스타일이라는것은 메소드 내에서 절대로 부수효과가 일어나면 안되는, 그래서 더 신뢰할 수 있고 재사용하기 쉬운 코드를 만드는게 주 목적이라 , 그 목적에 적합한 콜렉션이라 할 수 있습니다.


Linked List 식으로 구현 되 있으므로 head / tail  중간 삽입같은게 원할합니다.


생성 

배열 생성과 비슷합니다.

val list = List(1,2,3)   // new 와 [Int] 가 생략되었습니다. apply 라는 팩토리 함수가 암시적으로 호출.

아래 처럼 다양하게 만들 수 있습니다.

val x = List.range(1, 10) // 범위로 List(1, 2, 3, 4, 5, 6, 7, 8, 9)

val x = List.fill(3)("foo") // List(foo, foo, foo)
List.tabulate(5)(n => n * n) // List(0, 1, 4, 9, 16)
원소 가져오기 


val a = List(1,2,3) 

a(2)  



두개의 리스트 합치기 


두개의 리스트를 합쳐보겠습니다.


val a = List(1,2)

val b = List(3,4)


val c = a ::: b


이렇게 합니다.


:::  는 두개의 리스트를 합쳐주는 메소드 입니다.



리스트 앞에 요소 추가 


그럼 앞에다가 요소하나를 붙여주는 메소드는 무엇일까요?

List(1,2) 앞에다가 0 을 붙여서  (0,1,2) 로 만드는 방법 말이죠.


그건  :: 입니다.  콜론이 2개짜리네요.

근데 여기서 생각해 볼것은 서두에 List 는 변경 불가능하다고 했는데 앞에 숫자를 붙히다니? 

무슨 소리하는지 의심스러울거 같은데요.


예를 한번 보자구요.


val a = List(1,2)

val b = 0 :: a


이건데요. 


네 a 를 바꾼게 아니었습니다.  a 앞에 0 을 추가한 또 다른 b 라는 List 를 만든거에요.

즉 무엇을 변경해서 쓰려면, 기존것은 냅두고 기존것을 이용해서 새것을 만들어서 쓰라는 말입니다.


또 궁금한게 있을거 같은데요.

도대체 0 :: a 를 하는데 어떻게 a 앞에 0 이 들어가게 되는지 말이죠.


그 이유는 


:: 메소드는 0 의 메소드가 아니라  a 의 메소드입니다. 근데 앞에 있는 이유는 

그냥 규칙입니다. -.-;; 


우리의 마틴오더스키씨는 아주 많은걸  자신이 만든 언어에 쑤셔 넣었습니다.

그 규칙은 이름이 콜론(:) 으로 끝나는 메소드는 오른쪽 피연산자의 것으로 호출한다. 라고 합니다.


따라서


0 :: a  는  a.::(0) 이 되는 것입니다.

 


리스트 뒤에 요소 추가 


그럼 뒤에 추가하는것은 무엇일까요?


:+  입니다.


예를들어  


val a = List(1,2)

val b = a :+ 2


이런거죠.


하지만 웬간하면 이걸 쓰지마세요.

뒤에 추가하는 연산은 리스트의 길이만큼 오래 걸린다고 합니다.


이걸 효율적으로 하려면


- 일단 리스트를 뒤집고, 앞에다가 원소를 추가한후에 다시 뒤집는다.


(4 :: List(1,2,3).reverse).reverse

- 새 리스트로 추가한다.

List(1,2,3) ::: List(4)

- ListBuffer or ArrayBuffer 를 사용한다.  (나중에 살펴보는거로)  


입니다.



List 의 다양한 메소드들 


자 이런 변경 불가능한 List 를 사용해서 편하게 작업할 수 있는 방법들이 무지 많습니다.

문제는 편해지려면 이거 공부하고 외워야한다는거죠.  러닝커브가 올라갑니다. ;;


대표적인 몇가지만 살펴보도록 하겠습니다.


// 빈리스트

List()   


// 두번째 인덱스 원소 얻기 

val a = List(1,2,3) 

a(2)  


// 두번째 원소 까지  제거

val a = List(1,2,3) 

val b = a.drop(2)  


결과 : List(3)  


눈여겨 볼것은 자체의  원소를 제거한게 아니라,  원소 제거한 새로운 리스트를 반환합니다. 


// 요소중 길이가 3인것의 개수를 센다

val a = List ("hello", "world", "boy") 

a.count(s=> s.length == 3)   


s=> s.length == 3 은 람다식이죠? 인자가 s 인 함수란 야그입니다.  다음과 같아요


bool  func ( s ) {

  return s.length == 3

}


// 리스트의 각 원소를 변경하여 새 리스트를 반환합니다. 여기선 각 문자열 뒤에 X 를 붙힙니다.

val a = List ("hello", "world", "boy") 

val b = a.map(s => s + "X") 



자 다양한 메소드를 보았는데요. 이거 말고도 더 있긴합니다.

근데 여기서 생각해 봐야할것은 메소드가 많구나~~ 이게 아닙니다.


이런 메소드를 사용함으로써 var 를 안쓰게 됬다는겁니다. var 은 변경 가능한 변수를 만들때 씁니다.


예를들어 


위에서 count 같은 경우 대략 함수를 만들어 보면 


def count( all : Seq[String] ):Int = { 

   var temp = 0

   for ( one <-  all ) {

       if ( one.length == 4 ) 

          temp = temp + 1

   } 

   temp

}


이렇게 되잖습니까? 위에 보면 var 가 사용되었네요. 스칼라에서는 var 를 사용하지 말도록 합시다!!! 

(마틴오더스키는 var, val 둘다 만들어서 알아서 적재적소에 쓰이길 바랬지만 , 제 글을 보시는 분들은면 절대로 var 를 안쓰는 방향으로 '만' 생각하자구요. ) 


* 재귀를 활용한 count 함수  

 def count(list: Seq[String]): Int = {

    @tailrec
    def count(value: Int, remaining: Seq[String]): Int = {
      remaining match {
        case head :: tail if head.length == 4 => count(value + 1, tail)
        case head :: tail => count(value, tail)
        case Nil => value
      }
    }

    count(0, list)
  }

이렇게 count 를 var 없이 구현 할 수 있습니다. 꼬리재귀라는것을 사용한것인데요

순수함수형 언어에서는 재귀를 주로 사용한다네요.  이거보고 스칼라는 쓸 때없이 복잡한거네라고 생각하지마시구요 여기서는 그냥 이런게 있구나 하고 지나치시고 나중에 다시 차근차근 쉽게쉽게 살펴보자구요. 분명히 먼가 좋으니깐 저렇게 쓰는거다라고 생각하시고 넘어갑시다.



마지막 


List 의  짜증나는 규칙을 하나 억지로 외워봅시다.


val a = 1 :: 2 :: 3 :: Nil


는 List(1,2,3) 을 만들게 됩니다.


저 Nil 은 뭘까요? 


리스트 끝에 Nil 이 필요로 하는 이유는 :: 메소드가 List 클래스의 멤버이기 때문이랍니다.

만약 1 :: 2 :: 3 만 사용했다고 치면 , 마지막의 3 이 Int 형이라서 :: 메소드가 없기때문에

그냥 꽝이 되버리는 반면


마지막에 Nil 을 넣어주게되면 Nil 은 List 의 멤버이기때문에 타입을 추론해서 List 로 만들어 줍니다. 

'Scala' 카테고리의 다른 글

스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15
스칼라 강좌 (2) - Array  (0) 2016.06.13
스칼라 강좌 (1 ) - 소개  (0) 2016.06.13

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

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

개인적으로 느끼는 스칼라라는 언어는 , 창시자가 너무 생각이 많구나~ 욕심도 많고 그런 느낌을 받습니다. "내가 쓰기 편하도록 엄청나게 많이 신경썼으니, 니들도 이걸 잘 쓰려면 내가 엄청나게 많이 신경쓴 그 부분들을 너희들도 일단 엄청나게 신경써서  공부해~~ 그 후엔  편해질꺼야" 이건데요.  그냥 안쓸래요. 라고 말해드리고 싶기도 합니다. ㅎㅎ 

또한 이 언어는 하이브리드입니다. 함수형과 객체지향 양쪽을 지원합니다. 함수형을 추구하되 객체지향도 쓸 수 있다 정도입니다만, 덕분에 굉장히 강력해 질 수도 혹은 복잡할 수 있습니다. 딱 맞는 예는 아니지만 마치 하이브리드 객체지향인 C++ 이  더 순수한  객체지향언어인 자바보다 복잡하듯이 말이죠. C++ 개발자중 객체지향에 대해서 잘 모르는 개발자들이 많듯이 스칼라 개발자 중에서도 함수형개발이 먼지 모르고 그냥 객체지향식으로 사용할 가능성도 매우 큽니다. 

따라서 먼저 클로저나 하스켈을 공부하고 스칼라를 하면 어떨까 생각 해봅니다. 저는 스칼라를 먼저 했는데 , 스칼라만 사용하는게 아니라 다양한 언어와 동시에 개발을 진행 중이다보니 순수함수형의 DNA 가 뿌리내리기 정말 힘들더군요. 

아무튼 반응이 좋으면 100개는 채웠으면 하는 바람입니다.

재미로 보는 언어 특징

배우기도 쉽고 사용하기도 쉽다 : 파이썬
배우기는 쉬운데 사용하기는 어렵다 : C 
배우기는 어렵고 사용하기는 보통 : Scala
배우기도 어렵고 사용하기도 어렵다 : C++
배우기 보통 사용하기 쉬운편 : 자바,스위프트,Go

배우는것도 아리송하고 사용하기도 아리송하다 : Javascript 
참고 -> 심심풀이 자바스크립트 퀴즈 http://hamait.tistory.com/465


이 시리즈는 스칼라언어의 창시자인 마틴 오더스키가 직접 저술한
Programming in Scala (2판)   
을 참고로 하여  공부하면서 정리할 예정입니다.  잘못된 점이 있으면 지적해주시면 바로 수정하겠습니다.



오늘은 제가 선택한 스칼라가 자바와 다른점 4가지 포인트를 소개해보도록 하겠습니다. 


0.  함수

함수는 너무 중요해서 0 번입니다.
스칼라에서는 함수가 시작이자 끝입니다.


간단히 예를 들어보죠. 상상을 할 시간입니다. 객체지향을 잊어야합니다. 

수학에서 y = x + 1  이라는 함수가 있습니다.
개발자인 우리는 왼쪽  y 는 리턴 값 이라고 상상 할 수 있으며 오른쪽 x + 1 은 함수내용이라고
생각 할 수 있습니다.

다시 수학으로 돌아 와서 저 수식에서  y 값은 x 가 무엇이냐에 따라서 항상 고정입니다.  
이런걸 
굉장히 순수하다고 하는데요 왜냐?

x 에 2 를 넣었다고 생각해보자구요.
y = x + 1  이라고 표현 할 수 도 있지만 
y = 3 이라고 표현 할 수 도 있습니다. 

즉 함수가 하는 행위의 내용과 함수가 실행되어 나온 결과 값이 일치합니다.
매개변수로 2 가 입력 됬다고 할때  x+1 = 3 와 동일하다는 얘기죠.

즉  a = function (b) 

이런게 있을때  함수가 수학처럼 순수하다면 

a = 3 

이렇게 바꿀 수가 있다는 얘기입니다. 함수 자체와 결과가 일치합니다.
이러한 순수한 함수에 대해 공부하는게 스칼라에서 시작이자 끝입니다.

그럼 우리가 상식적으로 알고 있는 함수는 저게 일치 하지 않는데 왜 그럴까요?

int function ( int x ) {
return x + 1
}

이런 함수라면 순수합니다만..

int function ( int x ) {
  print(x)
  fileSave(x)
  collection.add(x)  
  return x + 1
}

위에 초록색처럼 내부에 이상한 짓거리를 하는 놈이 있다면 
과연  a = function (2) 를  a= 3이라고 말 할 수 있을까요?
해당 숫자를 넣으면 항상 같은 일이 벌어질거라는 확신을 가질 수 없게 됩니다.
이게 순수하지 않은 함수라는것이고 부수효과라고 말해지는것입니다.

스칼라는 순수한 함수를 지향하는 함수형 언어입니다.  (완전 함수형은 아닙니다.
C++ 이 C 개발자와 객체지향을 둘다 잡기 위한 약간의 혼종이라면 스칼라도 기존 자바/객체지향 세력을 포용하기 위한 느슨한 함수형 언어입니다. 하지만 이왕 스칼라를 사용한다면 함수형으로 마인드를 바꿔야합니다) 


1.  객체 


1


자 이건 무엇인가요? 

숫자 1입니다.  땡~~~~!!  

이것은 객체 1 입니다.   

스칼라에서는 모든것이 객체입니다. 


1 + 3 


숫자를 더 하는거다?

아닙니다.  (뭐 아니라고 할 것 까진 없지만 객체라고 사고 전환이 필요해서리..) 

아래와 같습니다.


1.+(3)


저게 왜 같냐? 라고 묻는다면 

스칼라에서는 매개변수가 하나 일 때  괄호랑 .  를 생략할 수 있습니다.

1.+(3) 여기서  .  랑 괄호를 빼면  

그렇습니다.  1+3 이 됩니다. ㅇㅋ!! 

모든게 객체다 라고 사고전환을 하는게 매우 중요합니다.


다른 예를 하나 들면 

for ( i <- 0 to 2) {
  print (i) 
}

0 과 1 이 출력될텐데요. 

위에서  0 to 2  또한 

0.to(2) 를  . 과 () 를 생략해서 표현한 것입니다. 

즉 0 객체에 to 메서드를 이용하여  범위를 생성합니다. 


2. 타입-변수 vs  변수-타입 


자바는 int a; 라고 하지만 스칼라는 val a : Int 라고 합니다. 

이렇게 바꾼 이유는 "타입 추론" 을 용이하게 하기 위함이라고 하는데요 타입추론을 사용하면

변수 타입이나 메소드의 번환 타입을 생략 할 수 있게 됩니다.

즉 타입을 쓰지 않아도 언어내부에서 알아서 타입을 챙겨준다. 입니다. 

예를들어 


val a : Int = 1  해도 되지만

val a = 1 이렇게 생략해도 됩니다.   

val  o = new Test  하면 o 를 알아서 Test 객체로 추론한다는것이죠.



3.  val / var 


변경 불가능한 변수 / 변경 가능한 변수로 나누는 것입니다.  
갑자기  const 도 생각나네요. C++ 에서 const 는 변수를 변경 불가능하게 만드는 키워드입니다.


int const* p;   
const* int p;
int* const p;
const int* p;
const int const* p;
const int* const p;

const int ** p;
int * const *p;
int ** const p;

void const f(){};
void f() const{};
void f(const objectA i){}
void f(const 
objectA &i) {}

const int f(const char* const str) const {}
const int& f() const{}

이렇게 다양하게 표현가능합니다.
포인터가 가르키는것을 변경 불가능하게하라
포인터가 가르키는 것의 내용을 변경 불가능하게 하라
함수내부에서 XXX 을 변경 불가능하게하라. 
리턴되는 값이 변경 불가능하게 하라.

등등 입니다.  장난하나 -.-;;


자 다시 스칼라로 돌아와서  val 과 var  에 대해서 알아 보겠습니다.

간단히 

val 은 변경 못한다는 뜻이구요.
var 은 변경해도 된다는 뜻입니다.

C,C++,Java,C# 은 var 가 일반적인 함수선언이며, 얼랭,하스켈,오캐멀등은 val 이 일반적인 변수입니다. 
하지만 스칼라는 var, val 은 그냥 사용자가 선택할 몫으로 두었습니다. 양쪽 다 유용하고 어떤 하나가 
특별히 악당이 아니라는 거죠. 물론 스칼라도 val 을 권장하긴 합니다. 

소스를 보시죠.

val  a = 10

a = 11   //  에러 !!!! 변경하지마~~

var b = 10

b = 11  // 좋습니다 !! var 는 변경을 허용합니다.

근데 여기서 잘 생각해보셔야할게 하나 있는데요.  
저기서 변환을 못시킨다는건 참조가 바뀌면 안된다는거지 
참조하고 있는 대상안의 값은 바뀌어도 무방합니다. 

예를들어 


class Test {

  var a = 1          

  def set(n:Int)={    //  스칼라는 함수선언시 def 를 사용합니다. 함수도 객체라 = 대입받습니다. 

    a = n

  }

  def print() = {println(a)}

}



 val test = new Test()  // 변경 불가능한 val 로 선언합니다. 

 test.print()  // test 객체를 출력하면 1 이 나옵니다.

 test.set(10) // 10 으로 "변경" 합니다. ??  엥 변경 ??? 

 test.print() // 10 으로 변경 되었습니다.  별 이상 없습니다.   

    

 test2 = new classTest() // 새로운 객체를 만들고

 test = test2  // 새로운 객체를 대입받으려고 하면 ~ 네 변경 불가능합니다!! 

    

val  로 선언했어도 set 함수를 통해 내부의 값을 바꾸는데 아무 이상없습니다.


스칼라에서는 var 을 어떤식으로 없애냐면 

def printArgs(args: Array[String]): Unit = {  // String 형 배열을 인자로 받고, 리턴되는 값은 없다.  
  var i = 0
  while (i < args.length){
    println(args(i))
    i += 1   
  }
}
def printArgs(args: Array[String]): Unit = {
  for (arg <- args){  // args 인자에서 하나씩 추출되어 arg 에 담김 
    println(arg)
 
  }
}
def printArgs(args: Array[String]): Unit = {
  args.foreach(println)   // args 에서 순회하는 메소드를 그냥 호출  
}

점점 간단해집니다. 

하지만 서두에도 말했다시피 우린 그냥 평범한 for, while 가지고 그동안 코딩잘 해왔는데 
굳이 다른 방식의 표현을 배우고 외워야합니다. 복잡도가 증가 했습니다. 
저걸 굳이 공부할 필요성에 대해서는 각자의 견해가 모두 다른게 당연하며  틀린건 아닙니다.

오늘은 요기까지 하구요~~

내일은 새로운 언어를 배우면 가장 먼저 익숙해져야하는  기본 콜렉션 몇가지를 살펴보도록 하겠습니다.

Array / List / Tuple / Set / Map 같은거 말이죠. . 

'Scala' 카테고리의 다른 글

스칼라 강좌 (6) - Map  (0) 2016.06.20
스칼라 강좌 (5) - Set  (0) 2016.06.19
스칼라 강좌 (4) - Tuple  (0) 2016.06.19
스칼라 강좌 (3) - List  (0) 2016.06.15
스칼라 강좌 (2) - Array  (0) 2016.06.13
스칼라 강좌 (1 ) - 소개  (0) 2016.06.13

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

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