일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 잃어버린 괄호
- 2021.01.17
- algorithm
- 알고리즘데이
- SWEA
- baekjoon1541
- 2021.01.06
- 2021.01.22
- Til
- 백준 1149
- 괄호
- 코드스쿼드 마스터즈
- 2021.01.14
- 마스터즈 2주차 회고
- 자바
- 2021.01.18
- 박재성
- 백준
- 알고리즘
- 2020.01.08
- 코드스쿼드
- 백준 9093
- 쉽게 배우는 운영체제
- 2021.01.12
- 2021.01.11
- 2021.01.19
- spring-boot
- 2021.01.13
- 2021.01.21
- java
- Today
- Total
Cooper's devlog
[TIL] 2021.01.19 - Closure, Pure Function, HOF 본문
[FP 미션 마무리]
어제 Functional Interfac를 return type으로 전달하려는 시도는 포기했다. 아직 함수형 인터페이스 새내기가 전공 심화를 하는 느낌이었다. 그래서 나의 수준에 맞게 전공 기초에 초첨을 맞춰서 코드를 작성하기로 했다.
우선 메서드로 선언한 형태를 자바 라이브러리에 있는 함수형 인터페이스(ex. Function, Predicate...)를 선언해서 사용하기로 했다.
그래서 각각의 Input type, return type에 맞는 함수를 선언해서 모든 메서드들을 함수형 변수로 변경시켰다.
처음에는 익숙하지 않아서 버벅거렸지만 계속해서 하나, 두개씩 변경해 나가다보니 그렇게 어렵지 않았다. 사람은 적응의 동물인가보다😑
[변경한 코드]
class ClassifierAlpha extends Alpha {
public static final Function<Set<Integer>, Integer> sum
= factors -> factors.stream().reduce(0, Integer::sum);
public static final Function<Integer, Integer> factorsSum = factors.andThen(sum);
public static final Predicate<Integer> isPerfect = x -> factorsSum.apply(x) - x == x;
public static final Predicate<Integer> isAbundant = x -> factorsSum.apply(x) - x > x;
public static final Predicate<Integer> isDeficient = x -> factorsSum.apply(x) - x < x;
}
오후에는 코드리뷰를 진행했다. 이미 오전 데일리 스크럼 시간에 내가 코드를 싹다 뒤집어 엎어 코드를 설명했었다. 그래서 딱히 할말은 없었지만 개인적으로 코드에서 맘에 안드는 부분이 있었다.
[맘에 안들었던 부분]
public static Function<INteger, String> printFunction =
x -> {
StringBuilder sb = new StringBuilder();
sb.append(x).append(" : ");
ClassifierType numberClassifierType = classifierTypeFilter.apply(x);
PrimeTYpe primeType = primeTypeFilter.apply(x);
ClassifierType numberClassifierType = getClassifierType(number);
PrimeType primeType = getPrimeType(number);
if(numberClassifierType == ClassifierType.ABUNDANT) {
sb.append("abundant ");
}
if(numberClassifierType == ClassifierType.DEFICIENT) {
sb.append("deficient ");
}
if(numberClassifierType == ClassifierType.PERFECT) {
sb.append("perfect ");
}
if(primeType == PrimeType.PRIME) {
sb.append("prime ");
}
return sb.toString;
}
위 코드를 보면 각각의 타입에 따라서 조건문(if)를 처리해서 결과를 추가하는 부분이 있다. 이렇게 되면 value를 추가하면 계속해서 조건문(if)를 처리해주어야 하기 때문이다. 조건문의 늪에 빠지고 싶지 않았다. 코드를 간결하게 작성하면서 좀 더 효율적인 코드를 작성하고 싶었다.
나의 해결책은 enum에 해당 value를 검색하는 method를 구현한 것이다. enum 상수의 value로 함수형인터페이스를 변수로 저장하면 해당 기능을 mapping할 수 있다. 그래서 기존에 구현한 Predicate<Integer>형태인 isPerfect, isBoundary, isAbundant를 사용했다.
[ClassifierType에 행위 추가하기]
package CS10.day5;
import java.util.Arrays;
import java.util.function.Predicate;
public enum ClassifierType {
PERFECT("perfect ", ClassifierAlpha.isPerfect),
ABUNDANT("abundant ", ClassifierAlpha.isAbundant),
DEFICIENT("deficient ",ClassifierAlpha.isDeficient);
private final String value;
private final Predicate<Integer> expression;
ClassifierType(String value, Predicate<Integer> expression) {
this.value = value;
this.expression = expression;
}
public String getValue() {
return value;
}
public static ClassifierType findClassifierType(int number) {
return Arrays.stream(values())
.filter(x -> x.expression.test(number))
.findFirst()
.orElseThrow(()-> new IllegalArgumentException("어떤 수도 아닙니다."));
}
}
- 우선 각각의 상수들에 Predicate<Integer>변수들을추가했다. 이를 통해 해당 상수에 도달할 경우 Predicate<Integer>를 선언할 수 있다.
- 그리고 findClassifierType 메서드를 선언했다. 해당 숫자가 가면 상수들의 Predicate를 통해 알맞는 수 형태를 반환한다.
[이전보다 깔끔해진 Main 코드]
public static final Function<Integer, ClassifierType> classifierTypeFilter =
x -> ClassifierType.findClassifierType(x);
public static final Function<Integer, String> printFunction =
x -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(x).append(" : ");
ClassifierType numberClassifierType = classifierTypeFilter.apply(x);
PrimeType primeType = primeTypeFilter.apply(x);
stringBuilder.append(numberClassifierType.getValue());
if (primeType == PrimeType.PRIME) {
stringBuilder.append(primeType.getValue());
}
return stringBuilder.toString();
};
enum 에 수의 형태를 반환하는 코드를 작성했더니 Main의 코드가 훨씬 간결해졌다. 그리고 변경사항에 따라서 계속해서 if문을 처리할 필요가 없어서 너무 좋았다. enum은 진리다.👍👍
enum 공부할 때 이동욱님의 블로그 글이 많이 도움이 되었다. 나중에 개발자로 성장해서 꼭 한번 뵙고 싶다.
[GOOD]
처음에는 lambda와 stream을 사용하는 것이 낯설었다. 그래서 당연히 잘 사용하지 않고 기존 방식만 사용하게 되었다.
이번 미션을 진행하면서 그동안 공부해왔던 lambda와 stream 사용량보다 더 많이 사용했던 것 같다. 신기한건 계속해서 사용하니까 된다. 처음에는 x-> 이 화살표만 보면 "뭐야? 응 스킵" 이랬지만 지금은 덤덤하게 코드를 작성하고 있다. 다시 한번 습관과 연습이 참 무섭다는 것을 다시 한번 깨달았다.
그래도 어느정도 람다, 스트림을 기본적으로 사용할 수 있어 뿌듯하다. 모던 자바 인 액션을 좀더 열심히 읽어보면서 공부해봐야겠다.
[BAD]
오늘 늦잠을 자서 오전에 책을 못 읽었다. 어제 어려운 코드를 작성하려고 밤늦게까지 붙잡고 있었더니 몸이 건방지게 반항한다.
컨디션 조절을 잘 못하는편인데 계속해서 수면 시간과 컨디션 조절을 꾸준히 신경써서 해야 겠다.
[TIL]
- 알고리즘 공부하기
- 오브젝트 책 읽기
- 오늘은 조금 일찍자기
[여기서부터 학습정리]
[Closure]
[1. first class function (function = first class citizen) ]
-
first class citizen의 정의
-
parameter를 function으로 전달할 수 있고
-
return 값으로 다른 function을 리턴할 수 있고
-
variable /data structure에 function을 저장할 수 있다.
-
-
anonymous function : function 자체에 메서드이다. = lambda expression
[2. Closure]
-
Closure란 ?
-
lambda expression을 표현한 함수에서 외부 값(non-local variable = free variable) 을 참조하는 경우
-
-
Closure 불리는 이유
-
함수의 scope(someMethod)가 확장해서 int a까지 덮어버린다(close over) → "Closure"
-
int a = 100;
someMethod ( x -> x * 2 + a); //a가 함수에 참조한다.-> closure
-
effectively final : final을 명시하지 않아도 final인 상태
-
자바 런타임 진행 시, 익명 클래스의 number가 값에 대한 정보(value)를 가지고 있으므로 number의 정보가 변경 금지.
-
그래서 number에 final을 선언하거나 만약 못했을 경우, effectively final로 작용해서 값 변경을 할 수 없다.
-
Anonymous Class의 number는 new Runnable의 scope에 속한다.
-
그런 와중에 number를 참조할 수 있다는 것 = Closure
-
[예제1. effectively final + Closure]
private void test() {
int number = 100;
//effectively final : final이지만 final로 표시 안된 상태
//자바8 이전-> final 선언 필요
//자바8 이후 -> final 선언 필요X
testClosure("Anonymous Class", new Runnable() {
@Override
public void run() {
// number = 20; //error
System.out.println(number); //effectively final로 사용되는 요소
//외부의 number 참조 : closure
}
});
testClosure("Lambda Expression", () -> System.out.println(number));
}
[예제2. scope 체크]
private void test4() {
int number = 100;
testClosure("Anonymous Class", new Runnable() {
@Override
public void run() {
int number = 50; // no compile-time error
System.out.println(number); //anonymous class의 scope는 new Runnable 내부이기 때문
}
});
testClosure("Lambda Expression", () -> {
// int number = 50; // compile-time error : 이미 참조하고 있기 때문 -> class의 인스턴스 변수
System.out.println(number); //-> lambda expression의 scope는 closed class
});
}
[예제3.외부 변수에 접근하고 싶은 경우]
public class ClosureExamples {
private int number = 999;
public static void main(String[] args) {
new ClosureExamples().test3();
}
private void test() {
int number = 100;
testClosure("Anonymous Class", new Runnable() {
@Override
public void run() {
System.out.println(number);
}
});
testClosure("Lambda Expression", () -> System.out.println(number));
}
-
this.number을 사용할 수 없는 이유
-
line 15에 있는 number의 this는 ClosureExample에 소속된 것이 아님
-
new Runnable()의 scope이다.
-
Runnable 내 ClosureExample의 number를 사용하고 싶은 경우
-
ClosureExamples.this.number 선언.
-
-
-
lambda expression에서 this.number는 ClosureExample의 인스턴스 변수이다.
-
이유 : Java는 lexical scope을 따르고 있어 lambda의 scope가 closed class(ClosureExamples)이다.
-
그러므로 lambda expression의 this는 ClosureExamples의 this를 따른다.
-
scope : 참조 대상 식별자(indentifier)을 찾아내기 위한 규칙
-
lexical scope(=static scope) : 함수 선언 시점에따라 상위 스코프가 결정하는 방식.
-
Dynamic scope : 함수의 호출 위치에 따라 상위 스코프가 결정되는 방식.
-
[lexical scope 예제]
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); //?
bar(); //?
-
참고 링크 : poiemaweb.com/js-scope
-
자바스크립트나 자바 모두 lexical scope 방식을 채택하고 있기 때문에 bar method 선언 시점의 상위인 var x를 참조해서 결과는 모두 1를 반환한다.
[순수함수(pure funtion)]
-
부수효과가 없는 함수
-
부수효과(side effect) : 외부 상태를 변경 또는 함수 인자를 직접 변경하는 것
-
-
함수에 동일한 인자를 제공할 경우, 항상 같은 값을 반환하는 함수. (결과를 예측할 수 있다.)
-
외부의 상태를 변경하지 않는 함수
-
사용하는 이유 : 순수 함수형태로 사용하면 모듈화 수준이 높아진다.
-
모듈화 : 독립적으로 재활용될 수 있는 소프트웨어 덩어리
-
[1.순수함수의 예]
public int add(int a, int b) {
return a + b;
}
해당 코드를 보면 입력된 파라미터에 의해서만 연산되어 결과를 출력한다.
-
다른 외부의 상태를 변경하지 않고
-
내부의 함수 인자들의 직접 변경이 없다.
-
그리고 파라미터에 같은 값을 넣어주면 항상 같은 값을 반환한다.
-
그러므로 이는 순수함수(pure function)이다.
[2.순수함수의 아닌 예 : 외부의 상태로 값이 변경된다.]
주로 순수함수가 위배되는 상황이 인스턴스 변수를 사용하는 경우이다.
static int c = 10;
public int add2(int a, int b) {
return a + b + c;
}
이 경우는 순수함수가 아니다.
왜냐하면 c의 변수가 변경됨에 따라서 결과 값이 다르게 출력될 수 있기 때문이다.
이렇다면 함수에 파라미터를 추가해주었음에도 불구하고 원하는 결과값을 얻을 수 있다는 보장이 없다.
[3. 순수함수가 아닌 예 : 외부의 상태를 변경한다.]
위와 같이 외부의 값을 변경하는 코드는 순수 함수라고 정의하지 않는다. 참고하자!
[고차함수(Higher-Older function(HOF))]
두가지 조건 중 하나 이상을 만족하는 함수를 고차함수라 한다.
-
하나의 함수가 다른 함수의 매개변수로 전달한다.
-
함수를 결과로 반환한다.
[ex1. Function를 매개변수로 받는 Function의 형태]
public class HigherOrderFunctionExamples {
public static void main(String[] args) {
final Function<Function<Integer, String>, String> f = g -> g.apply(10);
System.out.println(
f.apply(i -> "#" + i) // "#10"
);
}
}
-
f.apply(i -> "#" + i)
-
g = (i -> "#" + i)
-
g.apply(10) : 매개인자(i)로 10대입해서 결과(#10) 반환
-
[ex2. Function을 return type으로 반환한다.]
public class HigherOrderFunctionExamples {
public static void main(String[] args) {
//i : Integer
//Function<Integer, Integer> : i2 -> i + i2
final Function<Integer, Function<Integer, Integer>> f2 = i -> (i2 -> i + i2);
System.out.println(
f2.apply(1).apply(9) // 10
);
}
}
-
v2.apply(1).apply(9)
-
.apply(1) : 1를 매개인자(i)로 전달
-
.apply(9) : 9를 매개인자(i2)로 전달해서 결과(i + i2)를 반환.
-
[ex3. map함수]
private static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
final List<R> result = new ArrayList<>();
for (final T t : list) {
result.add(mapper.apply(t));
}
return result;
}
-
first-class-function
이 되려면 method 자체가 function처럼 쓰여질 수 있어야 한다.
-
즉, 데이터처럼 매개인자를 받고 결과를 리턴하는 방식으로 구현되어야 한다.
-
Method Reference : 메서드를 Function처럼 사용할 수 있는 기능.
-
이러면서 method와 function의 기준이 애매해졌다.
[ex4. filter는 HOF함수인가요? YES]
-
매개변수를 Function으로 받아서 사용하기 때문
[ex5. 연습]
System.out.println(
"f3.apply(1).apply(2).apply(3) = " + f3.apply(1).apply(2).apply(3)
);
같은 식이다.
[Reference]
- Closure 강의(Kevin TV)
'TIL' 카테고리의 다른 글
[TIL] 2021.01.22 (0) | 2021.01.22 |
---|---|
[TIL] 2021.01.20 - bitmask (0) | 2021.01.21 |
[TIL] 2021.01.18 (0) | 2021.01.18 |
[TIL] 2021.01.15 (0) | 2021.01.17 |
[TIL]2021.01.14 (0) | 2021.01.16 |