책에서는 2차원 레이아웃 라이브러리를 구현하는 과정을 통해 이번 챕터를 설명합니다.

2차원 레이아웃 라이브러리는 여러 문자열 요소를 조합해 2차원 레이아웃으로 문자열을 배치하고 표현하는 라이브러리입니다.

elem은 조합할 요소를 생성하는 팩토리 메소드 입니다. 그래서 요소끼리 above하면 세로로 요소를 결합한 새로운 요소가 생성됩니다.

beside를 수행하면 가로로 결합한 요소가 생성됩니다. 요소를 결합할 때 결합한 요소 간 길이가 맞지 않으면 양 옆에 빈 칸을 붙여서 가운데 정렬을 수행합니다.

 

클래스의 이용 형태는 시간이 지남에 따라 달라진다. 파라미터가 없는 메서드 정의를 통해 Element 클래스를 사용하는 클라리언트가 Element의 내부 구현에 영향을 받지 않는다.

네임스페이스(Namespace)는 개체를 구분할 수 있는 범위

스칼라는 메소드와 필드를 같은 개체로 인식해 추상 메소드를 서브 클래스에서 메소드가 아닌 필드로 변경할 수 있다.

 

ArrayElement에 하나의 문자열만 전달하는 ArrayElement의 서브 클래스 LineElement를 정의합니다.

 

LineElement는 한 줄의 요소를 표현하는 클래스이기 때문에 기존 heightwidth와는 다른 방식을 적용합니다.

그래서 height, width를 재정의하기 위해 widthheight 메소드를 오버라이드 합니다. 오버라이드할override 수식자를 무조건 붙어야 하는데

그 이유는 heightwidthElement 추상 클래스에서 정의한 구체 메소드이기 때문입니다.

Contents는 추상 멤버이기 때문에 ArrayElement에서 override 수식자를 붙이지 않았습니다.

자바는 오버라이드한 메소드에 대해 스칼라의 override 수식자 같이 무조건 적어줘야 하는 메타데이터 같은 게 없습니다.

물론 @Override가 존재하지만 선택사항입니다.

 

주어진 너비와 높이만큼 지정한 문자로 채우는 새로운 Element UniformElement를 정의합니다.

그래서 왼쪽과 같은 관계가 형성되고 다형성에 의해 Element 타입의 변수에는 Element의 서브 클래스 타입 객체 참조가 가능해 집니다.

 

invokeDemo 에서 Element 타입의 demo() 메소드를 호출하는 데 호출 결과 값을 보면 demo 메소드가 변수의 타입인 Element를 따라가지 않고

실행 시점의 객체 타입을 따라가는 것을 볼 수 있습니다.

 

 

상속 관계를 결정할 때는 “is-a” , 서브 클래스는 슈퍼 클래스이다. 라는 구문이 만족해야 합니다.

예를 들어 , 비둘기는 조류다. 독수리는 조류다. 라는 구문이 만족해야 합니다.

저희가 보고 있는 예제에서 현재 상속 관계가 맞는 지 살펴보겠습니다.

UniformElementElement이다.ArrayElementElement 이다. 라는 의미는 맞기 때문에 ElementUniformElement, ArrayElement 관계는 상속 관계입니다.

그러나 LineElementArrayElement 라고 할 수 없습니다. LineElement는 하나의 문자열만 표현하고 ArrayElement는 여러 문자열을 표현합니다. 비유하면 IntArray[Int]와 같습니다.

IntArray[Int]상속관계에 있지 않습니다.

그래서 오른쪽과 같이 관계를 바꿀 수 있습니다.

 

above 메소드는 위 코드와 같이 요소들을 세로로 결합한 새로운 요소를 생성하는 메소드입니다.

세로로 결합한 요소를 구현하는 방식은 contents 배열에 문자열을 추가하여 구현합니다. 그러면 객체를 print 할 때 하나의 문자열 출력하고 엔터를 치는 방식으로 세로로 결합된 요소를 만들게 됩니다.

 

beside를 수행하면 가로로 결합한 요소가 생성됩니다.

명시적으로 배열의 인덱스를 사용하는 방식보다 오류 발생 가능성이 작아집니다.

 

모든 함수는 비 공통 부분과 공통 부분이 존재합니다. 비 공통 부분은 인자로 주어져 호출에 따라 달라집니다.

만약 함수 값을 메소드의 인자로 전달하면 함수 값 내부 알고리즘이 전달되는 것이기에 메소드 내부 알고리즘을 동적으로 변경할 수 있습니다.

그래서 특정 메소드의 일정 부분이 동일하고 나머지 로직이 다르다면 기존에는 다른 로직마다 별도 메소드를 정의해야 하지만 함수 값을 인자로 전달받을 수 있다면 다른 로직만 함수값에 따라 변경하면 되기 때문에

하나의 메소드만 정의하면 됩니다. 따라서 코드 중복을 줄일 수 있습니다.

여기서 함수 값을 인자로 받는 함수를 고차 함수라고 부릅니다.

 

왼쪽의 FileMatcher 싱글톤 객체를 보면 메소드 파라미터, for, yield 까지 3개의 메소드에서 중복을 보입니다.

종복이 생기게 되면 만약 다른 fileMatcher 메소드를 추가한다면 예를 들어 filesEnding이 아닌 filesStarting을 추가한다면 중복해서 메소드를 추가해야 합니다.

그리고 여기서는 폴더의 모든 파일에 대해 query를 매치하고 있는 데 만약 첫 번째 파일만 제외한다고 하면 3개의 메소드 모두 수정을 해야 합니다.

그래서 왼쪽 코드에서 공통적인 부분과 비 공통 부분을 나눠 비 공통 부분을 인자로 전달하게 됩니다. 여기서 비 공통 부분은 if문 내부 조건이 됩니다.

이 조건은 값이 아닌 로직으로 되어 있기 때문에 인자에 함수 값을 전달해서 비 공통 부분을 변경할 수 있습니다.

오른쪽과 같이 filesMatching이라는 고차 함수를 정의합니다. 공통 부분이 들어간 것을 볼 수 있고 두 번째 인자로 메소드를 받습니다.

if 절에서 인자로 받은 matcher을 호출해 비 공통 부분이 인자값에 따라 달라지도록 합니다.

 

 

왼쪽 코드는 filesEnding에서 query 인자를 받고 이 인자를 또 filesMatching에 전달하는 데 굳이 그럴 필요는 없습니다.

클로저를 사용하면 메소드에서 외부 스코프의 변수에 접근 가능하기 때문에 오른쪽과 같이 endsWith의 인자에 query를 바로 넣을 수 있습니다.

 

API에 고차 함수를 포함 시킨 좋은 예는 스칼라 컬렉션 타입의 특별 루프 메소드입니다.

대표적으로 컬렉션의 exists 메소드가 있습니다.

클라이언트에서 인자로 받은 nums에서 음수가 있는 지 확인하기 위해 containNeg 메소드를 정의하고 홀수가 있는 지 확인하기 위해 containsOdd를 정의했습니다.

메소드를 보면 빨간 색 로직 부분을 제외하고 공통 부분인 걸 볼 수 있는 데 스칼라의 컬렉션 타입에서는 왼쪽 코드의 공통 부분과 비공통 부분인 if 조건 함수 값을 인자로 받는 exists 고차함수가 정의되어 있습니다.

그래서 왼쪽 코드를 exists 메소드를 활용해 오른쪽과 같이 단순하게 만들 수 있습니다.

 

커링은 인자 목록이 2개 이상인 함수를 의미합니다.

기존 함수는 왼쪽과 같이 전체 인자가 하나의 괄호 안에 들어가는 데 커링은 오른쪽 함수와 같이 인자를 여러 목록으로 나눌 수 있습니다.

인자를 여러 목록으로 나눴기 때문에 메소드를 호출할 때도 괄호로 나눠서 하게 됩니다.

실제로 curriedSum이 호출되는 과정을 보면 함수를 2번 호출하게 됩니다. 먼저 x가 입력된 함수 값이 반환됩니다. 왼쪽 아래 코드의 first와 같습니다.

그리고 그 함수값에 y를 적용하게 됩니다.

커링은 위치 표시자를 활용해서 부분 적용 함수로 사용할 수 있습니다.

curriedSum(1)_을 하게 되면 1을 제외한 나머지를 받는 부분 적용 함수로 바뀌게 되 부분 적용함수를 사용할 때 요구하는 인자값만 전달하면 됩니다.

 

컴퓨터 과학에서 추상화(abstraction)는 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말합니다.

첫 번째 코드의 경우 두 번 연산을 수행하는 흐름 제어를 추상화한 것인데 위와 같이 하면 여러가지 다른 로직을 가진 메소드들을 twice에 인자로 전달하는 것만으로 두 번 연산을 수행할 수 있도록 할 수 있습니다.

아래 코드는 함수에서 대신 자원을 열고 닫아주는 흐름 제어를 추상화한 것인데 빌려주기 패턴이라고도 합니다.

 

Call by value , 영어 그대로 값을 참조합니다. 즉 함수가 인자로 전달 될 경우, 그 즉시 함수를 평가하는 걸 말합니다

callByName이란? 값이 파라미터로 전달 될 때 평가되지 않고, 실제로 call이 될 때 평가하는 것을 말합니다.

위 예에서 Call by Value를 할 경우 assertionEnabledfalse일 때도 myAssert 괄호 안의 표현을 평가한다. 필요하지 않은 계산을 하는 부수 효과가 생기게 됩니다.

그러나 Call by Name의 경우 assertionEnabledfalse일 때 표현식을 계산하지 않기 때문에 부수 효과가 발생하지 않습니다.

Call by Name 코드를 보면 myAssert 인자로 “()=>5>3” 표현식을 줬는 데 읽기 힘듭니다. 그래서 아래와 같이 “()”를 함수 인자에서 생략하면 가독성 있게 파라미터를 전달할 수 있습니다.

+ Recent posts