부분 적용 함수(partially applied function)에 대한 예시이다. 마치 함수처럼 사용할 수 있다. 


def test(s1: String)(s2: String) = s1 + s2
val a = test("s1")
println(a)

이렇게 하면 에러가 발생한다.


Error:(10, 15) missing argument list for method test in object Main

Unapplied methods are only converted to functions when a function type is expected.

You can make this conversion explicit by writing `test _` or `test(_)(_)` instead of `test`.

  val a = test("s1")




에러가 발생하지 않도록 하려면 다음처럼 _를 추가해야 한다.

def test(s1: String)(s2: String) = s1 + s2
val a = test("s1") _
println(a)


실제 테스트 결과를 출력하면 다음과 같다.

def test(s1: String)(s2: String) = s1 + s2
val a = test("s1") _
println(a("s2"))


만약 매개변수가 3개라면 _(언더바)를 2개를 써야할까? 하나면 쓰면 된다. _는 나머지를 가르키는 대명사 역할을 한다.

def test(s1: String)(s2: String)(s3: String) = s1 + s2 + s3
val a = test("s1") _
println(a)


만약 다음과 같은 형태를 사용할 수 있다.

def test(s1: String) = (s2: String) => s1 + s2
val a = test("s1")
println(a)



언커링하려면 다음과 같다.

def test(s1: String, s2: String) = s1 + s2
val curried = (test _).curried
println(curried("s1")("s2"))
println(test("s1", "s2"))


결과는 다음과 같다.


s1s2

s1s2




curried함수는 Function2 트레이트의 함수로서 정의되어 있는 함수이다. 

/** Creates a curried version of this function.
*
* @return a function `f` such that `f(x1)(x2) == apply(x1, x2)`
*/
@annotation.unspecialized def curried: T1 => T2 => R = {
(x1: T1) => (x2: T2) => apply(x1, x2)


만약 커링함수를 언커링할 수 있다. 

val uncurried = Function.uncurried(curried)
println(uncurried("s1", "s2"))



함수와 콜렉션를 받는 함수라면 다음처럼 사용할 수 있다. 

object MyCombinator {
def foreach[A, U](f: A => U)(list: List[A]): Unit = list foreach f
}
val printX = (s: String) => println(s)
val f = MyCombinator.foreach(printX) _
f(List("1", "2"))

결과는 다음과 같다.


1

2








다음은 부분 함수이다. PartialFunction을 사용한다. 부분 적용 함수와 완전히 다른 형태이다. 입력 값이 일정 범위에 있는지를 정의할 수 있는 함수 정도(수학적인 부분을 의미하는지 알려주는 함수)가 될 것이다. 따라서 case 문으로 많이 사용된다. 

val one: PartialFunction[Int, String] = { case 1 => "one" }
println(one.isDefinedAt(1))
println(one.isDefinedAt(2))
println(one(1))


결과는 다음과 같다.


true

false





PartialFunction의 또 다른 예시이다. 

  val div: PartialFunction[(Double, Double), Double] = {
case (x, y) if y != 0 => x /y
}
println(div.isDefinedAt(1, 1))
println(div.isDefinedAt(2, 1))
println(div(1, 1))
}

결과는 다음과 같다.


true

true

1.0




PartialFunction는 case문 아니면 에러가 발생한다.  다음과 같은 코드에 컴파일 에러가 발생한다.

val one: PartialFunction[Int, String] = { "" }


Error:(9, 45) type mismatch;

 found   : String("")

 required: PartialFunction[Int,String]

  val one: PartialFunction[Int, String] = { "" }




항상 case 문이 있어야 하지만, 다음 코드는 컴파일 에러가 발생하지 않는다. 정의대로 구현했기 때문이다.

val one = new PartialFunction[Int, String] {
def apply(d: Int) = ""
def isDefinedAt(d: Int) = d != 0
}




PartialFunction 트레이트와 PartionFuction 오브젝트가 존재한다. 

trait PartialFunction[-A, +B] extends (A => B) { self =>
import PartialFunction._ ..

object PartialFunction {






저작자 표시
신고

'scala' 카테고리의 다른 글

[scala] 부분 적용 함수 / 커링 / 부분 함수  (0) 2016.12.05
[scala] 꼬리 재귀(tail recursion)와 @tailrec  (0) 2016.12.05
[scala] Future말고 Promise  (0) 2016.11.27
[scala] Future 2  (0) 2016.11.23
[scala] Future 1  (0) 2016.11.22
[zeppelin] spark의 변수 공유하기  (0) 2016.11.18
Posted by 김용환 '김용환'

스칼라에서의 꼬리 재귀와 tailrec를 공부한다.




머리 재귀 예시이다. 일면 stack over flow가 발생할 수 있다. 


def sum1(list: List[Int]): Int = list match {
case Nil => 0
case t :: tail => t + sum1(tail)
}
println(sum1((1 to 2).toList))
//println(sum1((1 to 1000000).toList)) //Exception in thread "main" java.lang.StackOverflowError

def sum2(list: List[Int]): Int = {
if (list.isEmpty) 0
else list.head + sum2(list.tail)
}
println(sum2((1 to 2).toList))
//println(sum2((1 to 1000000).toList)) //Exception in thread "main" java.lang.StackOverflowError

결과는 3이다.





중간값을 가진 꼬리 재귀로 구현해 보자. 



def sum3(list: List[Int], acc: Int): Int = {
if (list.isEmpty) acc
else sum3(list.tail, list.head + acc)
}
println(sum3(((1 to 2).toList), 0))
println(sum3((1 to 1000000).toList, 0))

def sum4(list: List[Int], acc: Int): Int = list match {
case Nil => acc
case h :: tail => sum4(tail, h + acc)
}
println(sum4(((1 to 2).toList), 0))
println(sum4((1 to 1000000).toList, 0))

결과는 다음과 같다. 중간 값을 저장했기 때문에 stack over flow가 발생하지 않았다. 


3

1784293664

3

1784293664






중간값을 꼬리 재귀에 조금만 수정해서 앞의 entry 함수를 하나 만들어본다.

def tailrecSum(l: List[Int]): Int = {
def sum5(list: List[Int], acc: Int): Int = list match {
case Nil => acc
case x :: tail => sum5(tail, acc + x)
}
sum5(l, 0)
}

println(tailrecSum((1 to 1000000).toList))


결과는 다음과 같다. 


1784293664








스칼라에는 꼬리 재귀 최적화 기능을 가지고 있다. 


@tailrec라고 재귀 함수 앞에 붙이면 스칼라 컴파일러에 꼬리 재귀가 있으니 최적화라고 알려준다.


@tailrec를 사용하려면 다음 import문을 사용한다.

import scala.annotation.tailrec




앞에 실행했던 예시는 아래와 같이 sum5앞에 @tailrec를 붙였다.

def tailrecSum(l: List[Int]): Int = { @tailrec
def sum5(list: List[Int], acc: Int): Int = list match {
case Nil => acc
case x :: tail => sum5(tail, acc + x)
}
sum5(l, 0)
}

println(tailrecSum((1 to 1000000).toList))



@tailrec는 아무 때나 최적화되지 않고, 심지어 에러가 발생할 수 있으니. 신중히 써야 할 수 있다.



아래 코드는 Recursive call not in position 이라는 컴파일 에러가 발생한다.

@tailrec
def factorial(i: BigInt): BigInt = {
if (i == 1) i
else i * factorial(i - 1)
}

for (i <- 1 to 10)
println(s"$i:\t${factorial(i)}")


재귀 함수가 public이면, 상속받아서 쓸 수 있기 때문에 쓰지 못하도록 에러를 발생시킨다.


class Printer(msg: String) {
@tailrec
def printMessageNTimes(n: Int): Unit = {
if(n > 0){
println(msg)
printMessageNTimes(n - 1)
}
}
}

new Printer("m").printMessageNTimes(10000)

could not optimize @tailrec annotated method printMessageNTimes: it is neither private nor final so can be overridden



final 메소드로 수정하니. 정상적으로 동작한다.

class Printer(msg: String) {
@tailrec
final def printMessageNTimes(n: Int): Unit = {
if(n > 0){
println(msg)
printMessageNTimes(n - 1)
}
}
}

new Printer("m").printMessageNTimes(10000)




트램폴린(trampoline)은 여러 함수가 다른 함수를 호출하여 이루어지는 재귀를 말한다. 


X를 호출하면 A를 호출했다가 A의 내부에서 B를 호출했고 B의 내부에서 B를 호출하면서. 계속 왔다 갔다하는 형태의 재귀를 말한다. 



스칼라에는 TailCall(https://www.scala-lang.org/api/current/scala/util/control/TailCalls$.html) 이라를 객체가 있으니, 이를 참조한다.


import scala.util.control.TailCalls._

def isEven(xs: List[Int]): TailRec[Boolean] =
  if (xs.isEmpty) done(true) else tailcall(isOdd(xs.tail))

def isOdd(xs: List[Int]): TailRec[Boolean] =
 if (xs.isEmpty) done(false) else tailcall(isEven(xs.tail))

isEven((1 to 100000).toList).result

def fib(n: Int): TailRec[Int] =
  if (n < 2) done(n) else for {
    x <- tailcall(fib(n - 1))
    y <- tailcall(fib(n - 2))
  } yield (x + y)

fib(40).result


저작자 표시
신고

'scala' 카테고리의 다른 글

[scala] 부분 적용 함수 / 커링 / 부분 함수  (0) 2016.12.05
[scala] 꼬리 재귀(tail recursion)와 @tailrec  (0) 2016.12.05
[scala] Future말고 Promise  (0) 2016.11.27
[scala] Future 2  (0) 2016.11.23
[scala] Future 1  (0) 2016.11.22
[zeppelin] spark의 변수 공유하기  (0) 2016.11.18
Posted by 김용환 '김용환'


자바에서 특정 클래스를 deep copy하려면 Object#clone() 메소드를 이용한다. 


    protected native Object clone() throws CloneNotSupportedException;



public class GcmMessage {


public Object clone() throws CloneNotSupportedException {

return (GcmMessage) super.clone();

}

}


//코드

GcmMessage gcmMessage = new GcmMessage();

GcmMessage newGcmMessage = (GcmMessage ) gcmMessage.clone();



List, Map과 같은 일부 Collection은 interface이라서 clone() 메소드를 지원하지 않는다. 



이럴 때는 ArrayList 또는 HashMap같은 실제 클래스(concrete class)를 사용해야 하지만...

Guava의 Lists또는 Maps(com.google.common.collect.Maps)의 static 생성 메소드를 사용하는 것이 훨씬 편하다.

Maps.newHashMap() 또는 Lists.newArrayList()와 같은 메소드를 사용하는 것이 deep copy에 훨씬 편하다.



Map<String, Object> newMap = Maps.newHashMap(map);









저작자 표시
신고
Posted by 김용환 '김용환'


티스토리 툴바