Scala - Higher Order Function
Higher Order Function (고차함수)
스칼라에서는 함수의 인자로 변수와 같은 값 뿐만 아니라 함수도 넘길 수 있다. 혹은 함수를 리턴받을수도 있다.
function as a value (값으로서 함수)
함수를 변수에 저장.
import scala.math._
val num = 3.14
val fun = ceil _ // fun을 ceil 함수로 설정. _는 함수를 뜻함.
// call fun
fun(num) // 4.0
// 함수를 다른 함수에 전달
Array(3.14, 1.42, 2.0).map(fun) // Array(4.0, 2.0, 2.0)
Anonymous Function (익명함수)
이름을 지정하지 않은 함수.
// 인자에 3을 곱한다.
(x: Double) => 3 * x
// 변수에 저장
val triple = (x: Double) => 3 * x
//익명함수를 다른 함수에 넘김.
Array(3.14, 1.42, 2.0).map((x: Double) => 3 * x) // Array(9.42, 4.26, 6.0)
맨 아래 줄 함수는 아래와 같이 작성할 수도 있다.
Array(3.14, 1.42, 2.0).map{ (x: Double) => 3 * x }
Array(3.14, 1.42, 2.0) map { (x: Double) => 3 * x }
함수 인자를 받는 함수
(parameterType) => resultType
// 인자로 들어오는 함수에 0.25를 인자로 줌.
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
valueAtOneQuarter(ceil _) // 1.0
valueAtOneQuarter(sqrt _) // 0.5
위 valueAtOneQuarter 함수의 타입은 ((Double) => Double) => Double
함수를 리턴
def mulBy(factor : Double) = (x : Double) => factor * x
val quintuple = mulBy(5)
quintuple(20) // 100
mulby 함수는 (Double) => ((Double) => Double)
parameter inference (인자 추론)
valueAtOneQuarter((x: Double) => 3 * x) // 0.75
valueAtOneQuarter((x) => 3 * x)
valueAtOneQuarter(x => 3 * x) // 인자가 하나이면 괄호 생략 가능
valueAtOneQuarter(3 * _) // 인자가 화살표 오른쪽에 하나만 나오면 이를 밑줄로 변경 가능
인자 타입을 미리 알 때만 단축 사용 가능.
val fun = 3 * _ // error: 타입 추론 불가
val fun = 3 * (_: Double) // OK
val fun: (Double) => Double = 3 * _ // fun의 type을 지정했기 때문에 ok
useful Higher order Function
// 삼각형을 출력하는 map
(1 to 9).map("*" * _).foreach(println _)
// sequence에서 짝수만 얻는 방법
(1 to 9).filter(_ % 2 == 0) // 2, 4, 6, 8
// 이항 함수를 받아서 왼쪽에서 오른쪽 순으로 시퀀스의 모든 원소에 적용.
(1 to 9).reduceLeft(_ * _) // (...((1 * 2) * 3) * ... * 9)
"Mary had a little lamb".split(" ").sortWith(_.length < _.length)
// return : Array("a", "had", "Mary", "lamb", "little")
closure
def mulBy(factor: Double) = (x: Double) => factor * x
클로저는 코드와 함께 코드를 사용하는 비지역 변수의 정의들로 구성.
이 함수들은 실제로는 인스턴스 변수 factor와 함수의 바디를 표함하고 있는 apply 메소드가 있는 클래스의 오브젝트로 구현된다.
SAM (Single Abstract Method)
예를 들어 버튼이 클릭되었을 때 카운터를 증가시키길 원한다고 가정.
val counter = 0
val button = new JButton("Increment")
button.addActionListener(new ActionListener {
override def actionPerformed(event: ActionEvent) {
counter += 1
}
})
를 아래와 같이 변경 가능.
button.addActionListener((event: ActionEvent) => counter += 1)
// 암묵 변환 제공
implicit def makeAction(action: (ActionEvent) => Unit) =
new ActionListener {
override def actionPerformed(event: ActionEvent) { action(event) }
}
Curring
인자 두개를 받는 함수를 인자 하나를 받는 함수로 바꾸는 프로세스.
def mul(x: Int, y: Int) = x * y // 두개의 인자를 받는 함수
def mulOneAtATime(x: Int) = (y: Int) => x * y // 하나의 인자를 받아 인자 하나를 받는 함수 리턴.
mulOneAtATime(6)(7) // 위 함수 호출
def mulOneAtATime(x: Int)(y: Int) = x * y // 커리 함수
val a = Array("Hello", "World")
val b = Array("hello", "world")
a.corresponds(b)(_.equalsIgnoreCase(_))
제어 추상화
def runInThread(block: () => Unit) {
new Thread {
override def run() { block() }
}.start()
}
runInThread { () => println("Hi"); Thread.sleep(10000); println("Bye") }
위 코드는 () => Unit
타입의 함수로 주어진다. 그러므로 호출 시에 () =>를 써주어야 한다.
이를 피하려면 아래처럼 이름으로 호출 표기법을 사용한다. 인자 선언과 인자 함수 호출에서 ()를 생략하고, => 는 생략하지 않는다.
def runInThread(block: => Unit) {
new Thread {
override def run() { block }
}.start()
}
runInThread { println("Hi"); Thread.sleep(10000); println("Bye") }
위 대신 until문을 정의해서 사용할 수도 있다.
def until(condition: => Boolean)(block: => Unit) {
if (!condition) {
block
until(condition)(block)
}
}
var x = 10
until (x == 0) {
x -= 1
println(x)
}
return 표현식
보통 스칼라는 값을 리턴하기 위해 return
문을 사용하지 않는다.
익명함수에서 바깥 이름 있는 함수에 값을 리턴하기 위해 return을 사용할 수 있다. 제어추상화에 유용하다.
def indexof(str: String, ch: Char): Int = {
var i = 0
until (i == str.length) {
if (str(i) == ch) return i
i += 1
}
return -1
}
이름 있는 함수에서 return을 사용할 때는 리턴 타입 지정 필요.