메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

함수형 사고: 함수적으로 사고하기, Part 3

필터링, 유닛 테스팅 및 코드 재사용을 위한 기술

Neal Ford, 소프트웨어 아키텍트, IBM
Neal Ford사진
Neal Ford는 글로벌 IT 컨설팅 업체인 ThoughtWorks의 소프트웨어 아키텍트이자 Meme Wrangler이다. 애플리케이션, 교육용 자료, 매거진 기사 및 비디오/DVD 프리젠테이션을 설계 및 개발하며 다양한 기술과 관련된 서적의 저자 또는 편집자이기도 하다. 최근에 출판된 책으로는 The Productive Programmer가 있다. 대규모 엔터프라이즈 애플리케이션의 설계 및 빌드에 많은 관심을 가지고 있는 그는 전세계의 개발자 컨퍼런스에서 국제적으로 인정 받고 있는 연사로도 활동하고 있다. 그의 웹 사이트를 살펴보자.

요약:  함수형 사고 시리즈의 저자인 Neal Ford는 함수형 프로그래밍 구조와 패러다임에 대해 계속 안내합니다. Scala로 된 숫자 분류 코드를 살펴보고 함수형 영역에서의 유닛 테스팅에 대해 간단히 살펴볼 것입니다. 그 다음에 부분적 애플리케이션과 커링(currying) — 코드 재사용을 활용할 수 있는 두 가지 함수형 접근방식 —에 대해 학습하고 재귀가 함수형 사고 방식에 어떻게 부합하는지 확인할 것이다.

이 연재 자세히 보기

기사 게재일:  2011 년 9 월 27 일
난이도: 중급 원문:  보기 PDF:  A4 and Letter (42KB | 9 pages)Get Adobe® Reader®
페이지뷰:  1366 회
의견:  


이 시리즈의 정보

이 시리즈는 독자의 관점을 기능적 사고방식으로 새로운 방향을 지정하여, 새로운 방식에서 공통적인 문제점을 살펴보고 일상적인 코딩을 개선하는 방법을 찾는 데 도움을 주는 것을 목표로 한다. 이는 함수형 프로그래밍 개념, Java 언어 내에서 함수형 프로그래밍을 허용하는 프레임워크, JVM에서 실행하는 함수형 프로그래밍 언어 및 언어 설계의 일부 미래 성향의 방향을 살펴본다. 이 시리즈는 Java 및 그 추상이 작업하는 방법을 알지만, 기능적 언어를 사용한 경험이 적거나 전혀 없는 개발자들에 적합하도록 맞춰져 있다.

Functional thinking첫 번째두 번째 기사에서 필자는 일부 함수형 프로그래밍 주제와 이러한 주제들이 Java™ 및 관련 언어에 어떻게 관련되는지 조사했다. 이번 기사에서는 이 탐색을 계속하여, 이전 기사에 나온 숫자 규정자의 Scala 버전을 알려주고 커링, 부분적 애플리케이션재귀의 일부 학문적인 주제에 대해 논의한다.

Scala로 된 숫자 규정자

필자는 숫자 규정자의 Scala 버전이 적어도 Java 개발자들에게는 구문적인 수수께끼가 가장 적기 때문에 마지막으로 남겨두었다. (규정자의 요구사항을 요약하면, 1보다 큰 어느 양의 정수나 주어지면, 이를 완전수(perfect), 과잉수(abundant) 또는 부족수(deficient)로 분류해야 한다. 완전수는 그 수 자체는 인수로 제외하고, 그 인수들을 합하면 최대 그 수까지 되는 수이다. 과잉수의 인수의 합계는 그 수보다 크고, 부족수의 인수의 합계는 그 수보다 적다.) 목록 1은 다음 Scala 버전을 보여준다.


목록 1. Scala로 된 숫자 규정자

package com.nealford.conf.ft.numberclassifier

object NumberClassifier {

  def isFactor(number: Int, potentialFactor: Int) =
    number % potentialFactor == 0

  def factors(number: Int) =
    (1 to number) filter (number % _ == 0)

  def sum(factors: Seq[Int]) =
    factors.foldLeft(0)(_ + _)

  def isPerfect(number: Int) =
    sum(factors(number)) - number == number

  def isAbundant(number: Int) =
    sum(factors(number)) - number > number

  def isDeficient(number: Int) =
    sum(factors(number)) - number < number
}  

독자가 지금까지 Scala를 본 적이 없다고 해도 이 코드는 매우 가독성이 높을 것이다. 이전과 같이, 두 가지 관심 있는 메소드는 factors()sum()이다. factors() 메소드는 1에서부터 대상 숫자까지 숫자 목록을 취하여, 필터링 범주로서 오른쪽의 코드 블록을 사용하여(그렇지 않으면, predicate라고 함) Scala의 내장 filter() 메소드를 적용한다. 코드 블록은 Scala의 내재된 매개변수를 활용하며, 이는 이름 지정된 변수가 필요하지 않을 때 이름이 없는 플레이스홀더(_ 문자)를 허용한다. Scala의 구문적 유연성 덕분에 filter() 메소드를 연산자를 호출하는 것과 동일한 방법으로 호출할 수 있다. 독자가 원한다면, (1 to number).filter((number % _ == 0))도 작동한다.

sum() 메소드는 지금까지 익숙한 fold left 연산(Scala에서 foldLeft()로 구현됨)을 사용한다. 이 경우에 변수의 이름을 지정할 필요가 없기 때문에, 필자는 _를 플레이스홀더로 사용하며, 이는 코드 블록을 정의하기 위한 간단한 깔끔한 구문을 활용한다. foldLeft() 메소드는 Functional Java 라이브러리에서 이름이 유사한 메소드와 동일한 태스크를 수행한다(참고자료 참조). 이는 첫 번째 기사에 다음과 같이 나타난다.

  1. 초기 값을 취하여 목록의 첫 번째 요소에 대한 연산을 통해 이를 결합한다.
  2. 결과를 취하여 동일한 연산을 다음 요소에 적용한다.
  3. 목록이 소진될 때까지 이를 계속 수행한다.

이는 숫자 목록에 합하는 등의 연산을 적용하는 방법의 일반화된 버전이다. 0부터 시작하고 첫 번째 요소를 더하고 결과를 취하여 이를 두 번째 요소로 더하고 목록이 소모될 때까지 계속한다.

유닛 테스팅

필자가 이전 버전에 유닛 테스트를 알려준 적이 없다고 하더라도, 모든 예제에 테스트가 있다. ScalaTest라는 효율적인 유닛 테스팅 라이브러리는 Scala에 사용할 수 있다(참고자료 참조). 다음 목록 2는 필자가 목록 1isPerfect() 메소드를 확인하기 위해 작성한 첫 번째 유닛 테스트를 보여준다.


목록 2. Scala 숫자 규정자에 대한 유닛 테스트

@Test def negative_perfection() {
  for (i <- 1 until 10000)
    if (Set(6, 28, 496, 8128).contains(i))
      assertTrue(NumberClassifier.isPerfect(i))
    else
      assertFalse(NumberClassifier.isPerfect(i))
}  

하지만 독자와 같이 필자는 더 함수적으로 사고하기 위해 노력하고 있으므로, 목록 2의 코드는 두 가지 측면에서 골치거리이다. 첫 번째, 이는 무엇을 수행하기 위해 반복하며, 이는 명령적 사고를 시연한다. 두 번째, 필자는 바이너리 범용 if 명령문을 신경 쓰지 않는다. 필자가 해결 중인 문제가 무엇인가? 필자는 숫자 규정자가 완전수로 불완전수를 인식하지 않도록 해야 한다. 목록 3은 다음과 같이 약간 다르게 서술하여 이 문제에 대한 해결책을 보여준다.


목록 3. 완전수 분류에 대한 대안 테스트

@Test def alternate_perfection() {
  assertEquals(List(6, 28, 496, 8128),
              (1 until 10000) filter (NumberClassifier.isPerfect(_)))
}  

목록 3은 1부터 100,000까지 완전한 숫자만 알려진 완전수 목록에 속하는 것임을 주장한다. 함수적으로 사고하는 것은 코드로 확장될 뿐만 아니라 이를 테스팅하는 것에 대한 사고방식으로도 확장한다.


부분적 애플리케이션과 커링

목록 필터링으로 보여준 함수형 접근방식은 함수형 프로그래밍 언어 및 라이브러리 전반에서 일반적이다. 코드를 매개변수로 전달시키는 기능을 사용하는 것은(목록 3에서 filter() 메소드와 같이) 코드 재사용을 다른 방식으로 생각하는 것을 시연한다. 독자가 기존의 설계 패턴 구동형 객체 지향의 영역에 익숙하다면, 이 접근방식을 Gang of Four Design Patterns 책의 Template Method 설계 패턴에 대한 이러한 접근방식과 비교한다(참고자료 참조). Template Method 패턴은 추상 메소드를 사용하고 개별 세부사항을 하위 클래스로 지연하기 위해 대체하여 기반 클래스에서 알고리즘의 기본 폼을 정의한다. 컴포지션을 사용하는 함수형 접근방식을 통해 기능을 적절하게 적용하는 메소드로 기능을 전달할 수 있다.

코드 재사용을 달성하는 또 다른 방법은 커링을 사용하는 것이다. 수학자인 Haskell Curry(Haskell 프로그래밍 언어도 그의 이름에서 나옴)의 이름에서 따온 커링은 다중 인수 함수를 변환하므로 단일 인수 함수의 체인으로 불릴 수도 있다. 긴밀하게 연관된 부분적 애플리케이션은 고정 값을 하나 이상의 인수로 함수에 지정하기 위한 기술이며, 그렇게 함으로써 arity(함수로 매개변수의 숫자)가 더 적은 또 다른 함수를 제작한다. 차이점을 이해하기 위해 다음 목록 4에서 커링을 시연하는 Groovy 코드를 살펴보도록 하자.


목록 4. Groovy의 커링

def product = { x, y -> return x * y }

def quadrate = product.curry(4)
def octate = product.curry(8) 

println "4x4: ${quadrate.call(4)}"
println "5x8: ${octate(5)}"

목록 4에서 필자는 두 개의 매개변수를 승인하는 코드 블록으로 product를 정의한다. Groovy의 내장 curry() 메소드를 사용하여 product를 두 가지 새로운 코드 블록에 대한 빌딩 블록으로 사용한다. 즉, 이는 quadrateoctate이다. Groovy를 사용하면 코드 블록이 간편하게 호출된다. 즉, call() 메소드를 명백히 실행하거나 코드 블록 이름 다음에 어느 매개변수나 포함하는 소괄호 세트를 지정하는 제공된 언어 레벨 간편 표기법(Syntactic sugar)을 사용할 수 있다(예를 들어, octate(5)로 표시됨).

부분적 애플리케이션은 커링과 유사하지만 하나의 인수로 결과 함수를 제한하지 않는 폭넓은 기술이다. Groovy는 curry() 메소드를 사용하여 다음 목록 5와 같이 커링과 부분적 애플리케이션 둘 다 처리한다.


목록 5. 부분적 애플리케이션 대 커링, 둘 다 Groovy의 curry() 메소드 사용

def volume = { h, w, l -> return h * w * l }
def area = volume.curry(1)
def lengthPA = volume.curry(1, 1) //partial application
def lengthC = volume.curry(1).curry(1) // currying

println "The volume of the 2x3x4 rectangular solid is ${volume(2, 3, 4)}"
println "The area of the 3x4 rectangle is ${area(3, 4)}"
println "The length of the 6 line is ${lengthPA(6)}"
println "The length of the 6 line via curried function is ${lengthC(6)}"

목록 5에서 volume 코드 블록은 잘 알려진 공식을 사용하여 고체 사각형의 정육면체 볼륨을 계산한다. 그러면 필자가 volume의 첫 번째 차원(높이의 h)을 1로 고정하여 area 코드 블록(사각형 영역을 계산함)을 만든다. 행 세그먼트의 길이를 리턴하는 코드 블록에 대한 빌딩 블록으로서 volume을 사용하기 위해 필자는 부분적 애플리케이션이나 커링 중 하나를 수행할 수 있다. lengthPA1에서 처음 두 개의 매개변수 각각을 고정하여 부분적 애플리케이션을 사용한다. lengthC는 커링을 두 번 적용하여 동일한 결과를 낳는다. 차이점은 미묘하고 최종 결과는 동일하지만, 독자가 커링부분적 애플리케이션이라는 용어를 함수형 프로그래머 내에서 교환하여 사용한다면, 올바르게 될 것이다.

함수형 프로그래밍은 새롭고 다른 빌딩 블록을 제공하여 명령형 언어가 다른 메커니즘으로 완수하는 동일한 목표를 달성한다. 이러한 빌딩 블록 사이의 관계는 세심하게 고려되었다. 이전에 필자는 코드 재사용 메커니즘으로 컴포지션을 보여주었다. 커링과 컴포지션을 결합할 수 있다는 것은 놀라운 일이 아니다. 다음 목록 6의 Groovy 코드를 살펴보자.


목록 6. 부분적 애플리케이션 컴포지션

def composite = { f, g, x -> return f(g(x)) }
def thirtyTwoer = composite.curry(quadrate, octate)

println "composition of curried functions yields ${thirtyTwoer(2)}"

목록 6에서 필자는 두 가지 함수를 작성하는 composite 코드 블록을 작성한다. 해당 코드 블록을 사용하여 필자는 두 가지 메소드를 함께 작성하기 위해 부분적 애플리케이션을 사용하여 thirtyTwoer 코드 블록을 작성한다.

부분적 애플리케이션과 커링을 사용하면 독자는 Template Method 설계 패턴과 같은 메커니즘과 유사한 목표를 달성한다. 예를 들어, 독자는 다음 목록 7과 같이 adder 코드 블록 위에 incrementer 코드 블록을 빌드하여 이를 작성할 수 있다.


목록 7. 다른 빌딩 블록

def adder = { x, y -> return x + y }
def incrementer = adder.curry(1)

println "increment 7: ${incrementer(7)}"

물론, Scala는 다음 목록 8과 같이 Scala 문서에서 나온 스니펫으로 시연한 대로 커링을 지원한다.


목록 8. Scala로 된 커링

object CurryTest extends Application {

  def filter(xs: List[Int], p: Int => Boolean): List[Int] =
    if (xs.isEmpty) xs
    else if (p(xs.head)) xs.head :: filter(xs.tail, p)
    else filter(xs.tail, p)

  def dividesBy(n: Int)(x: Int) = ((x % n) == 0)

  val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
  println(filter(nums, dividesBy(2)))
  println(filter(nums, dividesBy(3)))
}

목록 8의 코드는 filter() 메소드로 사용된 dividesBy() 메소드를 구현하는 방법을 보여준다. 필자는 dividesBy() 메소드의 첫 번째 매개변수를 코드 블록을 작성하기 위해 사용된 값에 고정시키도록 커링을 사용하여 익명 함수를 filter() 메소드로 전달한다. 필자가 매개변수로 대상 숫자를 전달하여 작성된 코드 블록을 전달할 때, Scala는 새 함수를 커링한다.


재귀를 통한 필터링

함수형 프로그래밍에 긴밀하게 연관된 또 다른 주제가 재귀이며, 이는(Wikipedia에 따르면) "항목을 자체적으로 유사한 방식으로 반복하는 프로세스"이다. 실제로는 스스로 동일한 메소드를 호출하여 내용에 대해 반복하는 전산학적 방식이다(종료 조건을 보유하도록 항상 신중하게 보장함). 문제의 핵심이 감소되는 목록으로 동일한 사항을 계속 반복해야 하는 필요성이기 때문에, 많은 경우에 재귀로 인해 이해하기에 간편한 코드가 나타난다.

목록을 필터링하는 것을 고려하자. 반복적인 접근방식을 사용하여 필자는 필터링 범주를 승인하고 해당 컨텐츠에 대해 루프하여, 원하지 않는 요소를 필터링해낸다. 다음 목록 9는 Groovy로 필터링의 간단한 구현 방식을 보여준다.


목록 9. Groovy로 된 필터링

def filter(list, criteria) {
  def new_list = []
  list.each { i -> 
    if (criteria(i))
      new_list << i
  }
  return new_list
}

modBy2 = { n -> n % 2 == 0 }

l = filter(1..20, modBy2)
println l  

목록 9filter() 메소드는 listcriteria(목록을 필터링하는 방법을 지정하는 코드 블록)를 승인하고 목록에 대해 반복하며, 각 항목이 예측과 일치하는 경우 새 목록에 추가한다.

이제 목록 8을 돌아보자. 이는 Scala로 된 필터링 기능의 재귀적 구현 방식이다. 이는 목록을 처리하기 위한 함수적 언어로 된 일반적인 패턴을 따른다. 목록의 하나의 보기는 두 부분으로 되어있다. 즉, 이는 목록에서 앞의 항목(헤드)과 모든 다른 항목들이다. 많은 함수형 언어는 이러한 관용구를 사용하여 목록을 계속 반복하기 위한 특정 메소드를 보유한다. filter() 메소드는 먼저 목록이 비어 있는지 확인한다 — 이 메소드에 종료 조건이 매우 중요함. 목록이 비어 있으면, 간단히 리턴한다. 그렇지 않으면, 매개변수로 전달된 조건부 조건(p)을 사용한다. 이 조건이 true이면(필자가 목록에서 이 항목을 원하는 것을 의미함) 필자는 현재 헤드와 필터링된 나머지 목록을 취하여 구성된 새 목록을 리턴한다. 조건부 조건이 실패하면, 필자는 필터링된 나머지로만 구성된 새 목록을 리턴한다(첫 번째 요소 삭제). Scala로 된 목록 구성 연산자는 두 경우에 대한 리턴 조건을 매우 가독성이 높고 이해하기 간편하게 만든다.

필자가 추측하건대, 독자가 현재는 재귀를 전혀 사용하지 않는다 — 이는 도구 상자의 일부도 아니다. 하지만, 이러한 일부 원인은 대부분의 명령형 언어가 이에 대한 지원이 활성화되지 않아 실제보다 사용하기에 훨씬 더 어렵게 된다는 사실에 달려있다. 깔끔한 구문과 지원을 추가하여 함수형 언어를 사용하면 재귀는 간단한 코드 재사용의 후보가 된다.


결론

이번 기사는 함수적 사고 영역에서 기능에 대한 필자의 조사를 완성한다. 우연히도 이 기사의 대부분은 필터링에 대한 내용이라서 이를 사용하고 구현하는 방법을 많이 알려주었다. 하지만 이는 그렇게 놀라운 일도 아니다. 많은 함수형 패러다임은 목록과 관련하여 제작되었다. 왜냐하면 대부분의 프로그래밍 핵심은 목록을 처리하는 것이기 때문이다. 목록과 관련하여 더 강력한 기능을 가진 언어와 프레임워크 작성이 가능하다.

다음 기사에서 필자는 함수형 프로그래밍의 빌딩 블록 중 하나인 불변성(immutability)에 대해 깊이 논의할 것이다.


참고자료

교육

제품 및 기술

토론

필자소개

Neal Ford사진

Neal Ford는 글로벌 IT 컨설팅 업체인 ThoughtWorks의 소프트웨어 아키텍트이자 Meme Wrangler이다. 애플리케이션, 교육용 자료, 매거진 기사 및 비디오/DVD 프리젠테이션을 설계 및 개발하며 다양한 기술과 관련된 서적의 저자 또는 편집자이기도 하다. 최근에 출판된 책으로는 The Productive Programmer가 있다. 대규모 엔터프라이즈 애플리케이션의 설계 및 빌드에 많은 관심을 가지고 있는 그는 전세계의 개발자 컨퍼런스에서 국제적으로 인정 받고 있는 연사로도 활동하고 있다. 그의 웹 사이트를 살펴보자.

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=자바
ArticleID=761613
ArticleTitle=함수형 사고: 함수적으로 사고하기, Part 3
publish-date=09272011

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.