함수형 프로그래밍의 정의
클로저의 기쁨에 대한 소개 는 다음과 같습니다.
함수형 프로그래밍은 무정형 정의를 갖는 컴퓨팅 용어 중 하나입니다. 100 명의 프로그래머에게 그들의 정의를 요청하면 100 개의 다른 답변을받을 것입니다 ...
함수형 프로그래밍은 함수의 적용 및 구성을 용이하게합니다. 언어가 기능적으로 간주 되려면 함수 개념이 일류 여야합니다. 일류 함수는 다른 데이터와 마찬가지로 저장, 전달 및 반환 될 수 있습니다. 이 핵심 개념 이외에도, FP 정의에는 순도, 불변성, 재귀, 게으름 및 참조 투명성이 포함될 수 있습니다.
Scala 2 판에서의 프로그래밍 p. 10의 정의는 다음과 같습니다.
함수형 프로그래밍은 두 가지 주요 아이디어로 안내됩니다. 첫 번째 아이디어는 함수가 일류 값이라는 것입니다 ... 함수를 다른 함수에 인수로 전달하거나 함수의 결과로 반환하거나 변수에 저장할 수 있습니다 ...
함수형 프로그래밍의 두 번째 주요 아이디어는 프로그램 작업에서 데이터를 변경하지 않고 입력 값을 출력 값에 매핑해야한다는 것입니다.
우리가 첫 번째 정의를 받아 들인다면, 코드를 "기능적으로"만들기 위해해야 할 일은 루프를 안쪽으로 돌리는 것입니다. 두 번째 정의에는 불변성이 포함됩니다.
퍼스트 클래스 기능
현재 버스 오브젝트에서 승객 목록을 가져 와서 버스 요금에 따라 각 승객의 은행 계좌를 줄인다고 가정 해보십시오. 이와 동일한 동작을 수행하는 기능적인 방법은 버스에 메소드를 두는 것입니다. 한 인수의 함수를 취하는 forEachPassenger라고도합니다. 그런 다음 버스는 승객을 반복 처리하지만 가장 잘 수행되며 승차 요금을 청구하는 고객 코드가 기능을 수행하여 forEachPassenger로 전달됩니다. 짜잔! 함수형 프로그래밍을 사용하고 있습니다.
피할 수 없는:
for (Passenger p : Bus.getPassengers()) {
p.debit(fare);
}
기능적 (스칼라에서 익명 함수 또는 "람다"사용) :
myBus = myBus.forEachPassenger(p:Passenger -> { p.debit(fare) })
더 달콤한 스칼라 버전 :
myBus = myBus.forEachPassenger(_.debit(fare))
일류가 아닌 함수
만약 당신의 언어가 일류 함수를 지원하지 않는다면, 이것은 매우 추악해질 수 있습니다. Java 7 이하에서는 다음과 같은 "기능 객체"인터페이스를 제공해야합니다.
// Java 8 has java.util.function.Consumer, but in earlier
// versions you have to roll your own:
public interface Consumer<T> {
public void accept(T t);
}
그런 다음 Bus 클래스는 내부 반복자를 제공합니다.
public void forEachPassenger(Consumer<Passenger> c) {
for (Passenger p : passengers) {
c.accept(p);
}
}
마지막으로 익명 함수 객체를 버스에 전달합니다.
// Java 8 has syntactic sugar to make this look more like
// the Scala solution, but earlier versions require manually
// instantiating a "Function Object," in this case, a
// Consumer:
Bus.forEachPassenger(new Consumer<Passenger>() {
@Override
public void accept(final Passenger p) {
p.debit(fare);
}
}
Java 8에서는 로컬 변수를 익명 함수의 범위로 캡처 할 수 있지만 이전 버전에서는 이러한 변수를 final로 선언해야합니다. 이 문제를 해결하려면 MutableReference 래퍼 클래스를 만들어야합니다. 위 코드에 루프 카운터를 추가 할 수있는 정수 별 클래스는 다음과 같습니다.
public static class MutableIntWrapper {
private int i;
private MutableIntWrapper(int in) { i = in; }
public static MutableIntWrapper ofZero() {
return new MutableIntWrapper(0);
}
public int value() { return i; }
public void increment() { i++; }
}
final MutableIntWrapper count = MutableIntWrapper.ofZero();
Bus.forEachPassenger(new Consumer<Passenger>() {
@Override
public void accept(final Passenger p) {
p.debit(fare);
count.increment();
}
}
System.out.println(count.value());
이러한 추악함에도 내부 반복자를 제공하여 프로그램 전체에 퍼져있는 루프에서 복잡하고 반복되는 논리를 제거하는 것이 유리합니다.
이 못생긴 점은 Java 8에서 수정되었지만 1 급 함수 내에서 확인 된 예외를 처리하는 것은 여전히 정확하지 않으며 Java는 여전히 모든 컬렉션에서 변경 가능성을 가정합니다. 이는 종종 FP와 관련된 다른 목표를 가져옵니다.
불변성
Josh Bloch의 항목 13은 "불변성을 선호합니다"입니다. 일반적인 쓰레기 이야기와 반대로 OOP는 불변의 물건으로 수행 할 수 있으므로 그렇게하는 것이 훨씬 좋습니다. 예를 들어, Java의 문자열은 변경할 수 없습니다. StringBuffer, OTOH는 변경 불가능한 문자열을 빌드하기 위해 변경 가능해야합니다. 버퍼 작업과 같은 일부 작업에는 본질적으로 변경이 필요합니다.
청정
각 함수는 최소한 메모 가능해야합니다. 동일한 입력 매개 변수를 제공하고 (실제 인수 외에 입력이 없어야하는 경우) 전역 상태 변경과 같은 "부작용"을 유발하지 않고 매번 동일한 출력을 생성해야합니다. / O 또는 예외 발생
Functional Programming에서 "일을하기 위해서는 보통 악이 필요합니다." 100 % 순도는 일반적으로 목표가 아닙니다. 부작용을 최소화하는 것입니다.
결론
실제로 위의 모든 아이디어 중에서 불변성은 OOP 또는 FP 등 내 코드를 단순화하기위한 실용적인 응용 프로그램 측면에서 가장 큰 승리였습니다. 반복자에게 함수를 전달하는 것이 두 번째로 큰 승리입니다. 자바 8 주면서 문서는 왜 최선의 설명이 있습니다. 재귀는 트리 처리에 좋습니다. 게으름을 사용하면 무한한 컬렉션으로 작업 할 수 있습니다.
JVM이 마음에 들면 Scala와 Clojure를 살펴 보는 것이 좋습니다. 둘 다 함수형 프로그래밍에 대한 통찰력있는 해석입니다. 스칼라는 C와 비슷한 문법을 사용하지만 형식이 안전하지만 C와 마찬가지로 Haskell과 공통으로 많은 구문을 가지고 있습니다. Clojure는 형식이 안전하지 않으며 Lisp입니다. 최근에 하나의 특정 리팩토링 문제와 관련하여 Java, Scala 및 Clojure 의 비교를 게시했습니다 . Logan Campbell의 Game of Life를 사용한 비교 에는 Haskell과 Typed Clojure도 포함됩니다.
추신
지미 호파 (Jimmy Hoffa)는 나의 버스 클래스가 변경 가능하다고 지적했다. 원본을 수정하는 대신이 질문에 대한 리팩터링의 종류를 정확하게 보여줄 것이라고 생각합니다. 이는 버스의 각 방법을 공장으로 만들어 새 버스를 생성하고 승객의 각 방법을 공장에서 새 승객을 생성하도록하여 해결할 수 있습니다. 따라서 모든 것에 반환 유형을 추가했습니다. 이는 Consumer 인터페이스 대신 Java 8의 java.util.function.Function을 복사한다는 것을 의미합니다.
public interface Function<T,R> {
public R apply(T t);
// Note: I'm leaving out Java 8's compose() method here for simplicity
}
그런 다음 버스에서 :
public Bus mapPassengers(Function<Passenger,Passenger> c) {
// I have to use a mutable collection internally because Java
// does not have immutable collections that return modified copies
// of themselves the way the Clojure and Scala collections do.
List<Passenger> newPassengers = new ArrayList(passengers.size());
for (Passenger p : passengers) {
newPassengers.add(c.apply(p));
}
return Bus.of(driver, Collections.unmodifiableList(passengers));
}
마지막으로, 익명 함수 객체는 수정 된 사물 상태 (새로운 승객이있는 새로운 버스)를 반환합니다. 이것은 p.debit ()가 원래보다 돈이 적은 새로운 불변 승객을 반환한다고 가정합니다.
Bus b = b.mapPassengers(new Function<Passenger,Passenger>() {
@Override
public Passenger apply(final Passenger p) {
return p.debit(fare);
}
}
바라건대 이제 명령형 언어를 얼마나 기능적으로 만들고 싶은지 스스로 결정하고 기능적 언어를 사용하여 프로젝트를 재 설계하는 것이 더 나은지 결정할 수 있기를 바랍니다. Scala 또는 Clojure에서 컬렉션 및 기타 API는 기능적 프로그래밍을 쉽게하도록 설계되었습니다. 둘 다 Java interop이 매우 뛰어나므로 언어를 혼합하고 일치시킬 수 있습니다. 실제로 Java 상호 운용성을 위해 Scala는 첫 번째 클래스 함수를 Java 8 기능 인터페이스와 거의 호환되는 익명 클래스로 컴파일합니다. Scala in Depth sect 의 세부 정보를 읽을 수 있습니다 . 1.3.2 .