스칼라에서 지원하는 고차 함수 - 간결한 코드로 강력한 기능 구현하기

View: 158 0 0
작성자: 달빛제이크
카테고리: Scala Language
발행: 2024-06-27 수정 2024-06-30

안녕하세요. 달빛제이크입니다.

고차 함수는 다른 함수를 인수로 받거나, 함수를 반환하는 함수를 말하고, 스칼라에서는 함수 값(Function value)을 통해 고차 함수를 구현할 수 있습니다. 고차 함수를 API로 제공하면 클라이언트 코드를 보다 간결하게 만들 수 있는데, 스칼라에서는 막강한 기능을 가진 고차 함수들을 다양하게 제공하고 있어 간결한 코드 구현이 가능합니다. 특히 반복 작업이 필요한 Collection에서 매우 유용하게 활용할 수 있습니다.

이번 글에서는 스칼라 프로그래밍에서 고차 함수를 어떻게 활용하는 지 List Collction type에서 제공하는 함수들을 살펴보고 몇 가지 주요 함수들에 대해 코드와 함께 살펴보겠습니다.

1. List의 고차 함수

List의 주요 고차 함수 소개

List 콜렉션 타입에서 제공하는 고차 함수는 count, exists, filter, forall, foreach, map, flatMap, sortWith 등 매우 다양합니다. 이 함수들은 기본 기능을 바탕으로 사용자가 원하는 Algorithm을 실행하기 때문에 반복문이나 조건문이 필요한 매우 많은 영역에서 사용자의 코드를 간결하게 만들어 줍니다.

  • count - 조건을 만족하는 원소의 개수를 반환
  • exists - 조건을 만족하는 원소가 있으면 true, 없으면 false를 반환
  • filter - 조건을 만족하는 원소들의 List를 반환
  • filterNot - 조건을 만족하지 않는 원소들의 List를 반환
  • forall - 모든 원소가 조건을 만족할 때 true를 반환
  • foreach - 각 원소들에 대한 Task를 실행 (Side Effect 실행)
  • map - 각 원소들에 대해 주어진 알고리즘을 수행 후 List로 반환
  • flatMap - map과 동일한 기능을 수행. List를 중첩해서 사용할 경우 결과를 평탄화(flatten)하여 단일 구조로 변환
  • sortWith - 각 원소들을 주어진 조건으로 비교하여 정렬
  • sortBy - 각 원소들에 대해 제공한 기준을 적용하여 정렬
  • find - 조건을 만족하는 첫 번째 요소를 Option 타입으로 반환
  • reduce - 순차적으로 제공한 알고리즘을 적용하여 누적한 값을 반환, List가 비어있으면 예외 발생
  • fold - 주어진 초기 값에 각 원소들을 순차적으로 결합하여 하나의 축약 값을 반환

이 함수들은 다른 콜렉션 타입에서도 동일한 이름과 기능을 제공하기 때문에 사용 방법을 한 번 숙지해놓으면 두루 사용이 가능합니다.

2. foreach

foreach 함수의 사용법과 예제

foreach는 println과 pair로 컬렉션에서 값을 확인하기 위해서 많이 사용하는 고차 함수입니다.

val fruits = List("apple", "banana", "cherry")
fruits.foreach(println)    // = fruits.foreach(fruit => println(fruit))으로 표현 가능

fruits 변수에 과일 이름들이 담겨 있는 List를 할당하고 foreach로 각각의 과일 이름을 출력하는 코드 입니다. 이는 for 문과 while 문으로 다음과 같이 작성할 수 있습니다.

val fruits = List("apple", "banana", "cherry")

// for-do 표현식
for fruit <- fruits do
  println(fruit)

// while 문
var i = 0
while i < fruits.length do
  println(fruits(i))
  i += 1

스칼라의 문법에 군더더기가 없다 보니, for 문이나 while 문으로 작성을 해도 그 간결함이 눈에 들어옵니다. 다만, for 문과 while 문으로 코드를 구현하게 되면 출력이라는 목적 보다 출력을 하기 위한 과정을 코드에 담아야 하는 생각의 전개가 필요합니다. foreach를 사용하면 출력이라는 목적 자체 즉 도메인을 구현하기 위한 알고리즘에 더 집중할 수 있게 됩니다.

3. exists

exists 함수의 사용법과 예제

컬렉션 내에 조건을 만족하는 원소의 유무에 따라 true, false를 반환하는 고차 함수 입니다.

val nums = List(1, 2, 3, 4)
nums.exists( _ < 0)    // false

val nums = List(-2, -1, 0)
nums.exists( _ < 0)    // true

_ < 0num: Int => num < 0을 placeholder syntax로 간략하게 표현한 것입니다.
이 코드는 for 문을 사용해서 다음과 같이 작성할 수 있습니다.

val nums = List(1, 2, 3, 4)

var exists = false
for num <- nums do
  if num < 0 then
    exists = true

4. filter

filter 함수의 사용법과 예제

filter는 컬렉션에서 특정 조건을 만족하는 요소들만 선택해서 동일한 타입의 새로운 컬렉션을 생성하는 고차 함수입니다.

val numbers = List(1, 2, 3, 4, 5, 6)
val evenNumbers = numbers.filter(n => n % 2 == 0)    // 짝수 선택
println(evenNumbers)    // List(2, 4, 6)

val fruits = List("apple", "banana", "cherry", "date")
val fruitsWithA = fruits.filter(fruit => fruit.contains("a"))    // a를 포함하는 문자열 선택
println(fruitsWithA)    // List("apple", "banana", "date")

val ages = Map("Alice" -> 25, "Bob" -> 20, "Charlie" -> 30)
val adults = ages.filter ( (name, age) => age >= 25 )    // 25세 이상인 사람 선택
println(adulsts)    // Map("Alice" -> 25, "Charlie" -> 30)

filter는 컬렉션에서 필요한 요소만 선택해주기 때문에 매우 다양한 상황에서 많이 사용합니다. 예제에서는 숫자, 문자열, Map에 대한 활용 사례를 간단히 코드로 작성하였습니다. 이를 절차형 프로그래밍 방식으로 구현하기 위해서는 for 문과 같은 반복문을 통해 내부 과정을 모두 구현해 주어야 하지만, 스칼라 언어에서는 자체 라이브러리에서 제공하는 고차 함수와 인수로 전달되는 함수 리터럴을 통해 간단히 구현하였습니다.

5. fold

fold 함수의 사용법과 예제

이번에는 좀 더 복잡한 형식을 가지고 있는 fold라는 고차 함수에 대해서 알아 보겠습니다. fold는 컬렉션에서 요소들을 축약하여 하나의 값으로 만드는 고차 함수 입니다. reduce와 유사한 기능을 가지고 있는데, reduce는 초기 값이 없다면 fold는 주어진 초기 값에 알고리즘 적용 값을 누적합니다. 초기 값이 필요하기 때문에 초기 값에 해당하는 파라미터가 제공되어야 합니다. 다른 고차 함수에 비해 파라미터 수가 많다 보니 복잡해 보일 수 있는데 그리 어렵지 않습니다.

먼저 간단한 예제를 살펴 보겠습니다.

val numbers = List(1, 2, 3, 4, 5)

// 초기 값 0과 각 요소를 더하여 합계를 계산
val sum = numbers.fold(0)((acc, n) => acc + n)

println(sum)    // 15

fold 함수에 0이라는 초기 값을 주고 numbers의 각 요소들을 모두 더한 합계를 반환 합니다. fold 함수의 파라미터 선언 부에 괄호를 두 개 사용했는데 이는 다음 글에서 살펴볼 Currying이라는 프로그래밍 기법입니다. 첫 번째 주어진 0이라는 인수를 acc에 담고 n에 할당한 각 요소들을 acc에 더하여 최종 합계를 산출합니다. 이를 문자열에 적용하면 다음과 같습니다.

val words = List("Scala", "is", "fun")

// 초기 값 ""와 각 요소를 결합하여 하나의 문자열을 만듦
val sentence = words.fold("")((acc, word) => acc + " " + word)

println(sentence.trim)    // "Scala is fun"

초기 값으로 "" (빈 문자열)이 주어지고 words의 각 요소들이 결합하여 최종 Scala is fun 결과물이 만들어졌습니다.

지금까지 스칼라 언어에서 제공하는 고차 함수에 대해서 살펴보고 몇 가지 함수의 예제 코드를 통해 사용 방법을 확인 했습니다. 이들 고차 함수들은 스칼라로 프로그래밍을 하다 보면 수시로 사용되기 때문에 금방 익숙해지고 자연스럽게 사용할 수 있습니다. 고차 함수는 개발자가 기능을 구현하려는 데 드는 절차적 코딩 노력을 줄이고, 도메인에 집중하여 알고리즘을 구현할 수 있도록 돕는 스칼라의 중요한 언어적 요소입니다.

다음 글에서는 예제에서 나왔던 커링에 대해서 이야기 하겠습니다. 감사합니다.

comments 0