jam 블로그

러닝스칼라 5장 - 일급 함수 본문

IT Book Study/러닝 스칼라

러닝스칼라 5장 - 일급 함수

kid1412 2020. 9. 28. 23:14
728x90

스칼라는 일급 함수, 선언형 프로그래밍을 지원한다.

**일급 함수(first-class function)** : 함수형 프로그래밍에서 함수는 일급(first-class) 객체

  1. 다른 데이터 타입처럼 리터럴 형태로 생성될 수 있다.
  2. val, var로 저장될 수 있다.
  3. 다른 함수의 매개변수나 반환값으로 사용될 수 있다.

**선언형 프로그래밍(declarative programming)** : 고차함수(또는 다른 함수를 사용하는 로직)는 단순히 이루어져야 할 작업을 선언하고 그 작업을 직접 수행하지 않음

함수 타입과 값

함수 타입은 입력 타입과 반환값 타입의 그룹으로 표현된다.

([<타입>, ...]) => <타입>

def double(x: Int): Int = x * 2
val myDouble: Int => Int = double
myDouble(5)

와일드 카드 연산자로 함수를 할당하면 함수 타입을 쓰지 않아도 된다.

val <식별자> = <함수명> _

val myDouble = double _

고차함수(higher-order function)

입력 매개변수나 반환값으로 함수 타입의 값을 가지는 함수

// safeStringOp: (s: String, f: String => String): String
def safeStringOp(s: String, f: String => String) = {
  if (s != null) f(s) else s
}

def reverser(s: String) = s.reverse

safeStringOp(null, reverser) // null
safeStringOp("Ready", reverser) // "ydaeR"

함수 리터럴(function literal)

함수명이 없는 함수
익명 함수(Anonymous function), 람다 표현식(Lambda expression), 람다(Lambda), (function0, function1, function2…) 으로도 불린다.

([식별자: <타입>, ...]) => <표현식>

val doubler = (x: Int) => x * 2
// val doubler = x => x * 2 안됨
val doubled = doubler(22) // 44

함수 리터럴은 고차 함수 호출 내부에서 정의 가능

def safeStringOp(s: String, f: String => String) = {
  if (s != null) f(s) else s
}
safeStringOp("Ready", (s: String) => s.reverse)
// safeStringOp("Ready", s => s.reverse) 가능

자리표시자 구문(placeholder syntax)

매개변수를 와일드카드 연산자( _ )로 대체

1) 함수의 명시적 타입이 정의되어 있고, 2) 매개변수가 한 번 사용되거나 사용되지 않을 때 사용가능
placeholder의 개수는 입력 인수 개수와 동일

val doubler: Int => Int = _ * 2

def safeStringOp(s: String, f: String => String) = {
  if (s != null) f(s) else s
}
safeStringOp("Ready", _.reverse)

def combination(x: Int, y: Int, f: (Int, Int) => Int) = f(x,y)
combination(23, 12, _ * _) // 23*12

타입 매개변수 사용하기

def tripleOp1[A, B](a: A, b: A, c: A, f: (A, A, A) => B) = f(a,b,c)
tripleOp1[Int, Double](23, 92, 14, _ * _ + _)
// tripleOp1(23, 92, 14, _ * _ + _) 불가능

def tripleOp2[A, B](a: A, b: A, c: A)(f: (A, A, A) => B) = f(a,b,c)
tripleOp2(23, 92, 14)( _ * _ + _)

부분 적용 함수(Partially Applied Function)와 커링(currying)

부분 적용 함수: 와일드 카드 연산자를 이용해서 어떤 함수를 부분적으로 적용한 함수

def factorOf(x: Int, y: Int) = y % x == 0
val multipleOf3 = factorOf(3, _: Int)
val y = multipleOf3(78)

커링: 한 매개변수 그룹은 적용하고 다른 그룹은 적용하지 않는 기법

def factorOf(x: Int)(y: Int) = y % x == 0
val isEven = factorOf(2) _
val z = isEven(32) // true

다중 매개변수 그룹을 가지는 함수를 함수 체인(chain) 형태로 간주한다.
예를 들어 def factorOf(x: Int, y: Int)의 함수 타입은 (Int, Int) => Boolean이다.
하지만 def factorOf(x: Int)(y: Int)의 함수 타입은 Int => Int => Boolean (체인 형식)이다.
이 함수를 커링하면 Int => Boolean이 된다.

이름에 의한(by-name) 호출 매개변수

값 또는 값을 반환하는 함수 모두를 취할 수 있는 매개변수, 함수 유연성을 높일 수 있다.

<식별자>: => <타입>

만약 함수를 매개변수로 받는 경우, 함수를 사용할 때 마다 함수를 호출한다.
함수에 반복하여 접근할 때 발생할 수 있는 문제들에 대해 생각하고 사용해야된다.
예를 들어, 데이터베이스에 고정값을 검색하고 그 값을 반환하는 표현식을 by-name 호출 매개변수로 사용한다면 함수 내에서 사용될 때마다 데이터베이스에 접근하게 된다.

def doubles(x: => Int) = {
  println("Now doubling " + x)
  x * 2
}

doubles(5)
// Now doubling 5
// res18: Int = 10

def f(i: Int) = { println(s"Hello from f($i)"); i }

doubles( f(18) )
// Hello from f(8)
// Now doubling 8
// Hello from f(8)
// res19: Int = 16

부분 함수(partial function)

입력타입을 만족하는 값을 모두 처리하지 못하는 함수
컬렉션과 패턴 매칭으로 작업할 때 유용하다.

val statusHandler: Int => String = {
  case 200 => "Okay"
  case 400 => "Your Error"
  case 500 => "Our Error"
}

statusHandler(401) // scala.MatchError 발생

함수 리터럴 블록으로 고차 함수 호출하기

def safeStringOp(s: String)(f: String => String) = {
  if ( s != null ) f(s) else s
}

val uuid = java.util.UUID.randomUUID.toString
val timedUUID = safeStringOp(uuid) { s =>
  val now = System.currentTimeMillis
  val timed = s.take(24) + now
  timed.toUpperCase
}
  • 데이터베이스 트랜젝션 관리에서 고차 함수는 세션을 열고, 함수 매개변수를 호출하고, 커밋또는 롤백을 하여 트랜젝션 종료
  • 오류를 내지 않을 때까지 함수 매개변수를 여러번 재시도
  • 지역, 전역, 외부값(환경변수)에 기반하여 조건부로 함수 매개변수 호출

함수 내용에 관계 없이 일반화해서 처리할 수 있는 부분를 고차 함수로 정의

Comments