-
[Scala]9-흐름 제어 추상화scala 2020. 11. 9. 00:21
P211
이렇게 함수를 인자로 받는 함수를 고차 함수 higher-order fuction 라 한다. 이러한 고차 함수는 코드를 간단하게 압축할 수 있는 더 많은 기회를 제공한다.
FileMatcher v1
object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesEnding(query: String) = for (file <- filesHere; if file.getName.endsWith(query)) yield file }
FileMatcher v2 기능추가
object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesEnding(query: String) = for (file <- filesHere; if file.getName.endsWith(query)) yield file def filesContaining(query: String) = for (file <- filesHere; if file.getName.contains(query)) yield file def filesRegex(query: String) = for (file <- filesHere; if file.getName.matches(query)) yield file }
FileMatcher v3 고차함수 사용
object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesMatching(query: String, matcher: (String, String) => Boolean) = { for (file <- filesHere; if matcher(file.getName, query)) yield file } def filesEnding(query: String) = filesMatching(query, _.endsWith(_)) def filesContaining(query: String) = filesMatching(query, _.contains(_)) def filesRegex(query: String) = filesMatching(query, _.matches(_)) }
FileMatcher v4 클로저 사용
object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesMatching( matcher: (String) => Boolean) = { for (file <- filesHere; if matcher(file.getName)) yield file } def filesEnding(query: String) = filesMatching( _.endsWith(query)) def filesContaining(query: String) = filesMatching( _.contains(query)) def filesRegex(query: String) = filesMatching( _.matches(query)) }
P215
고차 함수가 API 구현 시 코드 중복을 제거할 수 있음을 예제를 통해 보였다. 고차 함수의 또 다른 중요한 용도는 API에 고차 함수를 포함시켜 클라이언트 코드를 더 간결하게 만드는 것이다. 스칼라 컬렉션 타입의 특별 루프 메소드는 그 좋은 예다.
containsNeg v1
def containsNeg(nums: List[Int]): Boolean = { var exists = false for (num <- nums) if (num < 0) exists = true exists }
containsNeg v2 루프 메소드 사용
def containsNeg(nums: List[Int]) = nums.exists(_ <0)
P217
본래 언어에서 지원하는 듯한 추상화 구문을 만드는 방법을 이해하려면, 먼저 함수 언어에서 사용하는 기법 중 커링 currying을 이해해야 한다. 커링한 함수는 인자 목록이 하나가 아니고 여럿이다.
전형적인 형태의 함수 정의
def plainOldSum(x: Int, y: Int) = x + y
커링한 함수의 정의
def curriedSum(x: Int)(y: Int) = x + y
커링 함수 사용 방식
val onePlus = curriedSum(1)_ printf(onePlus(2) + "\n") val twoPlus = curriedSum(2)_ printf(twoPlus(2) + "\n")
P220
빌려주기 패턴을 사용하여 파일 쓰기
import java.io.File import java.io.PrintWriter import java.util.Date 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 Date) )
이 메소드를 사용하는 경우, 사용자 코드가 아니라 withPrintWriter가 파일 닫기를 보장한다는 장점이 있다. 이러한 기법을 빌려주기 패턴 loan pattern 이라고 부른다. withPrintWriter처럼 제어 추상화를 하는 함수가 자원을 열어 특정 함수에게 해당 자원을 빌려주기 때문이다.
커링을 사용하여 파일 쓰기
import java.io.File import java.io.PrintWriter import java.util.Date def withPrintWriter(file: File) (op: PrintWriter => Unit) { val writer = new PrintWriter(file) try { op(writer) } finally { writer.close() } } val file = new File("date.txt") withPrintWriter(file) { writer => writer.println(new Date) }
P223
이름에 의한 호출 파라미터(by-name parameter)를 이름 붙인 인자(named parameter)와 혼돈하지 않기 바란다. 함수를 호출할 때의 인자 계산 방식 차이임을 밝히기 위해 이름에 의한 호출 파라미터라고 번역했다. 'by-name'이라는 말은 'call by name'에서 따온 것 같다. 값에 의한 호출(call by value) 참조에 의한 호출(call by reference)이름에 의한 호출(call by name)이나 엄격한 계산(strict evaluation) 또는 미리 계산 (eager evaluation) 지연 계산 (lazy evaluation) 등의 차이에 대해서는 프로그래밍 언어 교재나 인터넷의 여러 문서 등을 살펴보라
고차함수 형태의 함수 호출
var assertionsEnabled = true def myAssert(predicate: () => Boolean) = if (assertionsEnabled && !predicate()) throw new AssertionError printf(myAssert(() => 5 > 3) + "\n" )
이름에 의한 호출 사용
var assertionsEnabled = true def byNameAssert(predicate: => Boolean) = if (assertionsEnabled && !predicate) throw new AssertionError printf(byNameAssert( 5 > 3) + "\n" ) printf(byNameAssert( 1 / 0 == 0) + "\n" )