아래의 글은 마틴 파울러 지음 김지원 옮김, 『리팩토링 코드 품질을 개선하는 객체지향 사고법』,한빛미디어(2012)의 내용을 기반으로 작성하였습니다.

 

1. Duplicated Code ( 중복 코드 )

: 똑같은 혹은 유사한 코드 구조가 두 군데 있을 때는 그 부분을 하나로 통일하면 프로그램이 개선됨

> 메서드 추출, 메서드 상향, 템플릿 메서드 형성, 알고리즘 전환, 주변 메서드 추출, 모듈 추출

 

2. Long Method  (장황한 메서드 )

: 메서드 안의 로직이 위임 없이 길게 구현된 코드

> 메서드 추출, 메서드 호출로 전환, 메서드 체인으로 전환, 매개변수 세터를 객체로 전환, 객체를 통째로 전달, 조건문 쪼개기, 루프를 컬렉션 클로져 메서드로 전환

 

3. Large Class ( 방대한 클래스 )

: 기능이 지나치게 많은 클래스, 인스턴스 변수가 많고, 중복코드가 있을 확율이 높다

> 클래스 추출, 하위 클래스 추출, 모듈 추출, 클래스 추출, 관측 데이터 복제

 

4. Long Parameter List ( 과다한 매개변수 )

: 매개변수가 많은 경우, 수정도 많다. 

> 매개변수 세트를 매서드로 전환, 객체를 통째로 전달

 

5. Divergent Change  ( 수정의 산발 )

: 새로운 기능 추가에 3개의 메서드를 수정하고 추가 기능은 4개의 메서드를 수정해야하네 > 하나의 클래스를 여러 개의 객체로 분리하는 것이 좋음

> 클래스 추출

 

6. Shotgun Surgery ( 기능의 산재 )

: 수정할 때 마다 여러 클래스에서 수 많은 자잘한 부분을 고처야 한다면 이 문제

> 메서드 이동, 필드 이동, 클래스 내용 직접 삽입

 

7. Feature Envy ( 잘못된 소속 )

: 객체의 핵심은 데이터와 그 데이터에 속하는 프로세스를 한 데 묶는 기술

> 메서드 이동, 메서드 추출, 위임 패턴

 

8. Data Clumps ( 데이터 뭉치 )

: 유사한 데이터는 모이는 경향으로 객체로 만들어야 한다.

> 클래스 추출, 매개변수 세트를 객체로 전환, 객체를 통째로 전달

 

9. Primitive Obsession ( 기본타입에 대한 집착 )

: String은 primitive 타입이 아님, 문자열 날짜도 클래스로 존재, 기본타입 아님, 

> 데이터 값을 객체로 전환, 분류 부호를 클래스로 전환, 조건문에 분류 부호가 사용될 땐, 분류 부호를 하위 클래스로 전환, 분류 부호를 상태/전략 패턴으로 전환, 클래스 추출, 매개변수 세트를 객체로 전환, 배열을 객체로 전환

 

10. Switch Statement ( 스위치 문 )

: switch 문은 중복이 생기기 쉬움.

> 메서드 추출, 메서드 이동, 분류 부호를 하위 클래스로 전환, 분류 부호를 상태/전략 패턴으로 전환, 상속 구조를 만들었다면 조건문을 재정의로 전환

 

11. Parallel Inheritance Ierarchies ( 평행 상속 계층 )

: 기능의 산태의 특수한 상황, 한 클래스의 하위 클래스를 만들 때 마다, 매번 다른 클래스의 하위 클래스도 만들어야 한다.

> 메서드 이동, 필드 이동

 

12. Lazy Class ( 직무유기 클래스 )

: 리팩토링 이후 사용하지 않는 클래스는 삭제

> 계층 병햡, 클래스 내용 직접 삽입, 모듈 내용 직접 삽입

 

13. Speculative Generality ( 막연한 범용 코드 )

: 미리 만들어 놓은 기능, 필요 없는 기능

> 계층 병합, 클래스 내용 직접 삽입, 매개변수가 있으면 매개 변수 제거, 메서드명 변경

 

14. Temporary Field ( 임시 필드 )

: 인스턴스 변수가 특정 상황에서만 할당 되는 경우

> 클래스 추출, 널 검사를 널 객체에 위임

 

15. Message Chain ( 메시지 체인 )

: 연쇄적 요청이 발생하는 문제점

> 대리 객체 은폐, 메서드 추출, 메서드 이동

 

16. Middle Man ( 과잉 중개 메서드 )

: 과도한 위임의 반복

> 과잉 중개 메서드 제거, 메서드 내용 직접 삽입, 위임을 상속으로 전황

 

17. Inappropriate Intimacy ( 지나친 관여 )

: 서로 지나치케 밀접한 두 클래스

> 메서드 이동, 필드 이동, 클래스의 양방향 연결을 단방향으로 전환,  클래스 추출, 대리 객체 은폐, 상속을 위임으로 전환

 

18. Alternative Class with Differenct Interfaces ( 인터페이스가 다른 대용 클래스 )

: 기능은 같은데 시그너쳐가 다른 메서드

> 메서드 명 변경, 메서드 이동, 상위 클래스 추출

 

19. Incomplete Library Class ( 미흡한 라이브러리 클래스 )

: 수정이 어려운 라이브러리 클래스

> 외래 클래스에 메서드 추가, 국소적 상속 확장 클래스 사용

 

20. Data Class ( 데이터 클래스 )

: 필드의 읽기 쓰기 메서드만 들어 있는 클래스 , public 필드가 있으면 안됨

필드 캡슐화, 컬렉션 캡슌화, 쓰기 메서드 제거, 메서드 이동, 메서드 추출, 메서드 은폐

 

21. Refused Bequest ( 방치된 상속물 )

: 상속 받은 메서드나 데이터가 하위 클래스에서 더 이상 필요 없는 경우

> 메서드 하향, 필드 하향

 

22. Comment ( 불필요한 주석 )

: 주석이 잘 못된 코드를 감추는 용도

> 메서드 추출, 메서드 이름 변경, 어셜션 넣기

 

아래의 글은 마틴 파울러 지음 김지원 옮김, 『리팩토링 코드 품질을 개선하는 객체지향 사고법』,한빛미디어(2012)의 내용을 기반으로 작성하였습니다.

 

 

리팩토링은 무엇입니까?

리팩토링은 왜합니까?

비지니스 유저에게 리팩토링이 필요하다고 요청하기 위해서는 어떤 논리가 필요합니까?

 

리팩토링은 무엇인가?

겉으로 드러나는 기능은 그대로 둔 채, 알아보기 쉽고 수정하기 간편하게 소프트웨어 내부를 수정하는 작업

소프트웨어를 더 이해하기 쉽고 수정하기 쉽게 만드는 것

겉으로 드러나는 소프트웨어 기능에 영향을 주지 않는 것

부적절한 위치에 있느 코드를 제거하는 작업

구조가 산만해진 코드를 정리하는 작업

 

 

리팩토링은 왜 해야하나?

소프트웨어 설계가 개선되니까, 

소프트웨어를 이해하기 더 쉬워지니까? : 이것은 좀 이슈가 있다. 패턴과 코드에 대한 이해도가 낮으면 리팩토링된 코드를 이해하기 어려워한다. 반대로 리팩토링을 많이 한 사람에게 반대의 코드가 보기어려워지는 것도 동일한 원리이다. 안보이니까 안 고치는 것이다.

 

코드 설계를 파악하기 힘들어질수록 설계를 고치기도 힘들어 진다.

코드가 길 수록 이해할 분량이 많으니 정확한 수정이 어렵다

코드 중복이 존재해서 여러번 수정하는 불편함을 없애려고

상황만 달리 해서 거의 같은 기능을 하는 코드가 여러 군데 있으면 한 군데 코드를 수정해도 다른 부분의 코드까지 함께 수정해야함

 

기능 추가와 리팩토링은 상호 배타적 작업

 

 

 

 

 

 

 

 

아래의 글은 이호진 지음, 『쉽게 배워 바로 써먹는 디자인 패턴』,한빛미디어(2020)의 내용을 기반으로 작성하였습니다.

 

 

상태 패턴

 

상태 패턴과 전략 패턴은 구조가 유사하지만 목적성으로 두 패턴을 구별 할 수 있습니다.

 

전략 패턴은 객체의 상태값에 관심이 없으며, 알고리즘을 교체하고 동작을 변경시키는 것만 생각합니다.

그러나 상태 패턴은 동작하는 객체의 변경이 상태에 따라 달라집니다.

상태 패턴에서는 상태 값이 매우 중요하며 다음 동작과 객체의 위임을 결정합니다.

 

상태 패턴은 행동 패턴으로 분류되고, 객체 내부 상태에 따른 동작 객체를 결정합니다.

상태별로 분리된 동작 객체는 독립적이며, 각 상태 값에 따라 국지화된 객체 행위를 위임합니다.

 

 

아래의 글은 마틴 파울러 지음 김지원 옮김, 『리팩토링 코드 품질을 개선하는 객체지향 사고법』,한빛미디어(2012)의 내용을 기반으로 작성하였습니다.

 

백문이 불여 일견이라는 말로 통일된다. 코드 예제가 우리에는 백마디 설명보다 효과가 크다.

 

 public class Movie {
 	public static final int CHILDREN = 2;
    public static final int REGULAR = 0;
    public static final int NEW_RELEASE = 1;
    
    private String title;
    private int priceCode;
    
    public Movie(String title
 }
아래의 글은 마틴 파울러 지음 김지원 옮김, 『리팩토링 코드 품질을 개선하는 객체지향 사고법』,한빛미디어(2012)의 내용을 기반으로 작성하였습니다.

 

몇 일 전에도 10년차 이상된 중급 개발자 리드 S님이 오시더니 자기 코드를 리뷰를 해달란다. 리뷰는 팀 내에서 해야지 내가 지금 코드 놓은지도 3개월이고 나의 신념상 같이 코드 개발을 하지 않으면서 코드에 감놔라 배놔라 하는 꼴은 나도 싫어서 뭘 할 수 있겠나 하고 일단은 들어보기나 하자고 해서 시작이 되었다. 

 

메소드를 Dto가지고 넣어다 뺐다 한다. 그러니 넣을 때 마다 값이 있는지 없는지를 체크해야하고, if 문으로 값이 있음 없음을 분기하여 아래에서 다시 if 문, 그곳에서 다른 서비스의 메소드를 호출하면 그 안에서 다시 if 문 ...

Dto에서 필요 없는 값을 지우는데 참조하는 곳에 많고, 지우다 보니 또 애매하게 판단을 해서 반쯤 사용하는 곳도 있고, 그래서 하나 지우는 데 벌써 몇개의 메소드를 왔다 갔다 하며, 수정하고, 심지어 메소드의 파라메터에 default 값을 주도록 변경하니 참조하는 곳 변경해야하고, 유사한 메소드를 여러 개가 있더라.

 

이미 하나 둘 알려주기에는 너무 멀어진 상황이라, 생각나는게 마틴 파울러의 리팩토링 책이여서, 이걸 팀에게 다시 알리는 게 나을 것 같아 이렇게 시작해 본다.

 

리팩토링은 겉으로 드러나는 코드의 기능은 바꾸지 않으면서 내부 구조를 개선하는 방식으로 소프트웨어 시스템을 수정하는 과정이다.

 

waterfall 방식이라고 하는 SI에서의 개발방식은 요구사항을 fix하고, 이에 맞게 설계를 하고, 명세서 (spec 일수도 있고, BRD, FRD로도 표현하고, wireframe등을 이용한 UI 설계서 등)가 나오면 그때 backend는 DB설계 API설계를하고, frontend 는 zeplin으로 된 UI를 가지고 UI를 구현한 뒤, API를 붙이는 작업의 흐름으로 간다. 

 

하지만, 어디 요구사항이 바뀌지 않는 프로젝트가 있던가.. 수정과 개발자의 의견과 빠진 요구사항과 edge case가 융합이 되면서 요구사항을 위한 요구사항이 추가 되고, 빠듯한 일정이라는 달콤한 압박이 밀려오면 일단 구현우선주의 메타포가 형성이 되면서 간단하게 수정하는 유혹의 과정을 거치면서 코드는 이해 할 수 없는 형태와 파편화를 가속화하게 된다. 

 

어쩌면 이제 나에게는 당연한 과정이 이를 경험해보지 못한 개발자에게는 잔소리와 알지도 못하면서 하는 충고로 변경이 되면서, 일단 돌아가면 된다는 무논리로 프로젝트는 오픈하게 된다.

 

나 또한 리팩토링을 알지 못했다면 이 과정이 당연하다 여겼겠지만, 20년전에 마틴 파울러는 이를 알고 책으로 만들었다. 처음에는 그냥 조금 더 나은 방법론이라 생각했지만, 보면 볼수록 곱씹어 볼 내용이 가득한지라, 한때는 이런 책을 쓸 수 있는 그 들의 세계와 저력이 부러울 따름었다. 하지만, 그 나라의 코드도 봤더니, 그 나라에 있는 사람도 모두 리팩토링을 잘 하는 것은 아니더라. 그러니 또 너무 기가 죽지는 말자. 그들에게도 우리와 같은 시절이 70년대 80년 대를 거치면서, 고난의 시간을 가졌고, 그 들 또한 답답함에 agile manifesto를 만들어서 우리도 바꾸지라고 했으니 그건 시간이 필요한 영역인 듯 하다.

 

리팩토링은 심미적인 요소가 반드시 존재하니, 개발자 스스로 아름다움을 추구하지 않는다면 리팩토링은 다들 시간 낭비 혹은 과한 요소라고 생각하시는 분들도 있다. 좀 더 현실적으로 말하자면, 다들 리팩토링이 중요하다고 하는데 리팩토링 관련된 책은 뭐가 있는지도 모른다. 그러니 말로만 리팩토링이요, 코드를 더 간결하고 직관적이고 아름답게 만드는 노력의 과정은 정말 필요하다는 당위성을 깨닫지 못한다. 리팩토링은 오랜 동안 실전에서 깨닫고 변경해가면서 느끼는 과정이 되면, 그 이후는 당연이 그 끝에 나와야 하는 코드의 모양이 어떻게 되는 코드의 구조를 잘 잡게 된다. 즉 디자인 패턴과도 연관이 되어 있다. 그리고 리팩토링은 하나의 팁이나 짧은 식견으로도 가르쳐 줄 수 없는 거라, 단순한 부분 교육으로도 쉽게 바뀌어 지지 않는다. 그리고 아직 나 또한 부족한 부분이 많은 터라, 알듯하면서도 어렵기도 한게 사실이다.

 

즉, 리팩토링은 오랜기간에 거친 훈련이며, 코드 뿐만 아니라 내부에도 돌아가는 매커니즘에 적합하게 코드를 변경하는 부분이라, 부단히 노력하고, 본인 스스로 적용해보면서 하나 하나 깨닫는 수련의 과정이다.

 

그러니 어렵고도 끝이 없는 과정이며, 정답도 없는 과정이다. 그리고 미적요소와 철학적 요소는 개인차가 반드시 존재하는 영역이라, 개인의 사상이 반영되는 영역이다. 하지만, 피겨선수 김연아의 플레이가 그랬고 피아니스트 조성진의 연주가 그랬든이, 어느 경지에 오른 작품은 아무리 모르는 사람이 보더라도 그 탁월함은 쉽게 판명이 난다.

 

그러니, 부단히 노력하여, 마틴 파울러처럼 이런 책은 못 만들더라고, 그 사람이 가르치는 내용은 따라가서 흉내라도 내보도록 하자.

그리고 가르치면서 나도 또 다시 배운다. 

 

 

 

 

 

아래의 글은 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 저/우정은 역, 『모던 자바 인 액션』,한빛미디어(2019), CH01의 내용을 기반으로 작성하였습니다.

Synchronize & Asynchronize

Sync : blocking call

Async : non-blocking call

Non-blocking : return first, then call back with results when call completed.

 

Java 8 쉬운 병렬처리

Stream.parallel

CompletableFuture

 

Stream Parallel vs CompletableFuture

Stream.parallel

I/O 가 포함되지 않는 계산 중심의 동작을 실행할 때는 스트림 인터페이스가 가장 구현하기 쉬우며 효율적

CompletableFuture

작업이 I/O를 기다리는 작업을 병렬로 실행할 때는 CompletableFuture가 더 많은 유연성을 제공하며 대기/계산의 비율에 적합한 스레드 수를 설정할 수 있다. 특히 스트림의 게으른 특성 때문에 스트림에서 I/O를 언제 처리할지 예측이 어려움

I/O : 파일 RW, DB작업,

 

kwonnam.pe.kr/wiki/java/8/completable_future

 

java:8:completable_future [권남]

 

kwonnam.pe.kr

www.baeldung.com/java-completablefuture

 

Guide To CompletableFuture | Baeldung

Quick and practical guide to Java 8's CompletableFuture.

www.baeldung.com

 

아래의 글은 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 저/우정은 역, 『모던 자바 인 액션』,한빛미디어(2019), CH01의 내용을 기반으로 작성하였습니다.

optional

Optional > way to express null value in dto

Optional returns stream

Optional.isPresent if else can be replaced with map with orElse

Optional in Optional solved by flatmap

 

Java time date

Express local data time with calendar

 

참조사이트 :

www.baeldung.com/java-optional

 

Guide To Java 8 Optional | Baeldung

Quick and practical guide to Optional in Java 8

www.baeldung.com

 

www.baeldung.com/java-8-date-time-intro

 

Introduction to the Java 8 Date/Time API | Baeldung

In this article we will take a look at the new Java 8 APIs for Date and Time and how much easier it is to construct and manipulate dates and times.

www.baeldung.com

 

아래의 글은 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 저/우정은 역, 『모던 자바 인 액션』,한빛미디어(2019), CH01의 내용을 기반으로 작성하였습니다.

Fork Join

Divide and conquer

Thread pool

 

Fork

Divide

Sub Branch

Stream Parallel 주의

내부 로직이 병렬성이 될지를 고민, 자료구조, 연산방식

타입 박싱 주의

Limit, findFirst 보다 findAny

소량은 fork join 비용이 더 높음

 

Parallel 성능 – N 1_000_000

Parallel 성능 – N 5 _000_000

Design pattern

Strategy 패턴

Interface execute

Interface validate

Class StringValidate

Class IntegerValidate

 

template 메소드 패턴

observer 메소드 패턴

registerObserver n times

Notify method call once, loop notify n times

Factory chain 패턴

Add successtor

Execute handlerequest both self

and successor

Then execute like chained

팩토리 method 패턴

List wishList = New ArrayList<>();

list.add(“Watch”);

List.add(“Phone”);

Arrays.asList(“Watch”, “Phone”)

Java 9

List.of(“Watch”, “Phone”);

참조사이트 :

blog.naver.com/PostView.nhn?blogId=2feelus&logNo=220732310413

 

Fork Join Framework - ForkJoinPool

ForkJoinPool 정확히는 Fork Join Framework 이라고 불러야 하고, ForkJoinPool은 그것의 대표 클...

blog.naver.com

 

okky.kr/article/345720

 

OKKY | [Java] 쓰레드풀 과 ForkJoinPool

메모리풀/쓰레드풀/캐쉬풀/DB풀  Pool 4총사 중 먼저  쓰레드 풀에 대해서 쉽게 그림으로 배워보아요. 오류나 추가사항이 있으면 댓글로 남겨주시면 소정의  포옹으로 보답을.... 쿨럭 자 시작합�

okky.kr

 

아래의 글은 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 저/우정은 역, 『모던 자바 인 액션』,한빛미디어(2019), CH01의 내용을 기반으로 작성하였습니다.

Collectors

Collection, collect와 다름

 

참고사이트 :

dzone.com/articles/java-8-streams-api-laziness

 

Java 8 Streams API: Laziness and Performance Optimization - DZone Java

In this post, we will move ahead with the Java 8 Streams API and have a look at the most important property of Java 8 Streams — laziness.

dzone.com

 

아래의 글은 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 저/우정은 역, 『모던 자바 인 액션』,한빛미디어(2019), CH01의 내용을 기반으로 작성하였습니다.

람다

표현식

(파라메터리스트) ->(화살표) { 바디) }

(String s) -> s.length()

(Apple a) -> a.getWeight() > 150

(int x, int y) -> { System.out.println(“sum=”+(x+y))

() -> 42

(String s1, String s2) -> s1.equals(s2)

 

함수형 인터페이스

추상메소드가 하나면 함수형 인터페이스

@FuntionalInterface

하나인거 보장

객체 생성후 override 없이 람다사용가능

 

함수 descriptor

함수형 인터페이스의 추상메서드의 시그너쳐

람다 표현식을 사용하기 위한 시그너처

표준화 -> 인터페이스

함수형 인터페이스의 시그너쳐의 표준화

타입은 모두 Object만 가능 : primitive type boxing

Predicate<T> – input T, return Boolean : boolean test(T t)

Consumer<T> – input T, return void : void accept(T t)

Function<T, R> - input T, return R : R apply(T t)

 

제공되는 함수형 인터페이스

함수형 인터페이스 함수 디시크립터 예제
Predicate<T> T -> boolean (List<String> list) -> list.isEmpty()
Consumer<T> T -> void (Apple a) -> System.out.println(a.getWeight)
Function<T> T -> R () -> new Apple(10)
Supplier<T> () -> T  
UnaryOperator<T> T -> T  
BiOperator<T> (T, T) -> T  
BiPredicate<L, R> (L, R) -> boolean  
BiConsumer<T, U> (T, U) -> void  
BiFunction<T, U, R> (T, U) -> R (Apple a1, Apple a2) ->
a.getWeight().compareTo(a2.getWeight())

 

메서드레퍼런스, 생성자 레퍼런스

(String s) -> Integer.parseInt(s)

Integer::parseInt

New Apple

Apple::new

 

형식추론

List<Apple> filteredAppleList = filter(appleList, (Apple a) -> a.getWeight() > 10);

List<Apple> filteredAppleList 2 = filter(appleList, a -> a.getWeight() > 10);

스트림, Stream

입출력 : stream

비디오 : 스트리밍

Stream processing : SIMD , 그래픽 병렬처리

SIMD : Single Instruction Multiple Data

리눅스 pipe

Java API : java.util.stream, java.util.function

 

Java 8 : Stream

 

스트림 특징

Collection 데이터 처리의 추상화

스트림 소스 : collection, array, io

Stream pipeline (스트림 연산은 스트림 자신을 반환)

Lazy 연산

Stream요소추가 및 요소 제거 불가

Steam은 한번만 탐색가능

 

참고사이트 :

dzone.com/articles/java-8-streams-api-laziness

 

Java 8 Streams API: Laziness and Performance Optimization - DZone Java

In this post, we will move ahead with the Java 8 Streams API and have a look at the most important property of Java 8 Streams — laziness.

dzone.com

 

아래의 글은 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 저/우정은 역, 『모던 자바 인 액션』,한빛미디어(2019), CH01, CH02의 내용을 기반으로 작성하였습니다.

 

Java release and roadmap

 

functionality

version feature
1, 2 Thread, lock, GC
5 Thread pool, noi, concurrency, annotation, generic, varargs
7 Diamond op, try resource, Fork/join
8 Default method, method reference,  lambda expression, stream api, completable future, new date api

 

언어의 진화방향

Concurrency -> parallel (stream, map/reduce)

Immutable (pure, side-effect-free, stateless)

Reduce boiler plate code

함수형 프로그래밍

일급함수 메서드와 클래스 자체가 값 값으로 메서드와 클래스 전달

메서드와 람다를 일급시민으로 격상 메서드 레퍼런스, 람다

stream

 

메소드레퍼런드

File::isHidden

 

람다

t -> convert(t)

t -> {return new NewObject(t.getId());}

(File f) -> f.getName().startWith(“prefix”);

 

참조사이트 :

https://dzone.com/articles/a-guide-to-java-versions-and-features

 

Guide to Java Versions and Features - DZone Java

In this guide. we will look at the differences between Java distributions and an overview of Java language features, including Java versions 8-13.

dzone.com

 

아래의 글은 조슈아 블로크 저/개앞맵시 역, 『이펙티브 자바 Effective Java 3/E
』, 인사이트(2018), 7-13 쪽의 내용을 기반으로 작성하였습니다.

 

toString이 뭐지?

Object.toString : 클래스_이름@16진수로표시한해시코드

 

 

항상 해야하나?

항상 할필요는 없고 필요한 객체 (dto, entity 등) 에 적용하면 디버깅이 편하다

 

재정의는 어떻게?

lombok @ToString으로하면 편하고 아니면 재정의

 

참고사이트 : 

johngrib.github.io/wiki/Object-toString/

 

java.lang.Object.toString 메소드

 

johngrib.github.io

 

아래의 글은 조슈아 블로크 저/개앞맵시 역, 『이펙티브 자바 Effective Java 3/E
』, 인사이트(2018), 7-13 쪽의 내용을 기반으로 작성하였습니다.

 

equals가 뭐지?

 

잘 모르면 무턱대고 정의하지는 마라, Object클래스의 equals를 그대로 사용하라

왜냐하면 

정의하지 않고 그대로 사용하면 ? 현재 구현체는 동일한 객체는 자기 자신만 같게 됨.

 

동일/동등 이라고도 하고

식별성 논리적 동치성이라도 하고

identity와 equity 이 차이 ( 인터뷰 기초 문제이지도 하지요 ㅎㅎ)

identity : 물리적으로 같은 객체인가?

equals : 같은 값인가?

 

일반 규약은 뭐지? > equals 메서드를 재정의 할때는 일반 규칙을 따라야함.

반사성 : reflexivity : x.equals(x) = true

대칭성 : symmetry : x.equals(y) == true 이면 y.equals(x) == true이다

추이성 : transitivity : x.equals(y) == true 이고 y.equals(z) == true 이면 x.equals(z) == true 이다

일관성 : consistency : x.equals(y) 반복호출시 항상 일관된 값

null아님 : x.equals(null) 은 false 이다

 

왜 지켜야하지? > 그러니 잘못 만들면 side-effect가 발생함. 즉, equals를 많은 곳에서 사용하고 있음.

사용하는 곳, collection class, 객체 비교, serialization, 

 

참고 사이트 :

dzone.com/articles/identity-vs-equality-in-java

 

Identity Vs. Equality in Java - DZone Java

In this post, we take a quick look at identity and equality in Java, what they are, and how they compare to one another.

dzone.com

www.nextree.co.kr/p11101/

 

헷갈리기 쉬운 “동등비교” 와 “정렬”

입사하여 처음으로 참여하게 된 프로젝트는 보험회사 직원들의 업무 관리 프로그램을 구축하는 것이었습니다. 자바로 개발하는 프로젝트는 처음이여서 어떻게 구현해야하는지에 대한 걱정이 �

www.nextree.co.kr

d2.naver.com/helloworld/831311

www.gisdeveloper.co.kr/?p=5332

 

Java에서 HashMap을 위한 Custom Key 정의하기 – GIS Developer

Java의 HashMap 자료구조는 고유한 값인 Key와 1:1로 연관되는 값(Value)을 하나의 쌍(Pair)로 하여 여러 개의 쌍을 저장하는 자료구조입니다. 흔히 Key 값으로 문자열이나 정수값을 사용하는 것으로도 충

www.gisdeveloper.co.kr

vandbt.tistory.com/41

 

리스코프 치환 원칙 - Liskov Substitution Principle for Primer

리스코프 치환 원칙 LSP Liskov Substitution Principle 은 객체-디자인의 S.O.L.I.D 5원칙 중의 가장 이해하기 어려운 원칙이라고 할 수 있습니다. 다른 원칙들은 이름에서 힌트를 얻을 수 있는데 LSP만은 이�

vandbt.tistory.com

 

아래의 글은 조슈아 블로크 저/개앞맵시 역, 『이펙티브 자바 Effective Java 3/E
』, 인사이트(2018), 7-13 쪽의 내용을 기반으로 작성하였습니다.

 

자바의 기본적인 객체 생성은 생성자를 통한 생성임.

생성을 제어하기 위한 다양한 방법으로 singleton 패턴과 , enum 방식의 singleton 방식 등이 있음

생성의 편의와 생성시 파라미터를 주기위한 제어 목적으로 static factory method와 빌터 패턴 등이 있음

 

static factory method 패턴은 다음과 같은 장점이 있다.

 

1. 생성에 특정한 의미를 주어 가독성을 높인다.

Foo f = new Foo(); // 생성자 방식

Foo f = new Foo(20); // 20이 뭐지?

Foo f = new Foo(20, 10); // 20은 뭐고 10은 뭐지?

Foo f = new Foo(10, 20); // 순서가 다르면 다른 의미 // 다른 시그너쳐 못반들어냄 (int, int)는 1개여야함

Foo f2 = Foo.createFoo(); // 팩토리 메소드 방식

Foo f2 = Foo.createFoo().money(20); // 가독성 좋음

Foo f2 = Foo.createFoo().money(20).age(10); // 가독성 훨씬 좋고 유연함

Foo f2 = Foo.createFoo().age(10).money(20); // 순서가 의미가 없음.

주의 : 편하다고 막 남용하지는 말고 파라메터를 많이 set 해야해야되는 경우와 생성 비용이 높은 경우에 활용하면 좋다

 

2. 인스턴스 생성 제어가 가능

 

생성을 메서드해서 하므로 생성제어를 매서드 내에서 할 수 있다.

 

3. 반환 타입의 하위 타입 객체를 반환할 수 있느 능력

 

4. 입력 매개변수에 따라 매번 다른 클래스이ㅡ 객체를 반환

 

5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 됨

 

static factory method 패턴은 다음과 같은 단점이 있다.

 

 

1. 상속 형태는 안됨

 

2. 어떤 메소드가 정적 메소드인지 알 수 없음 > 그래서 naming convention이 생겨남

 

 

정적 팩터리 메서드에 사용하는 naming conventions

방식 설명  
from 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드 Date d = Data.from(instance)  
of      
valueOf   String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');
 
instance or getInstance      
create or newInstance      
get<Type>, new<Type>, type      

 

팩토리 메소드 패턴과는 다르다고 봐야한다. ( 팩토리 메소드 패턴 : johngrib.github.io/wiki/factory-method-pattern/)

팩토리 메소드 패턴 객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내리도록 함

 

 참고사이트 : 

https://johngrib.github.io/wiki/static-factory-method-pattern

 

정적 팩토리 메서드(static factory method)

static 메서드로 객체 생성을 캡슐화한다

johngrib.github.io

johngrib.github.io/wiki/factory-method-pattern/

 

팩토리 메소드 패턴 (Factory Method Pattern)

객체를 생성하기 위한 인터페이스를 정의하고, 인스턴스 생성은 서브클래스가 결정하게 한다

johngrib.github.io

medium.com/webeveloper/%EC%8B%B1%EA%B8%80%ED%84%B4-%ED%8C%A8%ED%84%B4-singleton-pattern-db75ed29c36

 

싱글턴 패턴(Singleton Pattern)

자바와 스프링의 싱글턴 패턴(Singleton Pattern)과 차이점

medium.com

www.baeldung.com/java-constructors-vs-static-factory-methods

 

Java Constructors vs Static Factory Methods | Baeldung

Learn about static factory methods in Java and why they're sometimes preferred over constructors for instantiating and initializing objects.

www.baeldung.com

 

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

단파서가 필요함. 자신의 파서를 처음부터 개발하는 것

여러 파서 생성기중 하나를 사용하는 것

(파서 생성기로는Yacc, Bison, ALTLR)

(스캐너 생성기, Lex, Flex, JFlex)

내부 도메인 특화 언어

정규문법 (regular grammar), 문맥자유문법 (CFG)

 

1. 산술식

expr ::= term {"+" term | "-" term}
term ::= factor {"*" factor | "/' term"}
factor ::= floatingNumber | "(" expr ""




import scala.util.parsing.combinator._








class Arith extends JavaTokenParsers {
    def expr : Parcer[Any] = terms~rep("+"~term | "~".term)
    def term : Parcel[Any] = factor~rep("*"~factor | "/"~factor)
    def factor : Parser[Any] = floatingPointElement | "("~expr~")"
}
산술한 파서는 한번에 요청은 follback으로 남은
  1. 모든 생성 규칙은 각각 별도의 메소드가 된다.
  2. 각 메소드의 결과 타입은 Parser[Any] 이다.
  3. 문법에서는 순차 합성을 따로 표기하지 않지만, 프로그램에서는 ~로 명시적으로 넣어야 한다.
  4. 반복은 { ... } 대신에 rep ( ... ) 로 표현한다.
  5. 각 생성 규칙에 있는 마침표는 생략한다.

2. 파서실행

object ParseExpr extends Arith {
    def main(args: Array[String]) = {
        println("input = " + args(0))
        println(parseAll(expr, args(0)))
    }
}

 

3. 기본 정규표현식 파서

object MyParsers extends RegexParsers {
    val ident: Parser[String] = """[a-zA-Z_]\w*""".r
}

 

4. JSON 파서

value ::= obj | arr | stringLiteral | floatingPointNumber | "null" | "true" | false
obj ::= "{" [ members ] "}"
arr ::= "[" [ values ] "]"
members ::= member {"," member]
member := stringLiteral ":" value
value ::= value {"," value}




import scala.util.parsing.combinator._




class JSON extends JavaTokenParsers {
    def value : Parser[Any] = obj | arr | stringLiteral | floatingPointNumber | "null" | "true" | "false"
    def obj : Parser[Any] = "{"~repsep(member, ",")~"}"
    def arr : Parser[Any] = "["~repsep(value, ",")~"]"
    def member : Parser[Any] = stringLiteral~":"~value
}








import scala.util.parsing.combinator._




class JSON1 extends JavaTokenParsers {
    def obj: Parser[Map[String, Any]] = "{"~> repsep(member, ",") <~"}" ^^ (Map() ++ _)
    def arr: Parser[List[Any]] = "["~> repsep(value, ",") <~ "]"
    def member: Parser[(String, Any)] = stringLiteral~":"~value ^^ {case name~":"~value => (name, value)}
    def value : Parser[Any] = (
        obj
    | arr
    | stringLiteral
    | floatingPointNumber ^^ (_.toDouble)
    | "null" ^^ (x => null)
    | "true" ^^ (x => true)
    | "false" ^^ (x => false)
    )
}

5. 파서의 결과

문자열 파서는 문자열 반환

정규표현식 파서도 문자열 자체 반환

순차 합성 factor ::= P~Q는 P와 Q의 결과를 모두 반환

대안 합성 P | Q 는 어느쪽이든 성공한 결과

반복 합성 req(P) repsep(P, separator) 는 P의 모든 실행에서 나온 결과를 리스트로 반환

선택적 합성 opt(P)는 스칼라 Option 타입의 인스턴스 반환

^^ 연산자는 파서 결과를 반환 P ^^ f

~  연산자의 결과는 ~  라는 이름의 케이스 클래스 인스턴스를 반환함.

"{" ~ ms ~ "}" 은 ~ ( ~ ( "{", ms), "}") 와 동일

"{" > repsep (member, ",") <~ "}" ^^ (Map() ++ _)

 

 

6. 콤비네이터 파서 규현

scala.util.parsing.combinator.Parsers 트레이트

파서타입 type Parser[T] = input => ParseResult[T]

 

파서입력

입력 : 가공되지 않은 문자 시퀀스 대신 토큰 스트림, 

type input = Reader[Elem]

Reader 클래스는 scala.util.parsing.input 패키지에 있음.


파서의 결과

sealed abstract class ParseResult[T]
case class Success[T](result: T, int: Input)
    extends ParseResult[T]
case class Failure[T](msg: String, in: Input)
    extends ParseResult[Nothing]

 

Parser 클래스

 p =>

val id = this

id.m this.m

단일 토큰 파서

elem

순차합성 ~

elem 파서는 원소를 단지 하나만 읽는다. 

대안합성 |

재귀다루기

결과반환 p ^^ f

어떤 입력도 읽지 않는 파서 : success, failure

선택적 합성과 반복합성

 

7. 문자열 리터럴과 정규표현식

문자 시퀀스만 입력으로 가능

 

8. 어휘분석과 파싱

문법분석 → 어휘 분석 하고 몇가지 종류의 토큰으로 분류

토큰의 순서를 분석하는 분법분석이 있음

scala.util.parsing.combinator.lexical

scala.util.parsing.combinator.syntactical

 

9. 오류보고

스칼라 파싱 라이브러리는 단순한 휴리스틱을 구현, 모든 실패 중에서 가장 최근의 입력 위치에서 발생한 실패를 하나 선택

 

10. 백트래킹과 LL(1)

백트래킹을 사용해 여러 대안 사이에 파서를 선택함.

백트래킹을 사용해 문법을 파싱하려면 문법을 만들대 몇가지 제한이 필요함.

왼쪽 재귀 생성 규칙을 피해야 함.

expr ::= expr "+" term | term

expr이 즉시 자기 자신을 호출하기 때문에 호출만 게속 늘어나고 어이상 진전이 없다.

expr := term "+" expr | term

(1+2) * 3 첫번째 대안 실행, + 매칭할때 실패

그리고 나서 두번째 파싱 대안 시도, 성공, 결국 term 두번 실행

expr ":= term ["+" expr]

expr ::= term {"+" term} 

많은 언어가 소위 LL(1) 이라는 문법 따름

콤비네이터 파서를 그 문법으로부터 만든다면 결코 백트래킹은 하지 않을 것

파싱 프레임워크에서 ~! 라는 연산자를 사용하면, 문법이 LL(1)이라는 사실을 명시

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

스칼라 표준 라이브러리는 Future를 사용해 변경 불가능한 상태를 비동기 적으로 변환하는 것에 집중하는 것으로 동시성 해결

자바의 퓨처에서는 블로킹 방식의 get을 사용해 결과를 얻어와야 한다. 

자바에서는 get을 호출하기 전에 isDone을 호출해, Future가 완료했는지를 검사할 수 있고, 

그에 따라 블로킨을 방지할 수 있지만, 계산 결과를 사용해 다른 계산을 수행하려면, Future가 완료되기를 기다려야만 한다.

 

스칼라의 Future는 계산결과의 완료여부와 관계없이 결과 값의 변환을 지정할 수 있다. 

계산을 수행하는 스레드는 암시적으로 제공되는 실행 컨텍스트를 사용해 결정된다.

 

1. 낙원의 골치거리

자바에서는 각 객체와 연관된 논리적인 모니터를 통해 데이터에 대한 여러 쓰레드의 접근을 제어한다.

이 모델을 사용하려면, 어떤 데이터를 공유힐지 결정하고, 이 데이터에 접근하거나 제어하는 코드 주면을 synchronized 문으로 감싸야 한다.

자바 런타임은 락 메커니즘을 통해 동기화 된 부분을 같은 락으로 보호해서, 한 번에 한 스레드만 들어갈 수 있게 보장 해준다.

 

2. 비동기 실행과 Try

비동기적으로 실행하기 위한 전략을 제공하는 암시적인 실행 컨텍스트가 필요

JVM에서 전역 실행 컨텍스트는 스레드 풀을 사용

import scala.concurrent.ExecutionContext.Implicits.global

Option[scala.util.Try[Int]] = Some(Success(42))

예외난 경우

Option[scala.util.Try[Int]] = Some(Failure(java.lang.ArithmeticException: / by zero))

 

3. Future의 사용

스칼라의 Future에서는 Future 결과에 대한 변환을 지정해서 새로운 퓨처를 얻을 수 있다. 

새로 만들어지는 퓨처는 원래의 비동기 계산과 그 결과에 대한 변환, 이렇게 두 가지 비동기 계산을 조합한 것을 표현

Future 만들기 : Future.failed Future.successful Future.fromTry, Promise : 3 가지의 팩토리 메소드 제공

successful 팩토리 메소드는 이미 성공한 퓨처를 만든다

failed 메소드는 이미 실패한 퓨처를 만든다

fromTry 메소드는 try로 부터 이미 완료한 퓨처를 만든다

퓨처를 만드는 가장 일반적인 방법은 Primise

Primise[Int] 만든후 ,succes, failure, complete 이름의 메소드를 사용해 프라미스 완료

 

걸러내기 :  filter, collect

스칼라 퓨처는 filter 와 collect 제공, filter 메소드는 퓨처의 결과를 검증, 

val fut = Future { 42 }

val valid = fut.filter( res => res > 0 )

collect 메소드 사용하면 퓨처 값을 검증하고, 변환하는 작업을 한 연산에서 수행 가능

val valid = fut collect ( case res if res > 0 => res + 46 )

실패 처리하기 : failded, fallBackTo, recover, recoverWith

failed 는 퓨처가 실패하리라 예상하는 경우에 더 적합하다, 

recover 메소드를 사용하면 실패한 퓨처를 성공적인 것으로 변환 할 수 있다. 

recoverWith는 recover는 문제가 생기면 값으로 복구하는 데 반해, recoverWith는 퓨처 값으로 복구함.

 

두가지 가능성 모두를 매핑 : transform

하나는 성공의 경우를 변환, 다른 하나는 실패인 경우를 변환

 

퓨처 조합하기 : zip, Future.fold, Future.reduce, Future.sequcne, Future,traverse

zip은 2개의 성공적인 퓨처를 두 값으리 튜플을 제공하는 퓨처로 만들어 준다.

reduce는 초깃값 없이 폴드를 진행함

sequence는 퓨처로 이루어진 traversableOnce 컬렉션을 TraversableOnce 값이 담긴 츄러로 변환

Future.traverse 메소드는 아무 타입의 원소로 이뤄진 TraversableOnce를 츄러의 TraversableOnce로 변환하고, 값의 TraversableOnce로 완료하는 퓨처로 그것을 스퀀스화 해준다.

 

부수효과 수행하기 : foreach, onComplete, andThen

foreach는 성공적으로 완료된 경우 부수 효과를 실행

onComplete는 콜백함수 등록

andThen 콜백함수가 수행되는 순서 강제 지정

 

스칼라 2.12에 추가 : flatten, zipWith, transformWith

flatten : Futher 안에 Futher가 내포된 타입의 Futher로 변환함. 

zipWith는 기본적으로 두 Future를 함께 묵어써, 결과 튜플에 map을 수행

tranformWith는 Try를 받아서 Future를 돌려주는 함수를 사용해 퓨처 반환

 

4. Future 테스트

Future의 장점은 블로킹을 피할 수 있도록 가능하게 함.

Await은 Future의 결과를 기다리기 위해 블로킹 하는 과정을 편하게 함.

Await.result(fut, 15.seconds)

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. 스칼라에서의 동일성

스칼라와 자바는 동일성의 정의가 다름,  == 연산자는 값 타입에 대해서는 자연스러운 등호, 참조 타입의 경우에는 동일한 객체인지, equals 메소드는 참조 타입에 대한 표준 동일성 검사를 제공하는 사용자 정의 메소드

스칼라에사는 값 타입은 == 는 자바와 동일, 객체의 경우 == 는 자바의 equals와 동일 (동일 겍채는 x eq y), 새로운 타입에 대해 equals를 재정의하면 == 의 의미를 재정의 가능

오버라이드 하지 않은 경우는 equals는 자바의 ==와 마찬가지로 객체 동일성 사용

 

2.  동일성 비교 메소드 작성

equals 구현시 일관성이 없는 동작을 야기할 수 있는 네가지 일반적인 함정

  1. equals 선언 시 잘못된 시그니처를 사용하는 경우 : equals(other: Any) : Boolean
  2. equals 를 변경하면서 hashCode는 그대로 놔둔 경우 : 
    1. 만약 equals 메소드로 따졌을 때 두 객체가 같다면, hashCode를 각 객체에 호출한 결과도 같은 정수 값을 만들어내야만 한다
  3. equals 를 변경가능한 필드의 값을 기준으로 정의한 경우
  4. equals 를 동치 관계로 정의하지 않은 경우
    1. 반사성 : reflexive : 널이 아닌 값 x에 대해 x.equals(x) 는 true를 반환해야한다.
    2. 대칭성 : symmetric : 널이 아닌 값 x, y 에 대해 x.equals(y)가 true이면, y.equals(x) 도 true
    3. 추이성 : transitive : 널이 아닌 값 x, y, z 에 대해서 x.equals(y)가 true이고 y.equals(z) 도 true 이면 x.equals(z) 도 true를 반환
    4. 일관성 : consistent : 널이 아닌 값 x, y에 대해 x.equals(y)를 여러 번 호출해도, x, y 객체에 있는 정보가 변경되지 않는 이상 계속 true, false 중 한 값을 일관되게 반환

canEqual

LSP 위배, 에 대한 고민

 

3. 파라미터화한 타입의 동일성 정의

스칼라는 타입 소거 모델을 채택했고, 타입 파라미터는 실행 시점에 존재하지 않는다. 

와일드 카드 타입의 축약표현 : 알려지지 않은 부분이 안에 있는 타입 : Branch[_] : 패턴 매치와 메소드 호출의 타입 파라미터에 있는 밑줄은 서로 다르지만 의미는 같다, 즉 밑줄은 알려지지 않은 것을 표시

class Branch[T] {

    val elem: T,

    val left: Tree[T],

    val right: Tree[T]

} extends Tree[T] {

    override def equals(other: Any) = other match {

        case that: Branch[_] => (that canEqual this) &&

                this.elem == that.elem &&

                this.left == that.left &&

                this.right == that.right

        case _ => false

    }

    def canEqual(other: Any) = other.isInstanceOf[Branch[_]]

    override def hashCode: Int = (elem, left, right).##

}

4. equals와 hashCode 요리법

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

모둘화 할수 있는 방법

 

1. 문제

시스템을 이루는 각기 다른 모듈을 따로 따로 컴파일 할 수 있으면 여러 팀이 독립적으로 작업할 때 도움이 된다.

어떤 모둘의 구현체를 다른 것으로 바꿀 수 있다

인터페이스와 구현을 잘 분리할 수 있는 모듈 구조를 제공해야함.

어떤 모듈과 인터페이스를 동일한 다른 모둘로 변경하더라도, 그 모듈 인터페이스에 의존하는 다른 모듈들은 재컴파일 하지 않아야 한다.

모듈을 서로 연결할 방법을 제공해야 한다.

 

의존관계주입 dependency injection

객체를 모듈로 사용해 외부 프레임워크를 사용하지 않고도 대규모 프로그래밍의 모듈화를 원하는 대로 달성할 수 있는지 살펴볼 것임.

 

2. Recipe 애플리케이션

스칼라가 규모 확장성 있는 언어로 있기 위한 방법 중 하나는 작든 크든 동일한 구조를 사용

스칼라는 객체를 모듈로 사용한다. 프로그램을 모듈화 하는 시작저믄 테스트 하는 동안 목에 대한 구현으로 사용될 두 싱글톤 클래스

 

3. 추상화

정의를 객체가 아닌 클래스로 만든다. 

 

4. 모듈을 트레이트로 분리하기

트레이트를 사용해 모듈을 여러 파일로 분리

trait FoodCategories

abstract class Database extends FoodCategories

셀프 타입 : 클래스 안에서 사용하는 모든 List의 타입이 어떤 것인지 가정하는 것

object SimpleDatabase extends Database with SimpleFoods with SimpleRecipies

 

5. 실행 시점 링킹

실행 시점의 필요에 따라 어떤 모듈을 링크할지 결정 할 수 있다.

 

6. 모듈 인스턴스 추적

싱글톤 타입 : val database : db.type = db

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. 애노테이션이 왜 필요한가?

메타 프로그래밍 도구, 스칼라는 메타 프로그래밍을 사용할 수 있도록 핵심언어와 직교적인 최소한의 지원만 제공, 애노테이션 시스템, 

컴파일러는 개별 애노테이션에 어떤 의미도 부여하지 않는다.

 

2. 애노테이션 문법

@deprecated def bigMistake()  = ...

 

 

val, var, class, object, trait, type 등 모든 종류의 선언이나 정의에 사용 가능

표현식에 에노테이션 적용

(e: @unchecked) match {

}

 

 

@annot(exp, exp2, ...)

 

 

annot은 애노테이션 클래스를 지정,

스코프에 보이는 다른 변수 참조 가능

 

 

@cool val normal = "Hello"

@coolerThan(normal) val fonzy = "Heeyyy"

즉 @ new로 바꾸면 올바른 인스턴스 생성 표현식이 된다.

애노테이션이 직접 인자로 애노테이션을 쓸수는 없다.

 

3. 표준 애노테이션

@deprecated // 사용금지 경고

@volotile // 컴파일러에게 해당 변수를 여러 스레드가 사용함

@serializable // 이진 직렬화 @SerialVersionUID(1234) @transient // 직렬화 하면 안되는 필드

@get set

@tailrec // 꼬리 재귀 함수

@unchecked

@native

 

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. 전자우편 주소 추출

def isEMail(s: String) : Boolean

def domain(s: String) : String

def user(s: String) : String

 

2. 익스트랙터

object EMail {

    // 인젝션 injection 메소드

    def apply(user: String, domain: String) = user + "@" + domain

    def unapply(str: String): Option[(String, String)] = {

        val parts = str split "@"

        if (parts.length == 2) Some(parts(0), parts(0)) else None

    }

}

 

 

object EMail expends ((String, String) => String) {...}

 

 

 

 

unapply 메소드를 EMail을 익스트랙터로 바꿔준다.

 

 

val x: Any = ...

 

 

x match { case EMail(user, domain) => ... }

인젝션 : EMail 안의 apply 메소드는 인젝션, 이 메소드는 인자를 몇가지 받아서 어떤 집합의 원소를 만들어냄

익스트랙션 : unapply 메소드는 어떤 집합에 속한 원소에서 여러 부분의 값을 뽑아내기 때문

케이스 클래스와 패턴 매치 사이의 관계를 시뮬레이션 한다.

익스트랙션만 있을 수는 있다. 

하지만 상대성 원칙 (dual)에 맞춰 만들면, 좋은 설계

 

3. 변수가 없거나 1개만 있는 패턴

object Twice {

    def apply(s: String): String = s + s

    def unapply(s: String): Option[String] = {

        val length = s.length / 2

        val half = s.substring(0, length)

        if (half == s.substring(length)) Some(half) else None

    }

}

 

 

object UpperCase {

    def unapply(s: String): Boolean = s.toUpperScase == s

}

 

 

def userTwiceUpper(s: String) = s match {

    case EMail(Twice( x @ UpperCase()), domain) =>

        "dup match:" + x + " in domain " + domain

    case _ =>

        "no match"

}

 

4. 가변 인자 익스트랙터

dom match {

    case Domain("org, "acm") => print("acm.org")

    case Domain("com", "sum", "java") => print("java.sum.com")

    case Domain("net", _*) => print("a   .net domain")

}

 

 

object Domain {

    def apply(parts: String*): String =

        parts.reverse.mkString("."

    def unapplySeq(whole: String): Option[Seq[String]] =

        Some(whole.split("\\.").reverse)

}

 

 

def isTomInDotCom(s: String): Boolean = s match {

    case EMail("tom", Domain("com", _*)) => true

    case _ => false

}

기존 1개 변수 매치 성고시, 항상 고정된 숫자의 하위 원소만 반환, 

스칼라에서는 가변 길이를 처리할 때, 다른 익스트랙션 메소드를 정의해 사용 , unapplySeq

 

5. 익스트랙서와 시퀀스 패턴

package scala

 

 

object List {

    def apply[T](elems: T*) = elems.toList

    def unapplySeq[T](x: List[T]): Option[Seq[T]] = Some(x)

    ...

}

6. 익스트랙터와 케이스 클래스

표현독립성 : representation independence :  익스트랙터를 사용하면 패턴과 그 패턴이 선택하는 객체의 내부 데이터 표현 사이에 아무런 관계가 없도록 만들 수 있음.

표현독립성은 케이스 클래스에 비교해 익스트랙터가 지닌 중요한 장점

케이스 클래스 : 설정하고 정의하기가 훨씬 쉽고, 코드도 적게 필요, 익스트랙터 보다 패턴 매치가 효과적으로 가능, 스칼라 컴파일러가 케이스 클래스의 매펀 매치를 익스트랙터의 패턴 매치보다 더 잘 최적화 함.

어떤 케이스 클래스가 sealed된 케이스 클래스를 상속하는 경우, 패턴 매치가 모든 가능한 패턴을 다 다루는지 스칼라 컴파일러가 검사해서 그렇지 않은 경우는 경고를 해 준다.

 

7. 정규표현식

스칼라는 자바에서 정규표현식을 가져왔다, 자바는 대부분을 펄에서 따왔다

스칼라의 정규 표현식 클래스는 import scala.util.matching.Regex

스칼라 raw 문자열 : val decimal = new Regex( " " " ( - ) ? (\d)(\.\d*)? " " ") or decimal = " " " ( - ) ? (\d)(\.\d*)? " " ".r

스칼라의 모든 정규표현식은 익스트랙터를 정의함.

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

새 컬렉션 프레임워크으 주 설계 목표는 모든 연산을 가능한 한 적은 위치에 정의해서 중복을 피하는 것

설계시 택한 접근 방법은 대부분의 컬렉션 연산을 컬렉션 템플릿에 정의해서 개별 기반 클래스나 구현을 필요에 따라 유연하게 상속할 수 있게 제공하는 것

 

1. 빌더

대부분의 연산이 빌더와 순회 (build, traversal)을 가지고 구현됨.

package scala.collection.generic

 

 

class Builder[-Elem, +To] {

    def +=(elem: Elem): this.type

    def result(): To

    def clear()

    def mapResult[NewTo] (f: To => NewTo): Builder[Elem, NewTo] = ...

}

2. 공통 연산 한데 묶기

자연스러운 타입을 만들면서 동시에 구현 코드는 최대한 공유

스칼라 컬렉션은 동일 결과 타입 원칙, 예를 들어 filter 연산은 모든 컬렉션 타입에 대해 같은 컬렉션 타입을 반환

구현 트레이트 (implementation trait) 라 불리는 제네릭 빌더와 순회를 사용해 코드 중복을 줄이고, 동일 결과 타입을 달성

트레이트에는 Like라는 접미사가 붙는다. Traversable의 구현 트레이트는 TraverableLike

일반 컬렉션에는 하나의 타입 파라미터가 있는 반면, 구현 트레이트에는 2개가 있음.

trait TraversableLike[+Elem, +Repr] { ... } Elem은 순회 트레이트 원소 타입이며, Repr은 순회 타입 자체

package scala.collection

 

 

trait TraversableLike[+Elem, +Repr] {

    def newBuilder: Builder[Elem, Repr]

    def foreach[U[(f: Elem => U)

        ...

    def filter(p: Elem => Boolean) : Repr = {

        val b = newBuilder

        foreach { elem => if (p(elem)) b += elem }

        b.result

 

 

    }

 

 

 

// TraversableLike의 map 구현

def map[B, That] (f:Elem => B)

    (implicit bf: CanBuildForm[Repr, That, This]): That = {

    val b = bf(this)

    for (x <- this) b += f(x)

    b.result

}

 

 

// CanBuildForm 트레이트

package scala.collection.generic

 

 

trait CanBuildForm(-From, -Elem, +To] {

    def apply(from: From): Builder[Elem, To]

]

 

 

CanBuildForm[Set[_], A, Set[A]]

// 와일드카드 타입 Set[_] : 타입에 관계없니 Set을 만들 수 있다.

 

3. 새 컬렉션 통합

 

시퀀스 통합

abstract class Base

case object A extends Base

case object T extends Base

case object G extends Base

case object U extends Base

 

 

object base {

    val fromInt: Int => Base = Array(A, T, G, U)

    val toInt: Base => Int = Map(A -> 0, T -> 1, G -> 2, U -> 3)

}

 

 

import collection.IndexedSeqLike

import collection.mutable.{Builder, ArrayBuffer}

import collection.generic.CanBuildFrom

 

 

final class RNA1 private (val groups: Array[Int], val length: Int) extends IndexedSeq[Base] {

    import RNA1.*

 

 

    def apply(idx: Int): Base = {

        if (idx <0 || length <= idx)

            throw new IndexOutOfBoundsException

        Base.fromInt(groups(idx / N) >> (idx % N * ) & M)

    }

 

 

}

 

 

object RNA1 {

    private val S = 2

    private val N = 32 / S

    private val M = (1 << S) - 1

 

 

    def fromSeq(buf: Seq[Base]) : RNA1 = {

        val groups = new Array[Int]((buf.length + N - 1) / N)

        for (i <- 0 until buf.length)

            groups(i / N) != buf.toInt(buf(i)) << (i % N * S)

        new RNA1(groups, buf.length)

    }

 

 

    def apply(bases: Base*) = fromSeq(base)

}

 

 

// RNA1 생성자 private : 직접 생성할 수 없음.

// RNA1 시퀀스를 압축한 배열로 표현한다는 것을 감춤

// 인터페이스와 구현을 분리할 수 있음.

 

 

 

 

val xs = List(A, G, T, A)

RNA1.fromSeq(xs)

 

 

val rna1 = RNA1(A, U, G, G, T)

 

 

rna1.length

rna1.last

rna1.take(3)

IndexSeq[Base] = Vector(A, U, G)

 

 

정적 타입 결과는 IndexSeq[Base]이고 동적타입 결과는 Vector

이를 변경할려면 override

def take(count:Int) : RNA1 = RNA1.fromSeq(super.take(count))

이렇게 하면 take를 대신할 수 있음. 하지만 drop, filter, init은?

일관성을 위해서 seq를 반환하는 메소드 50개가 있는데 전부 변경해야함....

 

 

final class RNA2 private ( val groups: Array[Int], val length: Int) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA2] {

    import RNA2._

    override def newBuilder: Builder[Base, RNA2] = new ArrayBuffer[Base] mapResult fromSeq

 

 

    def apply(idx: Int): Base = ... // 예전과 동일...

}

 

 

val rna2 = RNA2(A, U, G, G, T)

 

 

rna2 take 3

RNA2 = RNA2(A, U, G)

 

 

rna2 filter (U !=)

RNA2 = RNA2(A, G, G, T)

 

val rna = RNA(A, U, G, G, T)

 

 

ran map { case A => T case b => b]

RNA = RNA(T, U, G, G, T)

 

 

rna ++ rna

 

 

RNA(A, U, G, G, T, A, U, G, G, T)

 

 

 

 

rna map Base.toInt

 

 

IndexedSeq[Int] = Vector(0, 3, 2, 2, 1)

 

 

rna ++ List("missing, "data)

 

 

Vector(A, U, G, G, T, missing data)

 

 

 

def map[B, That](f: Elem => b) (implicit cbf: CanBuildFrom[Repr, B, That]): That

 

 

Elem은 컬렉션의 원소 타입이며, Repr은 컬렉션 자체의 타입, TraversableLike나 IndexedSeqLike 같은 구현 클래스의 두 번째 타입 인자로 들어가야하는 타입

B는 매핑 함수의 결과 타입인 동시에 결과 컬렉션의 원소 타입이다.

 

 

 

 

final class RNA private (val groups: Array[Int], val length: Int) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA] {

    import RNA._

 

 

    // 'IndexedSeq' 에 있는 'newBuilder'를 재구현하는 것은 필수다

    override protected[this] def newBuilder: Builder[Base, RNA] = RNA.newBuilder

 

 

    // 'indexSeq'의 apply를 재구현하는 것은 필수다

    def apply(idx: Int): Base = {

        throw new IndexOutOfBoundsException

    Base.fromInt(group(idx / N) >> (idx % N * S) & M)

    }

 

 

    override def foreach[U](f: Base => U) : Unit = {

        var i = 0

        var b = 0

        while (i < length) {

            b = if (i % N == 0) groups(i / N) else b >>> S

            f(Base.fromInt(b & ))

            i += 1

        }

    }

}

 

 

object RNA {

    private val S = 2

    private val M = (1 << S) - 1

    private val N = 32 / S

     

    def fromSeq(buf: Seq[Base]: RNA = {

        val groups = new Array[Int]((buf.length + N + 1) / N)

        for (i < 0 until buf.length)

            groups(i / N) |= Base.toInt(buf(i) << (i % N * S)

        new RNA(groups, buf.length)

    }

 

 

    def apply(bases: Base*) = fromSeq(bases)

 

 

    def newBuilder: Builder[Base, RNA] = new ArrayBuffer mapResult fromSeq

 

 

    implicit def canBuildFrom: CanBuildFrom[RNA, Base, RNA] =

        new CanBuildFrom[RNA, Base, RNA] {

            def apply(): Builder[Base, RNA] = new Builder

            def apply(from: RNA): Builder[Base, RNA] = newBuilder

        }

}

 

 

foreach는 다른 곳에서도 많이 사용하기 때문에 최적화 구현을 필요로 한다.

 

새로운 집합과 맵의 통합

문자열이 키인 패트리샤 트라이 patricia trie

영숫자 부화화 정보를 가져오는 실욕적인 알고리즘 practical algorithm to retrieve information coded in alphanumeric

집합이나 맵을 트리로 구성하되, 검색 키의 각 문자가 유일한 자식 트리를 구성하게 만드는 것

 

 

아주 효율적인 검색과 변경을 제공함.

어떤 접두사를 포함하는 하위 컬렉션을 쉽게 선택할 수 있다는 특성이 있음.

 

val m = PrefixMap("abc" -> 0, "abd" -> 1, "al" -> 2, "all" -> 3, "xy" -> 4)

PrefixMap[Int] = Map((abc, 0), (abd, 1), (al, 2), (all, 3), (xy, 4))

 

 

m withPrefix "a"

Map((bc, 0), (bd, 1), (l, 2), (ll, 3))

 

 

import collection._

 

 

class PrefixMap[T] extends mutable.Map[String, T] with mutable.MapLike[String, T, PrefixMap[T]] {

    var suffixes: immutable.Map[Char, PrefixMap[T]] = Map.empty

    var value : Option[T] = None

 

 

    def get(s: String): Option[T] =

        if (s.isEmpty) value

        else suffixes get (s(0)) flatMap (_.get(s substring 1))

 

 

    def withPrefix(s: String): PrefixMap[T] =

        if (s.isEmpty) this

        else {

            val leading = s(0)

            suffixes get leading match {

                case None =>

                    suffixes = suffixes + (leading -> empty)

                case _ =>

            }

            suffixes(leading) withPrefix (s substring 1)

        }

 

 

    override def update(s: String, elem: T) =

        withPrefix(s).value = Some(elem)

    override def remove(s: String): Optio[T] =

        if (s.isEmpty) { val prev = value; value = None; prev }

        else suffixes get (s(0) flapMap (_.remove(s substring 1)))

 

 

    def iterator: Iterator[(String, T)] =

        (for (v <- value.iterator) yield ("", v) ++

            (for ((chr, m) <- suffixes.iterator;

                (s, v) <- m.iterator) yield (chr +: s, v))

    def += (kv: (String, T)): this.type = (update(kv._1, kv._2); this }

    def -= (s: String): this.thype = { remove(s); this }

    override def empty = new PrefixMap[T]

}

 

 

// 5개 이하이면 변경 불가능한 map : suffixes

// newBuilder가 없다. 이유는 맵이나 집합에는 MapBuilder 클래스의 인스턴스인 디폴트 빌더가 따라오기 때문

 

 

import scala.collection.mutable.{Builder, MapBuilder}

import scala.collection.generic.CanBuildFrom

 

 

object PrefixMap {

    def empty[T] = new PrefixMap[T]

 

 

    def apply[T](kvs: (String, T)*): PrefixMap[T] = {

        val m: PrefixMap[T] = empty

        for (kv <- kvs) m += kv

        m

    }

     

    def newBuilder[T]: Builder[(String, T), PrefixMap[T]] =

        new MapBuilder[String, T, PrefixMap[T]](empty)

 

 

    implicit def canBuildForm[T]

        : CanBuildFrom[Prefix[_], (String, T), PrefixMap[T]] =

            new CanBuildFrom[PrefixMap[_], (String, T), PrefixMap[T]] {

            def apply(from: PrefixMap[_]) = newBuilder[T]

            def apply() = newBuilder[T]

        }

}

 

정리

  1. 컬렉션을 변경 가능학 할지 여부를 결정해야 한다.
  2. 기반 트레이트를 제대로 선택해야 한다
  3. 대부분의 컬렉션 연산을 구현하기 위해 구현 트레이트를 제대로 선택해야 한다
  4. map이나 그와 비슷한 연산을 사용해 컬렉션 타입의 인스턴스를 반환해야 한다면, 암시적인 CanBuildForm을 동반 객체에 제공해야 한다.
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

컬렉션 api 사용

  1. 사용하기 쉬움 : 20-50개 메소드 조합
  2. 간결함
  3. 안전함
  4. 빠름
  5. 보편적임

 

1. 변경가능, 변경 불가능 컬렉션

scala.collection

.mutable, immutable, generic

 

scala.collection

scala.collection.immutable

scala.collection.mutable

 

스칼라는 항상 변경 불가능한 컬렉션을 선택한다.

 

2. 컬렉션 일관성

Travelsable

    Iterable

        Seq

            IndexedSeq

                Vector

                ResizableArray

                GenericArray

            LinearSeq

                MutableList

                List

                Stream

            Buffer

                ListBuffer

                ArrayBuffer

        Set

            SortedSet

                TreeSet

            HashSet (mutable)

            LinkedHashSet

            HashSet (immutable)

            BitSet

            EmptySet, Set1, Set2, Set3, Set4

        Map

            SortedMap

                TreeMap

            HashMap (mutable)

            LinkedHashMap (mutable)

            HashMap (immutable)

            EmptyMap, Map1, Map2, Map3, Map4

 

 

Buffer 트레이트는 mutable만 존재

 

3. Traversable 트레이트

def foreach[U] (f: Elem => U)

컬렉션의 모든 원소를 순회하면서 주어진 연산 f를 각 원소에게 적용하는 것을 목적으로 한다.

메소드는 다음 범주 중에 하나에 속한다.

  1. 추가 메소드 : ++는 두 순회 가능 객체를 하나로 엮거나, 어떤 순회 가능 객체의 뒤에 이터레이터의 모든 원소를 추가함
  2. 맵 연산 : map, flapMap, collect는 어떤 함수를 컬렉션에 있는 원소에 적용해 새로운 컬렉션을 만들어 냄
  3. 변환 연산 : toIndexedSeq, toIterable, toStream, toArray, toList, toSeq, toSet, toMap은 Traversable 컬렉션을 더 구체적인 컬렉션으로 변환
  4. 복사 연산 : copyToBuffer, copyToArray는 컬렉션의 원소를 버퍼나 배열에 각각 복사
  5. 크기 연산 : isEmpty, nonEmpty, size, hasDefiniteSize
  6. 원소를 가져오는 연산 : head, last, headOption, lastOption, find
    1. 어떤 컬렉션이 매번 원소를 같은 순서로 반환하는 경우 순서가 있는 컬렉션이라고 명
  7. 하위 컬렉션을 가져오는 연산 : takeWhile, tail, init, slice, take, drop, filter, dropWhile, fiterNot, withFilter : 인덱스 범위나 술어에 따라 컬렉션의 일부 반환
  8. 분할 연산 : splitAt, span, partition, groupBy 는 컬렉션을 여려 하위 컬렉션으로 나눈다
  9. 원소 테스트 메소드 : exists, forall, count
  10. 폴드 연산 : foldLeft, foldRight, /:, :, reduceLeft, reduceRight 이상 연산을 연속된 원소에 반복 적용
  11. 특정 몰드 메소드 : sum, product, min, max 는 특정 타입 (수 이거나 변경 가능함 타입)에 컬렉션에 적용
  12. 문자열 연산 : mkString, addString, stringPrefix
  13. 뷰 연산 : view는 필요에 따라 계산이 나중에 이뤄지는 연산

 

4. Iterable 트레이트

def foreach[U] (f: Elem => U): Unit = {

val it = iterator

while (it.hasNext) f(it.next))

}

에터레이터를 반환하는 메소드 : grouped, sliding

메소드

추상 메소드 : xs.iterator

이터페이터

xs grouped size

xs sidling size

xs takeRight n

xs dropRight n

xs zip ys

xs zipAll (ys, x, y)

xs.zipWithIndex

xs sameElements ys

 

Traversal이 있는 이유 중 하나는 iterator를 구현하는 것보다 foreach 메소드의 구현이 효율적일 때가 있다.

iterable 하위 븐류인 Seq, Set, Map 3가지 트레이트

 

5. 시퀀스 트레이트 : Seq, IndexedSeq, LinearSeq

Seq 트레이트는 시퀀스 표현

시퀀스에서 사용할 수 있는 연산

  1. 인덱스와 길이 연산 : apply, isDefinedAt, length, indices, lengthCompare
    1. Seq[T]는 타입의 시퀀스는 inx 인자를 받아서 돌려주는 부분함수
    2. Seq[T] PartialFunction [Int, T] 를 확장
    3. seq 0부터 길이 -1 까ㅣㅈ의 인덱스가 붙는다.
  2. 인덱스 찾기 연산 : indexOf, lastIndexOf, indexOfSlice, lastIndexOfSlice, indexWhere, lastIndexWhere, seqmentLength, prefixLength
  3. 추가 연산 : +:, :+, padTo : 시퀀스 맨앞에나 맨 위에 원소 추가
  4. 변경 연산 : updated, patch는 원래 시퀀스를 일부 원소를 바꿔서 나옴
  5. 정렬 연산 : sorte, sortWith, sorBy
  6. 반전 연산 : reverse, reverseIterator, reverseMap
  7. 비교 연산 : startsWith, endsWith, contains, corresponds, containsSlice
  8. 중복 집합 연산 : intersect, diff, union, distinct

Seq 트레이느는 LinearSeq, indexedSeq 두가지 하위 트레이트가 있음.

선형 시퀀스는 더 효율적인  head, tail 연산

인덱스 시퀀스는 효율적인 apply, length, update

List, Stream은 선형 시퀀스

Array, ArrayBuffer는 인덱스드 시퀀스

Vector 클래스는 선형 시퀀스와 인덱스 시퀀스 중간

 

버퍼

변경 가능한 시퀀스의 한 범주, 

원소 추가 : + =, + + + 

맨 앞 원소 추가 : + = : , + + = :

ListBuffer, ArrayBuffer있음. 

 

6. 집합 Set

Set은 원소 중복을 허용하지 않는 Iterable 이다.

  1. 검사 : contains, apply, subsetOf
  2. 추가 연산 : +, ++
  3. 제거 연산 : -, - -
  4. 집합 연산 : 합집합, 교집합, 차집합 union intersect diff

 

7. 맵

맵은 key와 value의 쌍 (이를 mapping, 연관 association )

  1. 검색 연산 : apply, get, getOrElse, contains, isDefinedAt
  2. 추가와 변경 연산 : + , + + , updated
  3. 제거 연산 ; - , - -
  4. 하위 컬렉션 생성 메소드 :keys, keySet, keysIterable, valuesIteravble, values
  5. 변환 연산 : filterKeys, mapValues

이전 결과를 저정하두면 cache 구현도 가능

 

8. 변경 불가능한 구체적인 컬렉션 클래스

리스트

리스트 : 유한한 변경 불가능한 시퀀스, 리스트 첫원소, 나머지 부분 접근에는 상수시간, 그 외 연산은 선형 시간

 

스트림

스트림 : 리스트와 비슷하지만 원소를 지연 계산함, 스트림은 무한할 수 있음

리스트는 :: 연산자로 구성, 스트림은 #:: 를 사용해 구성

var str = 1 #:: 2 #:: 3 #:: Stream.empty

// head는 1 tail은 2, 3

 

 

def fibFrom(a: Int, b: Int): Stream[Int] =

    a #:: fibFrom(b, a + b)

 

 

val fibs = fibFrom(1, 1).take(7)

fibs.toList()


벡터

벡터는 헤드가 아닌 원소도 효율적으로 접근할 수 있는 컬렉션 타입임. 벡터는 넓고 얇은 트리로 표현

벡터는 변경 불가능

벡터를 변경하려면, deep copy 필요하지만, 전체 노드 카피가 아닌 해당 업데이트 하는 트리만 카피

노드당 32개 원소 32 * 32 = 1024

2개 브랜치 2 15

3개 브랜치 2 20

4개 브랜치 2 25

 

변경 불가능한 스택

LIFO 시퀀스 Stack, push, pop, peek 상수시간 소요

 

변경 불가능한 큐

FIFO

 

범위 

range란 간격이 일정한 정수를 순서대로 나열한 시퀀스, 1,2,3 도 범위, 5, 8, 11, 14도 범위

범위를 만들때, to 와 by 메소드 사용

1 to 3

5 to 14 by 3  ( 5, 8, 11, 14)

 

해시 트라이

변경 불가능한 집합이나 맵을 효율적으로 구현하는 표준적인 방법

트라이의 표현은 벡터와 비슷하게 32개의 원소나 32개의 하위 트리를 포함하는 노드를 사용하는 트리

선택시 해쉬코드 사용

어떤 키를 찾는 작업은 키의 해시코드의 최하위 5비트를 루트의 하위 트리를 선택하기 위해 사용

그 다믐 5비트는 그 다음 단계의 트리 선택

스칼라의 맵이나 집합에서는 기본 구현으로 트라이를 사용

원소가 1개에서 4개 사이인 맵이나 집합은 원소를 필드로 저장하는 단일 객체로 생성

비어 있는 변경 불가 집합이나 맵은 싱글톤 - 메모리 중복 사용 방지

 

적흑 트리 red black tree

균형 이진 트리, treeset, treemap은 적흑트리

 

비트 집합

비트 집합은 작은 정수의 컬렉션을 그 보다 더 큰 정수의 비트로 표현함.

내부적으로 비트 집합은 64비트 Long 배열 사용

배열의 첫번째 Long은 0에서 63까지의 정수 두번째 Long은 64부터 127까지 표현

집합에 원소를 추가하는 데는 비트 집합의 배열에 있는 Long의 개수에 선형으로 비례하는 시간이 걸린다

 

 

리스트 맵

리스트 맵은 key value 쌍의 연결 리스트로 맵을 표현

리스트 맵에 대한 연산은 선형으로 크기에 비례

 

9. 변경 가능한 구체적인 컬렉션 클래스

 

배열버퍼

배열 버퍼는 배열과 크기를 저장

배열 버퍼에 원소 추가하는데 상수 분할 상환 시간 amortized time

분할 상황 시간 분석 - 배열 할당은 자주 일어자니 않으므로 배열 할당에 소요되는 시간 비용이 크더라도, 평균적인 비용은 상수시간에 근사함

버퍼 구성한 다음 array 변환 toArray

 

리스트 버퍼

배열 버퍼와 비슷하지만 내부에 연결 리스트 사용

버퍼 구성한 다음 리스트로 변환 toList

 

문자열 버퍼

문자열 만들대 유용 toString

 

연결 리스트

next로 연결됨, 빈 시퀀스도 nullPointerException은 반환안함.

빈 연결 리스트는 next가 자기 자신을 가리킴

 

이중 연결 리스트

양향향 next와 prev 제공

 

변경 가능한 리스트

단일 연결 리스트에 맨마지막 노드를 가리키는 포인터 존재, 리스트 뒤에 추가가 상수시간에 됨, 

MutableList에서 mutable.LinearSeq 표준 구현임.

 

enqueue대신에 + = 나 + + = 연산 사용

 

배열 시쿼스

고정 크기의 변경가능한 시퀀스

 

스택

정해진 변수로 계속 변경 가능

 

배열 스택

ArrayStack은 필요에 따라 크기를 변화시키는 배열, 인덱스를 통한 빠른 접근

 

해시 테이블

원소를 배열에 추가, 원소의 해시코드에 따라 그 위치를 결정

스칼라 변경 가능 맵은 해시 테이블 기반

 

약한 해시

weak reference 약한 참조, 어떤 객체를 가리키는 모든 참조가 약한 참조뿐이라면 gC는 그 객체가 사용 중이지 않다고 판단

키나 함수 결과를 해시 맵에 저장하면 계속 키가 커지는 데 참조를 다 하면, 맵에서 삭제됨.

 

concurrent map

여러 스레스에서 접근 가능, atomic operatation

 

변경 가능한 비트 집합

제자리에서 비트 변경 가능

 

10. 배열

자바 배열과 일대일 대응 : Array[Int] int[] :  Array[String] String[]

그리고 추가적인 기능

  1. 스칼라 배열은 제네릭할 수 있다 Array[T]
  2. 스칼라 시퀀스와 호환 가능
  3. 모든 시퀀스의 연산을 지원

가능한 이유 : 암시적 변환을 대칭적으로 사용함. 배열을 Seq로 사용할 때마다 암시적으로 그것을 Seq의 서브타입으로 감싸준다.

서브 클래스는  scala.collection.mutable.WrappedArray

배열에 적용 가능한 다른 변환 : 배열을 모든 시퀀스 메소드를 지원하는 ArrayOps 라는 객체로 감싼다

ArrayOps 변환이 WrappedArray 변환보다 우선순위가 높다

 

Array[T] 타입

런타임 힌트 class tag / 

완전히 제네릭 한 경우는 context bound 사용

import scala.reflect.ClassTag

def evenElems[T: ClassTag](xs: Vector[T]) : Array[T] = {

    val arr = new Array[T] ((xs.length + 1) / 2)

    for (i <- 0 until xs.length by 2)

        arr(i/2) = xs(i)

    arr

}

 

11. 문자열

문자열은 직접적인 시퀀스는 아니다. 하지만 이들도 시퀀스로 변활할 수 있고, 모든 시퀀스 연산을 지원

 

 

12. 성능특성

 

변경불가능

List, Stream, Vector, Stack, Queue, Range, String

 

변경 가능

ArrayBuffer, ListBuffer, StringBuffer, MutableList, Queue, ArraySeq, Stack, ArrayStack, Array

 

변경불가능

HashSet/HashMap TreeSet/TreeMap BitSet ListMap

 

13. 동일성

컬렉션 라이브러리는 동일성과 해시에 대해 일관된 접근 방법을 취한다.

컬랙션을 집합, 맵, 시퀀스로 구분, 다른 범주에 속하는 컬렉션은 같지 않다. Set(1, 2, 3) 과 List(1, 2, 3)은 같지 않다.

같은 범주에 속한 두 컬렉션이 포한하는 원소가 모두 같으면 두 컬렉션은 서로 같으며, 그 역도 성립한다.

List(1, 2, 3) == Vector(1, 2, 3)

HashSet(1, 2) == TreeSet(1, 2)

 

14. 뷰

컬렉션에는 새로운 컬렉션을 만들 수 있는 메소드가 많다.

map, filter, + + : 변환기 transformer

변환기 구현에는 엄격한 방식과 엄격하지 않은 방식 (게으른)

엄격한 변환기는 새 컬렉션에 모든 원소를 다 집어 넣는다.

엄격하지 않은 변환기는 컬렉션에 대한 프록시만 만들고, 원소는 요청이 있을 때만 만든다.

 

염격하지 않는 변환기 예

def lazyMap[T, U] (col1: Iterable[T], f: T => U) =

    new Iterable[U] {

        def iterator = col.iterator map f

    }

 

Stream 메소드를 제외하고는 기본적으로 모두 엄격함. Stream 은 자신의 모든 변환기를 게으르게 구현함.

모든 컬렉션을 엄격하게 만들거나 되돌리는 체계적인 방법이 있다. 컬렉션 뷰를 바탕으로

val v = Vector(1 to 10: _*)

v map (_ + 1) map (_ * 2)

 

 

(v.view map (_ + 1) map (_ * 2)).force

 

 

val vv = v.view

 

 

vv map (_ + 1)

 

 

res13 map (_ *2)

 

 

rea14.force

v.view 호출은 SeqView 필요에 따라 계산하는 Seq 반환

중간구조를 없애기 위해서 

view 사용을 고려해야하는 두가지 이유 :

첫째 성능 : 컬렉션을 뷰로 바꿔서 중간 결과 생성을 피할 수 있음

둘째, 변경 가능한 시퀀스의 뷰에 대해서 

회문 : parlindrome

 

def isPalindrome(x: String) = x == x.reverse

def findParlindrome(s: Seq[String]) = s find isPalindrome

 

 

findParlindrome(words take 1000000) / 단점은 회문이 시퀀스에  첫단어 일지라도, 항상 백만 단어 짜리 시퀀스 생성

 

 

findPalindrome(words.view take 1000000)

원소가 백만개 있는 시퀀스를 만드는 대신 가벼운 뷰하나 생성

 

 

val subarr = arr.view.slide(3, 6)

이뷰는 원소는 복사하지 않고, 각 원소에 대한 참조만을 갖는다.

 

뷰가 모든 것을 모듈화 하는데 도움이 됨.

 

스크림을 제외한 모든 컬렉션과 뷰는 엄격하다, 엄격한 것에서 게으른 컬렉션으로 가는 방법을 view 메소드를 사용하는 방법뿐

부수 효과 없는 컬렉션 반환을 사용하는 완전히 함수적인 코드에서 뷰사용

모든 변경을 명시적으로 수행해야 하는 변경 가능한 컬렉션에서만 뷰 사용

 

가장 피해야 하는 것은 부수효과 있으면서 새 컬렉션을 만드는 연산과 뷰를 혼용하는 것

 

15. 이터레이터

이터페이터는 컬렉션이 아님, 원소에 하나하나 접근할 수 있는 수단, hasNext, next로 접근

버퍼이터레이터 : 

 

16. 컬렉션 처음 만들기

 

17. 자바와 스칼라 컬렉션 변환

JavaConversions 객체에 대한 암시변환 제공

스칼라direction자바

Iterator <=> java.util.Iterator
Iterator <=> java.util.Enumaration
Iterator <=> java.util.Iterable
Iterable <=> java.util.Collection
mutable.Buffer <=> java.util.List
mutable.Set <=> java.util.Set
mutable.Map <=> java.util.Map
Seq => java.util.List
mutable.Seq => java.util.Set
Set => java.util.Set
Map => java.util.Map

 

 

18. 결론

함수 리터럴과 변경 불가능한 다양한 컬렉션 타입과의 결합

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. List 클래스 개관

리스트는 패키지 않에 List 라는 추상 클래스로 되어 있다. 두가지 서브클래스가 있는데, ::와 Nil 이다

List는 추상 클래스다. new List는 안된다. 타입 파라미터 T, +는 리스트가 공변성, 유연해짐, 이로서 List[Int] 타입의 값을 List[Any] 같은 타입의 변수에 할당 가능

리스트 연산은 3가지 기본 메소드로 만들 수 있음, 

def isEmpty: Boolean

def head: T

def tail: List[T]

package scala

abstract class List[+T] {

}

- Nil 객체

Nil 객체는 빈 리스트를 정의, Nil은 List[Nothing]을 상속, 공변성을 감안하면, Nil은 모든 List 타입과 서로 호환 가능

case object Nil extends List[Nothing] {

    override def isEmpty = true

    def head: Nothing =

        throw new NoSuchElementException("head of empty list")

    def tail: List[Nothing] =

        throw new NoSuchElementException("tail of empty list")

}

 

- :: 클래스

:: 클래스는 콘즈 cons 라고 부르며, 구성하다 construct 의 약자이다. 비어 있지 않는 리스트를 표현

:: 인 이유는 중우 표기 :: 와 패턴 매치를 하기 위해서

:: 가 case class 인 경우, x :: xs  패턴은 :: (x, xs) 이다

final case class ::[T](hd: T, tl: List[T]) extends List[T] {

    def head = hd

    def tail = tl

    override def isEmpty: Boolean = false

}

 

 

// 더 짧게

final case class ::[T](head: T, tail: List[T]) extends List[T] {

    override def isEmpty: Boolean = false

}

모든 케이스 클래스의 파라미터는 암시적으로 해당 클래스의 필드임

 

리스트 구성 construct

contruct 메소드  인 ::와 ::: 는 특별, 콜론:으로 끝나기 때문에 오른쪽 피연산자에 바인딩 된다.

x :: xs 가 x.:: (xs) 가 아니고 xs. :: ( x )  호출과 같다.

x는 리스트의 원소 타입이므로 임의의 타입이 될수 있고 그런 타입에는  :: 메소드가 있다는 보장이 없음.

따라서 :: 메소드는 원소 값을 받아서 새 리스트를 만들어 내야 한다.

def :: (U >: T)(x: U): List[U] == new scala.::(x, this)

이 메소드는 타입 파라미터 U를 받는 다형성 메소드 임에 유의. U는 리스트의 원소 타입인 T의 슈퍼타입이어야 한다는 제약 (U >: T) 추가하는 타입은 U여야 하고 결과 타입은 List[U] 이다

def :::[U >: T](prefix: List[U]): List[U] =

    if (prefix.isEmpty) this

    else prefix.head :: prefix.tail :: this

 

 

prefix.head :: prefix.tail ::: this

prefix.head :: (prefix.tail ::: this)

(prefix.tail ::: this ).::(prefix.head)

this.::(prefix.tail).::(prefix.head)

2. 리스트 버퍼 클래스

리스텅 대한 전형적인 접근 패턴은 재귀

def incAll(xs: List[Int]): List [Int] = xs match {

    case List() => List()

    case x :: xs1 => x + 1 :: incAll(xs1)

}

// 꼬리 호출이 안므로 stack frame 이 필요하고, 물리적 한계는 30,000 - 50,000 의 원소 제한

 

 

var result = List[Int]()

for (x <- xs) result = result ::: List(x + 1)

result

// 맨뒤에 추가하므로 비효율

 

 

import scala.collecion.mutable.ListBuffer

val buf = new ListBuffer[Int]

for (x <- xs) buf += x + 1

buf.toList

 

 

// 더 나은 대안 리스트 버퍼

 

 3. 실제 List 클래스

final override def map[U](f: T => U): List[U] = {

    val b = new ListBuffer[U]

    var these = this

    while (!these.isEmpty) {

        b += f(these.head)

        these = these.tail

    }

    b.toList

}

 

 

final case class ::[U](he: U, private[scala] var tl: List[U)) extends List[U] {

    def head = hd

    def tail = tl

    override def isEmpty: Boolean = false

}

// vl이 var 인데 변경가능이데 private[scala] 이라 패키지 안에서만 변경 가능, 클라이언트는 변경 불가

 

 

override def toList: List[T] = {

    exported = !start.isEmpty

    start  

}

 

 

override def += (x: T) = {

    if (exported) copy()

    if (start.isEmpty) {

        last0 = new scala.::(x, Nil)

        start = last0

    } else {

        var last1 = last0

        last0 = new scala.::(x, Nil)

        last1.t1 = last0

    }

}

 

4. 외부에서 볼 때는 함수형

외부에서는 함수적이지만, 내부에서는 리스트 버퍼를 사용해 명령혀으로 되어 있음

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. 암시적 변환

메소드가 하나뿐인 인터페이스를 구현하는 내부 클래스 : 액션 리스너 등으로 외부의 이벤트를 받아 동작을 제어

스칼라에서는 함수 리터럴로 가능한데, 자바 스윙에는 없었음. 이또한 자바 8이 되면서 추상 메소드가 하나뿐인 인터페이스는 람다로 변경이 가능해짐.(SAM Single Abstract Method), 혹은 함수형 인터페이스

 

자바 스타일

var button = new JButton

button.addActionListener(

    new ActionListener {

        def actionPerformed(event: ActionEvent) = {

            println("pressed!")

        }

    }

)

 

 

 

 

implicit def function2ActionListener(f: ActionEvent => Unit) =

    new ActionListener {

        def actionPerformed(event: ActionEvent) = f(event)

    }

 

 

button.addActionListener(

    function2ActionListener(

        (_: ActionEvent) => println("pressed")

    )

)

 

 

implicit으로 선언했기 때문에 생략해도 컴파일러가 알아서 처리 해줌

 

 

button.AddActionListener(

    (_: ActionEvent) => println("pressed")

)

 

2. 암시적 규칙

암시적 정의는 컴파일러가 타입 오류를 고치기 위해 삽입 할 수 있는 정의

컴파일러는 암시적 변환을 위해서 다음과 같은 규칙에 따라 처리함.

표시규칙 : implicit로 표시한 정의만 검토 대상

 

스코프규칙 : 삽입된 implicit 변환은 스코프 내에 단일 식별자로만 존재하거나, 변환의 결과나 원래 타입과 연관이 있어야 한다.

 

예외적으로 컴파일러는 원 타입이나 변환 결과 타입의 동반 객체에 있는 암시적 정의도 살펴본다

한번에 하나만 규칙 : 오직 하나의 암시적 선언만 사용한다.

 

명시성 우선 규칙 : 코드가 그 상태 그대로 타입 검사를 통과한다면 암시를 통한 변환을 시도하지 않는다.

 

암시적 변환 이름 붙이기

 

object MyConversion

implicit def stringWrapper(s: String) : IndexedSeq[Char] = ....

import Myconversion.stringWrapper

암시가 쓰이는 부분

 

"abc".exists

stringWrapper("abc").exists 로 변환하는데 String에는 exist 메소드가 없지만, IndexSeq에는 있기 때문

 

3. 예상 타입으로의 암시적 변환

implicit def doubleToInt(x: Double) = x.toInt

 

 

val i:Int = 3.5

i: Int = 3

scala.Predef 는 모든 스칼라 프로그램에 암시적으로 임포트 됨

 

4. 호출 대상 객제 변환

용도 :

1. 수신 객체 변환을 통해 새 클래스를 기존 클래스 계층구조에 매끄럽게 통합 할 수 있다

2. 언어 안에서 도메인 특화 언어를 만드는 일을 지원

 

새 타입과 함께 통합하기

val oneHalf = new Rational(1, 2) // 1/2

oneHalf + 1

res1: Rational = 3/2

 

 

1 + oneHalf

error : overloaded method value ... cannot be applied.

 

 

implicit def intToRational(x: Int) = new Rational(x, 1)

 

 

1 + oneHalf

res2: Rational = 3/2

intToRational(1) + oneHalf

 

새로운 문법 흉내 내기

Map(1 - > "one, 2 - > "two", 3 - > "three")

스칼라 프리엠블 scala.Predef의 ArrowAssoc 클래스의 메소드

package scala

object Predef {

    class ArrowAssoc[A](x: A) {

        def -> [B](y: B): Tuple2[A, 2] = Tuple(x, y)

    }

    implicit def any2ArrowAssoc[A](x: A) ArrowAssot[A] = new ArrowAssoc(x)

}

 

암시적 클래스

case class Rectangle(width: Int, height: Int)

 

 

implicit class RectangleMaker(width: int) {

    def x(height: Int) = Rectangle(width, height)

}

 

 

implicit def RectangleMaker(width: Int) = new RectangleMaker(width)

 

 

val myRectangle = 3 x 4

myRectangle: Rectangle = Rectangle(3, 4)

암시적 클래스는 케이스 클래스 일 수 없으면, 암시 클래스의 생성자에는 파라미터가 1개만 있어야 한다.

암시 클래스는 반드시 다른 객체, 클래스, 또는 트레이트와 같은 파일에 들어 있어야 한다.

 

5. 암시적 파라미터

class PrefereedPrompt(val: preference: String)

 

 

object Greeter {

    def greet(name: String)(implicit prompt: PreferredPrompt) = {

        println("Welcome, " + name + ". The system is ready.")

        println(prompt.preference)

    }

}

 

 

val bobsPrompt = new PreferredPrompe"relax> ")

Greeter.greet("bob")(bobsPrompt)

 

 

Welcome, Bob. The system is ready.

relex>

 

 

object JoesPrefs {

    implicit val prompt = new PreferredPrompt("Yes, master> ")

}

 

 

Greeter.greet("Joe")

cound not find implicit value for

 

 

import JoesPrefs._

 

 

Greeter.greet("Joe")

Welcome, Joe. The system is ready.

Yes, master>

 

여러 파라미터가 있는 암시적 파라미터 목록

class PreferredPrompt(val preference: String)

class PreferredDrink(val preference: String)

 

 

object Greeter {

    def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) = {

        println("Welcome, " + name + ". The system is ready.")

        print("But while you work, ")

        println("why not enjoy a cop of " + drink.preference +" ?")

        println(prompt.preference)

    }

}

 

 

object JoesPrefs {

    implicit val prompt = new PreferencePrompt("Yes, master> ")

    implicit val drink = new PreferenceDrink("tea")

}

 

 

import JoesPrefs._

 

 

Greeters.greet("Joe")(prompt, drink)

Greeters.greet("Joe")

 컴파일러가 암시적 파라미터를 고를 때, 스코프 안에 있는 값의 타입과 파라미터의 타입이 일치하게 하기 때문에, 

실수로 일치하는 일이 적도로 (오작동), 암시적 파라미터를 충분히 드물거나, 특별한 타입으로 만들것

암시적 파라미터가 가장 많이 쓰이는 경우는 파라미터 목록의 앞쪽에 명시적으로 들어가야 하는 인자 타입에 대한 정보를 제공하고 싶은 경우.

 

암시적 파라미터에 대한 스타일 규칙

암시적 파라미터의 타입 안에는 일반적이지 않은 특별한 이름의 타입을 사용하는 게 좋다.

def maxListPoorStyle[T](elements: List[T])(implicit orderer: (T, T) => Boolean): T

흔한 제네릭 타입임.

def maxListImpParam[T](elements: List[T])(implicit ordering: Ordering[T])): T

 

6. 맥락 바운드

스칼라는 이 파라미터의 이름을 없애고 메소드 헤더를 맥락 바운드 (context bound)를 사용해 더 짧게 작성할 수 있게 해준다.

def maxList[T](elements: List[T])(implicit ordering: Ordered[T]): T

    .. if (ordering.gt(x, maxRest)) x

 

 

def implicitly[T](implicit t: T) = t

 

 

def maxList[T](elements: List[T])(implicit comparator: Ordered[T]): T

    .. if (implicitly[Ordering[T]].gt(x, maxRest)) x

 

 

def maxList[T : Ordering](elements: List[T]): T

    .. if (implicitly[Ordering[T]].gt(x, maxRest)) x

[T <: Ordered[T]] 라고 쓰면 그것은 T가 Ordered[T] 타입이어야 한다는 뜻

[T : Ordering] 이라고 쓰면 그것은 T가 어떤 것이 되어야 한다는 것을 말하지 않는다. 다만 T와 관련 있는 어떤 형태의 순서(Ordering)이 존재해야한다는 뜻

맥락 바운스를 사용하면, 순서(또는 그 타입의 어떤 다른 성질)를 필요로 하는 코드를 사용하되, 그 타입의 정의를 변경하지 않고도 그것이 가능하게 해준다.

 

7. 여러 변환을 사용하는 경우

암시적 변환이 여러개 있을 경우, 변환을 추가 하지 않는다.

2.7까지느 명시적으로 지정

2.8에서는, 가능한 변환 중 하나가 다른 하나보다 절대적으로 더 구체적이라면 컴파일러는 더 구체적인 것을 선택한다.

다른 변환 대신 항상 선택하리라고 믿을 만한 이유가 있다면, 명시적으로 사용을 요구하지 않는다.

더 구체적인 상황

  • 전자의 인자 타입이 후자의 서브타입이다.
  • 두 변환 모두 메소드 인데, 전자를 둘러싼 클래스가 후자를 둘러싼 클래스를 확장한다.

동기는, 자바 컬렉션과 스칼라 컬랙션, 그리고 문자열의 상호작용성을 향상하기 위함.

val cba = "abc".reverse

cba타입을 유추하면 직관적으로는 String이지만 collection 임.

즉 "abc" == "abc".reverse.reverse 가 false 임

2.8에서 String에서 StringOps라는 새로운 타입으로 변환하는 더 구체적인 암시 변환이 생김.

 

8. 암시 디버깅

명시적으로 변환해보거나

-Xprint:typer

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

추상 멤버 : 클래스나 트레이트의 멤버가 그 클래스 안에 완전한 정의를 갖고 있지 않으면 추상 클래스

추상 멤버는 그 멤버가 정의된 클래스를 상속한 서브클래스에서 구현해야함.

자바에서는 추상 메소드를 정의

총 4가지 종류의 추상 멤버,ㅣ val, var, 메소드, 타입

미리 초기화된 필드 : pre-initialized field

지연 : lazy val

경로 의존적인 타입 : path dependent type

열거형 : enumeration

 

1. 간단한 추상 멤버

추상타입 T, 추상 메소드, 추상 val 추상 var

trait Abstract {

    type T

    def transform(x: T) T

    val initial: T

    var current: T

}

 

 

class Concrete extends Abstract {

    type T = String

    def transform(x: String) = x + x

    val initial = "Hi"

    var current = initial

}

 

2. 타입멤버

추상 타입은 클래스나 트레이트의 멤버로 정의없이 선언된 타입

트레이트는 정의상 추상적이지만, 스칼라에서 추상타입이라고 부르지 않음.

스칼라의 추상타입은 Abstract 트레이트의 T 타입 처럼 항상 어떤 클래스나 트레이트의 멤버이다

alias 로 생각할 수도 있음

 

3. 추상 val

val initial: String

이름과 타입은 추지만 값은 지정하지 않음.

변수에 정확한 값은 모르지만, 그 변수가 클래스의 인스턴스에서 변하지 않으리란 사실은 알고 있을 때 추상 val을 사용한다.

initial의 추상 val이면 클라이언트는 obj.initial을 사용할 때 마다 같은 값을 얻을 수 있음을 확신

intial이 추상 메소드라면, 그런 보장은 없음.

추상 val은 그에 대한 구현에 제약을 가한다. 구현시 val 정의를 사용해야한다는 제약이다.

 

4. 추상 var

trait AbstractTime {

    var hour: Int

    var minute: Int

}

 

 

getter/setter 스칼라가 만들어줌.

5. 추상  val 초기화

추상 val은 때때로 슈퍼 클래스 파라미터와 같은 역할을 한다.

슈퍼 클래스에 빠진 미세한 부분을 서브 클래스에 전달 할 수 있는 수단

트레이트에서는 파라미터를 넘길 생성자가 없기 때문이다

트레이트를 파라미터화 할려면 서브 클래스에서 구현하는 추상 val을 통해서 수행

trait RationalTrait {

    val numerArg: Int // 유리수 분자

    var denomArg: Int  // 분모

}

 

 

new RationalTrait(

    val numerArg = 1

    val denomArg = 2

}

// 익명클래스

 

 

new RationalTrait {

    val numerArg = expr1

    val denomArg = expr2

}

// 익명 클래스 초기화 중에 표현식 expr1, expr2 계산

// 익명 클래스는 RationalTrait 다음에 초기화 됨

numberArg와 demonArg값은 RationalTrait 초기화 전에는 사용가능하지 않기 때문에 0을 돌려 받음.

 

 

 

 

 

// 아래의 버전에서는 문제가 됨

trait RationalTrait {

    val numerArg: Int

    var denumArg: Int

    require(denomArg != 0)

    private val g = gcd(numerArg, denomArg)

    val numer = numerArg / g

    var denom = denumArg / g

    private def gcd(a: Int, b: Int): Int =

        if (b == 0) a else gcd(b, a % b)

    override def toString = numer + "/" + denum

}

 

 

val x = 2

new RationalTrait(

    val numerArg = 1 * x

    var denumArg = 2 * x

)

 

 

requirement failed Exception

 

 

RationalTrait 클래스 초기화 할때 denumArg 값이 0이기 때문

클래스의 파라미터 인자는 클래스 생성자에 전달되기 전에 계산한다

반면 서브클래스의 val 정의를 계산하는 것은 슈퍼 클래스를 초기화 한 다음에 이뤄진다.

이에 대한 해결로 필드를 미리 초기화 하는 것과 지연 val 하는 방법 두 가지가 있음. (방법이 미리 다 해 놓거나 아님 다 초기화 되고 하거나...)

 

 

필드를 미리 초기화

중괄호를 이용하여 슈퍼클래스 호출 전에 서브클래스 필드 초기화

미리 초기화 한 필드는 클래스 생성자의 인자와 비슷하게 동작함.

new {

    val numberArg = 1 * x

    val denumArg = 2 * x

} with RationalTrait

 

 

object twoThird extends {

    val numerArg = 2

    var denumArg = 3

} with RationalTrait

 

 

class RationalClass(n: Int, d: Int) extends {

    val numerArg = n

    val denomArg = d

} with RationalTrait {

    def + (that: RationalClass) = new RationalClass(

        numer * that.denom + that.number * denom,

        denom * that.denom

    )

}

 

지연 계산 val 변수

내가 초기화 하는게 아닌 system이 초기화하게 지연, 

어떤 val 정의를 lazy 하게 만들면 됨.

object Demo {

    lazy val x = { println("initilized"); "done"}

}

 

 

Demo.x 선택시 println 부분 호출됨.

 

 

trait LazyRationalTrait {

    val numerArg: Int

    val denomArg: Int

    lazy val numer = numberArg: g

    lazy val denom = denomArg: g

    override def toString = numer + "/" + denum

    private lazy g = (

        require(denomArg != 0)

        gcd(numerArg, denomArg)

    )

    private def gcd(a: Int, a: Int): Int =

        if (b == 0) a else gcd(b, a % b)

}

 

6. 추상 타입

추상 타입 선언은 서브 클래스에서 구체적으로 정해야 하는 어떤 대상에 대한 빈공간을 마련해 두는 것

T는 선언 시점에는 어떤 타입인지 알려져 있지 않은 타입을 참조하기 위해 사용함

class Food

abstract class Animal {

    type SuitableFood <: Food

    // SuitableFood의 상위 바운스는 Food

    def eat(food: SuitableFood)

}

 

 

class Grass extends Food

class Cow extends Animal {

    type SuitableFood = Grass

    override def eat(food: Grass) = {}

}

 

 

class Fish extends Food

var bessy: Animal = new Cow

bessy eat (new Fish)

 

 

error: type mismatch

required: bessy.SuitableFood

 

 

//의미 : messy가 참조하는 객체의 멤버인 SuitableType

 

7. 경로에 의존하는 타입

마지막 오류 메세지는 eat 메소드가 요구하는 타입인 bessy.SuitableFood

경로에 의존하는 타입 path-dependent type

경론ㄴ 객체에 대한 참조를 의미, 참조변수 : 싱글톤 객체 이름

문제는 eat 메소드에 넘기는 SuitableFood 객체의 타입인 bessy.SuitableFood는 eat 의 파라메터 타입인 lassie.suitableFood와는 같지 않음.

하지만 두 Dog의 경우는 실제로 동일한 타입임.

경로 의존 타입은 자바의 내부 클래스 타입과 문법이 비슷하지만, 

경로 의존 타입은 외부 객체에 이름을 붙이는 반면

내부 클래스 타입 이름의 외부 클래스에 이름을 붙인다는 점

자바와 마찬가지로 스칼라에서도 내부 클래스 인스턴스에는 그 인스턴스를 둘러 싼 클래스와 종류

외부 클래스 인스턴스를 지정하지 않고 내부 클래스만 인스턴트.

 

다른 방법

o1.Inner 타입은 특정 이후 후 인스턴스를 가져오기

경로 의존 타입은 자바의 클래스 타입과 비슷하지만, 

new o1.Inner

 

8. 세분화한 타입

어떤 클래스 A가 다른 클래스 B를 상속할 때, 전자 (A)가 후자(B)dml 이름에 의한 서브타입 nominal subtype

구조적인 서브 타이핑 지원 : structual subtyping

세분화한 타입을 사용하면 됨 : refinement type

Animal {type SuitableFood = Grass}

 

 

// 목초리

class Pasture {

    var animals: List[Animal {type SuitableFood = Grass}] = Nil

}

 

9. 열거혐

경로 의존적 타입의 재미있는 활용 ; 스칼라의 열거형 : enumeration type

object Color extends Enumeration {

    val Red = Value

    val Grren = Value

    val Blue = Value

}

 

 

object Direction extends Enumeration {

    val North = Value("North")

    val East = Value("East")

    val South = Value("South")

    val West = Value("West")

}

 

 

Direction.Value 과 Color.Value는 다름

 

10. 사례 연구

object Converter {

    var exchangeRate = Map (

        "USD" -> Map("USD" -> 1.0, "EUR" -> 0.7596, "JPY" -> 1.211, "CHF" -> 1.223),

        "EUR" -> Map("USD" -> 1.316, "EUR" -> 1.0, "JPY" -> 1.594, "CHF" -> 1.623),

        "JPY" -> Map("USD" -> 0.8257, "EUR" -> 0.6272, "JPY" -> 1.0, "CHF" -> 1.018),

        "CHF" -> Map("USD" -> 0.8108, "EUR" -> 0.6160, "JPY" -> 0.982, "CHF" -> 1.0)

    )

}

 

 

abstract class CurrentyZone {

    type Currency <: AbstractCurrency

    def make(x: Long): Currency

     

    abstract class AbstractCurrency {

        val amount: Long

        def designation: String

 

 

        def + (this: Currency): Currency = make(this.amount + that.amount)

         

        def * (x: Double): Currency = make(this.amount * x).toLong)

        def - (this: Currency): Currency = make(this.amount - that.amount)

        def / (this: Double) = make((this.amount / that).toLong)

        def / (this: Currency): Currency = make(this.amount / that.amount)

 

 

        def from(other: CurrencyZone#AbstractCurrency): Currency =

            make(math.round(

                other.amount.toDouble * Converter.exchangeRate

                    (other.designation)(this.designation)

        private def decimal(n: Long): int =

            if (n == 1) 0 else 1 + decimals(n / 10)

         

        override def toString =

            ((amount.toDouble / CurrencyUnit.amount.toDouble)

                formatted ("%." + decimals(CurrencyUnit.amount) + "f")

                + "" + designation)

    }

 

 

    var CurrencyUnit: Currency

}

 

 

Japan.Yen from US.Dollar * 100

 

 

Europe.Euro from res16

 

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

큐를 통해서 해당 내용 설명

 

 

1. 함수형 큐

class Queue[T] (

    private val leading: List[T]

    private val trailing: List[T]

) {

    private def mirror =

        if (leading.isEmpty)

            new Queue(trailing.reverse, Nil)

        else

            this

     

    def head = mirror.leading.head

 

 

    def tail = {

        val q = mirror

        new Queue(q.leading.tail, q.trailing)

    }

 

 

    def enQueue(x: T) =

        new Queue(leading, x :: trailing)

 

}

 

 

2. 정보 은닉

내부 구현을 감춤. 1의 은닉을 해야하는 주된 이유는

생성자 파라메터가 2개인 이유, 이는 큐를 표현하는 상식적인 방법이 아니기 때문, 

하여 생성자를 감춰야함.

 

2.1 비공개 생성자와 팩토리 메소드

생성자를 비공개로 만듦 : private

이렇게 할 경우, 타입으로 이 이름을 사용할 수는 없음.

생성자는 보조 생성자를 추가 : def this() = this(Nil, Nil)

 

3. 변성표기

생성자 아니고 트레이트로 만들면 타입이 아님, 타입 파라메터를 받기 때문, 

def method(q: Queue) : X 

def method(q: Queue[AnyRef]) : 파라미터화 된 타입

타입 생성자라고도 함 Queue, Queue[Int], Queue[String] 등의 일련의 타입을 생성

제네틱 트레이드라고도 함.

Queue트레이트는 제네릭 큐,Queue[Int]는 구체적 큐

Queue 트레이트는 타입 파라미터 T에 대해 공변적 이다 (유연하다)

trait Queue[+T] : 공변적이 됨. (유연해 짐)

  • 접두사도 있다 : 반공변

trait Queue[-T]

어떤 타입 파라미터의 공변, 반공변, 무공변 여부를 파라미터의 변성 variance 라고 부름

+, - 변성 표기 variance annotation

스칼라는 배열을 무공변으로 다룬다.

 

4. 변성 표기 검사

스칼라 컴파일러는 타입 파라미터를 표기한 변경 표기를 검사함.

스칼라 컴파일러는 클래스나 트레이트 본문의 모든 위치를 긍정적, 부정적, 중립적 으로 구분

 

5. 하위 바운스

class Queue[+T] (private val leading: List[T],

    private val trailing: List[T] ) {

        // U >: T  - T를 U의 하위 바운스로 지정

        // 따라서 U는 T의 슈퍼타입이여야만 한다.

        def enqueue[U >: T](x: U) =

            new Queue[U](leading, x :: trailing)

}

Fruit 클래스에 두 서브클래스가 Apple, Orange가 있다.

Queue[Apple] 하면 Queue[Fruit]으로 변경됨

타입 위주의 설계

스칼라는 자바의 와일드카드에서 볼수 있는 사용 위치 변셩 보다 선언 위치 변셩을 선호함.

 

6. 반 공변성

 

U 타입의 값이 필요한 모든 경우를 T 타입의 값으로 대치할 수 있다면, T  타입을 U 타입의 서브타입으로 가정해도 안전함.

리스코프 치환 원칙, 즉 상위에서 할 수 있는 연산은 하위에서도 다 가능해야한다를 다르게 사용한 것

trait Function1[-S, +T] {

    def apply(x: S) : T

}

 

class Publication(val title: String)

class Book(title: String) extends Publication

 

 

object Library {

    val books: Set[Book] =

        Set(

            new Book("Programming in Scala")

            new Book("Walden")

        )

    def printBookList(info: Book => AnyRef) = {

        for (book <- books) println(info(book))

    }

}

 

 

object Customer extends App {

    def getTitle(p: Publication): String = p.title

    Library.printBookList(getTitle)

}

 

book → publication => String → AnyRef

Book => AnyRef

 

7. 객체의 비공개 데이터

스칼라의 변성 검사 규칙에는 객체 비공개를 정의를 위한 특별한 규칙이 있음.

스카라가 +나 - 변성 표기가 있는 타입 파라미터를 같은 변성 위치에서만 사용하는지 검사할 때, 

객체 비공개 정의는 제외하고 검사한다.

private[this]

 

8. 상위 바운드

정렬 함수 예, 비교 함수를 첫번째 인자로 받고, 정렬할 리스트를 두 번째 커링한 인자로 바는 병합 정렬 함수

다른 구현 방법은

리스트에 Ordered 트레이트를 섞어 넣는 것

하위 바운스  > :

상위 바운스 < :

def orderedMergeSort[T <: Ordered[T]](xs: List[T]): List[T] = {

    def merge(xs: List[T], ys: List[T]): List[T] =

        (xs, ys) match {

            case (Nil, _) => ys

            case (_, Nil) => xs

            case (x :: xs1, y :: ys1) =>

                if (x < y) x :: merge(xs1, ys)

                else y :: merge(xs, ys1)

        }

    val n = xs.length / 2

    if (n == 0) xs

    else {

        val (ys, zs) = xs splitAt n

        merge(orderedMergeSort(ys), orderedMergeSort(zs))

    }

}

 

T <: Ordered[T] 라는 문법을 사용해서 타입 파라미터 T 상위 바운드가 Ordered[T]

orderedMergeSort 에 전달하는 리스트 원소의 타입이 Ordered의 서브 타입이여만 한다는 의미

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH21. 암시적 변환과 암시적 파라미터  (0) 2020.03.29
Scala CH20. 추상 멤버  (0) 2020.03.29
Scala CH18. 변경 가능한 객체  (0) 2020.03.29
Scala CH17. 컬렉션  (0) 2020.03.29
Scala CH16. 리스트  (0) 2020.03.29
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. 객체가 변경 가능한 상태

순수 함수형 객체의 필드 메소드 호출 > 동일한 결과

지역 변수가 var 이면 변경 가능한 객체 

 

2. 재할당 가능한 변수와 프로퍼티

getter : var x 는 x

setter : x_=

 

var celsius: Float = _ // 초기화

= _ 사용하지 않으면 추상 변수 선언한 것으로 간주함.

 

3. 이산 이벤트 시뮬레이션

디지털 회로를 위한 DSL

언어를 정의하는 과정은

  1. 스칼라 같은 호스트 언어 안에 DSL을 포함시키는 일반적인 방법
  2. 간결하지만 범용적인 프레임워크 제시
  3. 시뮬레이션 동안 동작 기록
  4. 물리적 대상을 시뮬레이션 객체로 모델링하고 물리적 (실제) 시간을 시뮬레이션 프레임워크를 사용해 모델링

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH20. 추상 멤버  (0) 2020.03.29
Scala CH19. 타입 파라메터화  (0) 2020.03.29
Scala CH17. 컬렉션  (0) 2020.03.29
Scala CH16. 리스트  (0) 2020.03.29
Scala CH15. 케이스 클래스와 패턴 매치  (0) 2020.03.29
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. 시퀀스

가장 중요한 시퀀스는 List 클래스, 변경 불가능한 linked list, 원소 추가나 삭제가 용이, 임위의 위치에 접근할때 순차 일기

 

배열

배열은 원소의 시퀀스를 저장, 

임의의 원소에 효율적으로 접근

 

리스트 버퍼

리스트 끝에 추가할려면 reverse 하고 앞에 추가하고, 다시 reverse

reverse 연산을 피할 수 있는 방법은 ListBuffer

리스트 버퍼는 변경가능한 객체

+= 원소 추가

+=: 원소 앞에 추가

ListBuffer눈 잠재적인 stock overflow 방어

 

배열버퍼

ArrayBuffer

 

문자열

StringOps

 

2. Set(집합)과 Map(맵)

Set과 Map 을 만들면 디폴트로 변경 불가능한 객체 생성

Map, Predef.Map, scala.collection.immutable.Map 동일

Set의 특징은 특정 객체는 최대 하나만 들어가도록 보장

같은지는 == 로 결정

Map의 특징은 집합의 각 원소 사이에 연관 관계 설정

맵 생성은 key, value 필요

Set : HashSet 으로 제공

Map : HashMap

SortedSet

SortedMap

구현은 TreeSet, TreeMap

원소나 키는 red-black tree 사용

 

 

3. 변경가능, 변경 불가능

가능한 변경 불가능

++= 는 집합에 원소 컬랙션을 추가한다.

 

4. 컬렉션 초기화

TreeSet 만들어서 ++ 연산자 통해서 추가

배열이나 리스트로 바꾸기

toList

toArray

 

5. 튜플

정해진 개수의 원소를 한데 묶는다

원소 타입이 서로 다를 수 있다

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

리스트 특징

리스트는 변경 불가능

리스트 구조는 재귀적이지만, (즉 연결 리스트)

배열은 평면적이다

 

스칼라의 리스트 타입은 공변적 (covariant)

List[String]은 List[Object]의 서브타입

List[Nothing]은 List[T]의 서브타입

 

- 리스트 생성

Nil :: (cons)

List(1, 2, 3) 은 1 :: (2 :: (3 :: Nil)) 이라는 리스트 생성

val nums = 1 :: 2 :: 3 :: 4 :: Nil

 

- 리스트의 연산

head는 어떤 리스트의 첫 번째 원소를 반환

tail는 어떤 리스트의 첫 번째 원소를 제외한 나머지 원소로 이뤄진 리스트

isEmpty는 리스트가 비어 있다가 true 반환

 

::: 두인자는 리스트여서 xs ::: ys 는 xs의 모든 원소 뒤에 ys의 모든 원소가 오는 새로운 리스트

 

 

종류메소드

리스트 길이 x.length
리스트 양끝에 접근 init, last
리스트 뒤집기 reverse
접두사, 접미사 drop, take, splitAt
리스트 선택 apply, indices
리스트 펼치기 flatten
리스트 순서쌓 묶기 zip, unzip
리스트 출력 toString, mkString
리스트 변환 iterator, toArray, copyToArray
   
리스트 매핑 map, flatmap, foreach
리스트 filter filter, partition, find, takeWhile, dropWile, span
리스트 전체 술어 forall, exists
리스트 폴드 /:, :\
리스트 정렬 sortWith

 

종류e메소드

리스트 만들기 List.apply
범위 리스트 만들기 List.range
균일한 리스트 List.fill
도표화 List.tabulate
여러 리스트 연결 List.concat
   
   
   
   

 

 

아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

abstract class Expr

case class Var(name: String) extends Expr

case class Number(num: Double) extends Expr

case class UnOp(operator: String, arg: Expr) extends Expr

case class BinOp(operator: String, left: Expr, right: Expr) extends Expr

 

 

def simplifyTop(expr: Expr) : Expr = expr match {

    case UnOp("-", UnOp("=", e) => e

    case BinOp("+", e, Number(0)) => e

    case BinOp("*", e, Number(1)) => e

    case _ => expr

}

 

 

match는 자바의 switch와 비슷

스칼라의 매치는 표현식으로 그 식은 결과 값을 내놓는다.

스칼라 대안 표현식은 다음 케이스로 빠지지 않는다

매치에 실패하면 에러 발생

 

상수 패턴 : contant pattern

변수만을 사용한 패턴 : variable pattern

와일드카드 패턴 : wildcard pattern

생성자 패턴 : constructor pattern

 

와일드카드 패턴

(_)는 어떤 객체라도 매치할수 있다

 

expr match {

    case BinOp(op, left, right) =>

        println(expr + " is a binary operation")

    case _ => // 디폴트 처리

}

 

 

상수패턴

상수패턴은 자신과 똑같은 값과 매치된다.

 

 

def describe(x: Any) = x match {

    case 5 => "five"

    case true => "truth"

    case "hello" => "hi!"

    case Nil => "the empty list"

    case _ => "something else"

}

 

 

변수패턴

와일드 카드 처럼 어떤 객체와도 매칭된다.

와일드 카드와 다른 점은 변수에 객체를 바인딩한다.

 

 

expr match {

    case 0 => "zero"

    case somethingElse => "not zero: " + somethingElse

}

 

 

 

생성자패턴

BinOp("+", e, Number(0))인 형태로 이름 다음에 괄호로 둘러쌓인 여러 패턴인 "+", e, Number(0) 이 왔다.

인자로 전달받은 것이 괄호안의 패턴과 정확히 일치하는지 매치

 

 

expr match {

    case BinOp("+", e, Number(0)) => println("a deep match")

    case _ =>

}

 

 

시퀀스 패턴

expr match {

    case List(0, _, _) => println("found it")

    case _ =>

}

 

 

튜플 패턴

def tupleDemo(expr: Any) {

    expr match {

        case (a, b, c) => println("matched" + a + b + c)

        case _ =>

    }

}

 

 

타입지정 패턴

def generalSize(x: Any) = x match {

    case s: String => s.length

    case m: Map[_, _] => m.size

    case _ =>

}

 

어떤 표현이 string 타입인지

expr.isInstanceOf[String]

 

 

동일한 표현식을 문자열 타입으로 반환

expr.asInstanceOf[String]

 

 

타입소거의 유일한 예외는 배열임.

 

 

변수바인딩

변수가 하나만 있는 패턴 말고, 다른 패턴에 변수를 추가도 가능

expr match {

    case UnOp("abs", e @ UnOp("abs", _)) => e

    case _ =>

}

 

 

패턴 가드

패턴 뒤에 오고 if로 시작한다.

 

 

def simplifyAdd(e: Expr) = e match {

    case BinOp("+", x, y) if x == y => BinOp("+", x, Number)

    case _ =>

}

 

봉인된 클래스 : sealed case classed

match 마지막에 디폰트를 넣지만 이는 합리적인 디폴트 동작이 있을 때만이고, 

그런 동작이 없다면, 어떻게 하면 모든 가능성을 다 처리했다고 안심할까?

match 식에서 놓친 패턴 조합이 있다면 컴파일러에게 찾도록 도움 요청

 

케이스의 클래스의 슈퍼 클래스를 봉인된 케이스로 만드는 것

그 클래스와 같은 파일이 아닌 다른 곳에서 새로운 서브 클래스를 만들수 없다.

sealed abstracrt class Expr

 

Option 타입

있으면 Some(x) 형태로 값이 있고 없으면 None 반환

Option[String]

 

 

부분함수

val second: PartialFuncrtion[List[Int}, Int} = {

    case x :: y :: _ => y

}

 

 

new PartialFunctin[List[Int], Int] {

    def apply(xs: List[Int] = xs match {

        case x :: y :: _ => y

    }

    def isDefinedAt(xs: List[Int]) = xs match {

        case x :: y :: _ => true

        case _ => false

    }  

}

어떠한 리터럴의 타입이 PartialFunction이면 이 변환을 수행

하지만, Function이거나 타입 표기가 없으면 완전한 함수로 변환

 

 

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH17. 컬렉션  (0) 2020.03.29
Scala CH16. 리스트  (0) 2020.03.29
Scala CH14. 단언문과 테스트  (0) 2020.03.29
Scala CH13. 패키지와 임포트  (0) 2020.03.29
Scala CH12. 트레이트  (0) 2020.03.29

+ Recent posts