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

 

assert( 1 == 1)

if () 

else {

 } ensuring ( a< 10)

 

JVM - ea -da 옵션 으로 assert, ensuring 켜거나 끌 수 있다

 

스칼라테스트 : ScalaTest, Specs2, ScalaCheck

 

suite : 스칼라 테스트의 중심개념, 테스트는 시작해서 성공하거나 실패하거나, 계속 결과를 기다리거나, 취소될 수 있는 이름 어떤 것이거나

트레이트 suite

 

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

 

프로그램의 의존도를 줄이는 디커플링, 모듈화

모듈에는 내부와 외부, 

 

패키징

패키징 + 중괄호

 

한 패키지 안에 여러 패키지

* 대신 _

import package._

 

코드 어디에서 import 가능

 

스칼라의 유연한 import

1. 코드 중간이든 어느 곳에서나 사용할 수 있다

2. 패키지 뿐만 아니라, 싱글톤 또는 일반 객체도 참조할 수 있다

3. 불러온 멤버 이름을 숨기거나 다른 이름을 지정할 수 있다

 

import Fruits.{Apple, Orange}

 

import Fruits.{Apple => McIntosh, Orange}

 

import Fruits.{_}

 

import Fruits.{ Apple => McIntosh, _}

모든 객체 불러오고 rename

 

import Fruits.{Pear => _, _}

Pear 를 제외한 모든 멤버를 불러온다

 

이름을 => _ 로 하는건 이름을 숨긴다는 의미

 

 

암시적 모든 임포트

 

import java.lang._

import scala._

import Predef._

스칼라 내부의 암시적 변환

 

private : 

내부 inner 접근 안됨

 

protected:

서브 클래스에서만 그 멤버에 접근

 

private[X] 접근 제어 지정

 

private[mypackage] 

 

mypackage 내에서 접근 가능

 

private 보다 더 제한적인 접근 제어

private[this] 객체 비공개

 

자바에서 정적 멤버와 인스턴스 멤버는 동일한 클래스

스칼라에는 정적 멤버가 없음

 

여러 멤버를 포함하며 단 하나만 존재하는 동반 객체 companion object 존재

스칼라의 접근 규칙은 비공 개 또는 보호 접근에 대해 동반 객체와 클래스에 동일한 권리를 준다

동반 객체에 보호 멤버를 선언하는 것도 말이 안된다

 

 

패키지 전체 스코프에 도우미 메소드를 만들고 싶으면

패키지 최상위 수준에 넣고 패키지 객체를 만들고 메소드 정의

package object bobsdelgith {

}

패키지 객체를 사용하는 다른 용도는

패키지 내에서 사용할 타입 별명과 암시적 변환 을 넣기 위해

패키지 객체는 package.class로 컴파일 되는데

패키지 클래스와 대응되는 패키지 디렉토리에 들어감

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

 

트레이트는 코드 재사용의 근간을 이루는 단위

트레이트로 메소드와 필드 정의를 캡슐화 하면 트레이트를 조합한 클래스에서 그 메소드나 필드를 재사용할 수 있다.

트레이트는 mix in 이 가능

  1. 간결한 인터페이스를 확장해 rich interface를 만드는 방법
  2. 쌓을 수 있는 변경을 정의 (stackable modification)

 

 

trait Phiosophical {

    def philosophize() = {

        println("I consume memory, therefor I am")

    }

}

 

 

class Animal

trait HasLegs

 

 

class Frog extends Animal with Philosophical with HasLegs {

    override def toString = "green"

}

 

트레이트는 클래스 파라메터를 가질 수 없다.

클래스는 super 호출을 정적으로 바인딩하지만 트레이트에서는 동적으로 바인딩 한다.

super.toString을 어떤 클래스에서 사용하면, 어떤 메소드 구현을 호출할지 정확히 알 수 있다.

하지만, 트레이느에서 super.toString을 작성해도 정의하는 시점에는 super가 호출할 실제 메소드 구현을 알 수 없다.

호출할 메소드는 트레이트를 mix in 할때마다 새로 정해진다. 이를 stackable modification

 

방법 1 풍부한 인터페이스

트레이트를 이용해 인터페이스를 풍부하게 만들고 싶다면, 트레이트에 간결한 인터페이스 역할을 하는 추상 메소드를 구현하고

풍부한 인터페이스 역할을 할 어려 메소드를 추상 메소드를 사용해 같은 트레이트 안에 구현하면 된다.

풍부해진 트레이트를 클래스에 믹스인 하고, 추상 메소드로 지정한 갈견한 인터페이스만 구현하면, 

결국 풍부한 인터페잇 구현을 모두 포함한 클래스를 완성할 수 있다

인터페이스에 디폴트 메소드 넣듯이...

 

방법 2. 변경 쌓아 올리기

 

abstract class IntQueue {

    def get(): Int

    def put(x: Int)

}

 

 

import scala.collection.mutable.ArrayBuffer

 

 

class BasiccIntQueue extends IntQueue {

    private val buf = new ArrayBuffer[Int]

    def get() = buf.remove(0)

    def put(x: Int) = {  buf += x }

}

 

 

trait Doubling extends IntQueue {

    abstract override def put(x: Int) = { super.put(2 * x) }

}

첫째는 슈퍼 클래스로 IntQueue를 선언한다는 것

이 선언은 Doubling 트레이트가 IntQueue를 상속한 클래스에만 믹스인 될 수 있음

두번째는 트레이트의 추상 메소드가 super를 호출한다는 점

일반적인 클래스는 이런식 호출은 실행시점에 호출 실패

하지만 트레이트에서는 호출을 동적으로 바인딩해서 Doubling을 put을 제공하는 트레이트나 클래스에 믹스인한다면 그때 해당 super.put 호출

 

믹스인 순서가 중요

가장 오른쪽의 트레이트를 먼저 호출

 

케이크 패턴 찾아보기

 

트레이트를 통한 다중 상속된 클래스 트레이트를 선형화 하여 차례로 super를 호출 및 누적

 

트레이트 or 추상클래스 가이드 라인

  1. 재사용할게 아니면 클래스
  2. 서로 관련이 없는 클래스에서 어떤 행위를 여러번 재사용하면, 트레이트
  3. 스칼라에서 정의하 내용을 자바 코드에서 상속, 추상 클래스
  4. 바이너리로 배포 및 누가 상속할거면, 추상 클래스
  5. 판단이 서지 않으면, 트레이트
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

optional

Any 모든 클래스의 슈퍼 클래스, Nill, Nothing 모든 클래스의 서브 클래스

 

AnyVal, AnyRef

 

AnyVal : Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit

op : +, *, ||, &&

 

 

def error(message: String): Nothing =

    throw new RuntimeException(message)

 

 

def divide(x: Int, y: Int): Int =

    if (y != 0) x / y

    else error("can't devide by zero")

 

 

Nothing이 Int의 subtype이므로 전체 조건식의 return type Int에 합치

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH13. 패키지와 임포트  (0) 2020.03.29
Scala CH12. 트레이트  (0) 2020.03.29
Scala CH10. 상속과 구성  (0) 2020.03.29
Scala CH08. 함수와 클로저  (0) 2020.03.29
Scala CH07. 내장 제어 구문  (0) 2020.03.29
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

콤비네이터

 

메소드의 경우 구현이 없으면 추상 메소드다

 

클래스는 추상 메소드를 선언한다 ( not 정의)

 

파라메터 없는 메소드

빈 괄호나 아에 괄호가 없는 메소드

필드나 메소드 중 어떤 방식의로 속성을 정의하더라도 클라이언트 코드에는 영향을 끼치지 말아야 한다는 원칙 => 단일 접근 원칙

 

abstract class Element {

 

 

    def contents: Array[String]

    def height: Int = contents.length

    def width: Int = if (height == 0) 0 else contents(0).length

 

 

 

 

}

 

 

abstract class Element1 {

    def contents : Array[String]

    val height = contents.length

    var width = if (height == 0) 0 else contents(0).length

}

 

 

-- 필드로 할 경우 클래스 초기화 시 값이 미리 계산됨.

 

스칼라에서는 파라메터 없는 메소드와 빈 괄호 메소드를 자유롭게 섞어 쓸 수 있게 한다.

메소드 오보라이드 용이

호출하는 함수가 어떠한 작업을 수행한다면 빈 괄호를 사용하라

프로퍼티에 대한 접근만 수행한다면 괄호를 생략해라

 

- 클래스 확장

비공개가 아닌 부모의 멤버를 모두 물려받는다

서브타입으로 만든다

서브 클래스 라고 한다

부모는 수퍼 클래스

생략하면 scala.AnyRef 상속함.

 

오버라이드 적용됨.

 

자바에 4개의 네임 스페이스 : 필드, 메소드, 타입, 패키지

스칼라에 2개의 네임 스페이스 : 값 (필드, 메소드, 패키지, 싱글톤 객체), 타입 (클래스와 트레이트 이름)

스칼라가 필드와 메소드를 동일한 네임스페이스로 취급하는 우이유는 정확히 파라미터 없는 메소드를 val로 오버라이드 하기 위해서 이다

 

- 파라메터 필드

class ArrayElement (

    val contents: Array[String]

    // 동일한 필드와 파라메터를 동시에 정의하는 단축표기

) extends Element

 

 

class Cat {

    var dangerous = false

}

 

 

class Tiger (

    override val dangerous: Boolean,

    private val age: Int

) extends Cat

 

- 슈퍼 클래스의 생성자 호출

class LineElement(s: String) extends ArrayElement(Array(s)) {

    override def width = s.length

    override def height = 1

}

 

 

// override def hight = 1

// 컴파일 오류

 

추상 멤버를 구현한 경우에는 override 생략 가능

우연한 오버라이드는 깨지기 쉬운 기반 클래스 라고 불리는 문제의 가장 흔한 사례임.

클래스 계층에서 기반 클래스 (슈퍼 클래스) 에 추가한 멤버로 인해 클라이언트 코드가 깨지는 위험

상위에서 정의 되어 있는 것을 하위에서 override 없이 적기만 해도 컴파일 오류 남. 즉 사전에 오작동 방지 상위 메소드에서 추가하는 경우

 

- 다형성과 동적 바인딩

Element 타입의 변수가 하위 ArrayElement 타입의 객체를 참고 가능 : 서브 타입 다형성

 

val e: Element = new ArrayElement(Array("hello", "world"))

 

 

class UniformElement (

    ch: Char,

    override val width: Int,

    override val height: Int

) extends Element {

    private val line = ch.toString * width

    def contents = Array.fill(height)(line)

}

 

변수나 표현식에 대한 메소드 호출을 동적으로 바인딩

upcating 되더라도 실제 ref class의 오버라이딩 메소드가 호출됨.

 

- final 멤버 선언

override 못하게 선언

class와 member에 적용 가능

 

- 상속과 구성 사용

상속 is a 관계

 

- above, beside. toString 구현

abstract class Element {

 

    def contents: Array[String]

     

    def width: Int = if (height == 0) 0 else contents(0).length

     

    def height: Int = contents.length

 

 

    def above(that: Element): Element =

        new ArrayElement(this.contents ++ that.contents)

 

 

    def beside(that: Element): Element =

        new ArrayElement(

            for (

                (line1, line2) <- this.contents zip that.contents

            ) yield line1 + line2

        )

     

    override def toString = contents mkString "\n"

}

- 팩토리 객체 정의

팩토리 객체는 다른 객체를 생성하는 메소드를 제공하는 객체이다

object Element {

     

    private Class ArrayElement (

        val contents: Array[String]

    ) extends Element

 

 

    private class LineElement(s: String) extends Element {

        val contents = Array(s)

        override def width = s.length

        override def height = 1

    }

 

 

    private class UniformElement (

        ch: Char,

        override val width: Int,

        override val height: Int

    ) extends Element {

        private val line = ch.toString * width

        def contents = Array.fill(height)(line)

    }

 

 

    def elem(contents: Array[String]) : Element =

        new ArrayElement(contents)

 

 

    def elem(chr: Char, width: Int, height: Int) : Element =

        new UniformElement(chr, width, height)

     

    def elem(line: String) : Element =

        new LineElement(line)

}

 

 

import Element.elem

 

 

abstract class Element {

    def contents: Array[String]

     

    def width: Int =

        if (height ==0) 0 else contents(0).length

 

 

    def height: Int = contents.length

 

 

    def above(that: Element): Element = {

        val this1 = this widen that.widen

        val that2 = that widen this.width

        elem(this.contents ++ that.contents)

    }

 

    def beside(that: Element): Element = {

        val this1 = this heighten that.height

        val that1 = that heighten this.height

        elem(

            for (

                (line1, line2) <- this.contents zip that.contents

            ) yield line1 + line2

        )

    }

     

    def widen(w: Int): Element =

        if (w <= width) this

        else {

            val left = elem(' ', (w - width) / 2, height)

            var right = elem(' ', w - width - left.width, height)

            left beside this beside right

        }

 

 

    def heighten(h: Int): Element =

        if (h <= height) this

        else {

            var top = elem(' ', width, (h - height) / 2

            var bot = elem(' ' , width, h - height - top.height)

            top above this above bot

        }

 

    override def toString = contents mkString "\n"

}

 

 

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH12. 트레이트  (0) 2020.03.29
Scala CH11. 스칼라의 계층구조  (0) 2020.03.29
Scala CH08. 함수와 클로저  (0) 2020.03.29
Scala CH07. 내장 제어 구문  (0) 2020.03.29
Scala CH06. 함수형 객체  (0) 2020.03.29
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

optional

함수 정의 방법

  1. 메소드
  2. 내포함수
  3. 함수 리터럴
  4. 함수 값

 

1. 메소드

특정 객체의 멤버로 함수를 만드는 것

 

2. 지역 함수

빌딩 블록

대부분 자바에서는 private 메소드를 이용해 목적 달성

스칼라에서는 함수 안에 함수 정의

중첩과 스코프는 함수를 포함한 모든 스칼라 구문에 적용할 수 있음.

 

3. 1급 계층 함수

함수 정의 호출 리터럴로 표기해 값으로 주고 받을 수 있음

해당 클래스를 실행 시점에 인스턴스 화 하면 함수 값이 된다

모든 함수 값은 FunctionN 트리에트 중 하나를 확장하여 얻은 인스턴스

Function0 인자가 없는 함수

Function1 인자가 1개인 함수

둘 이상의 라인은 블록으로 감싸면 되다

 

4. 함수 리터럴

( x ) => x + 1

x => x + 1

x => x + 1

_ > 1

 

(_ : Int) + ( _ : Int)

 

someNumbers.foreach( println _ )

이 경우는 하나의 인자에 대한 위치 표시자가 아니라 전체 인자 목록에 대한 위치 표시자

4. 부분 적용 함수

def sum (a : Int, b : Int, c : Int) = a + b + c

sum(1, 2, 3)

val a = sum _

a(1, 2, 3)  

a.apply(1, 2, 3)

 

sum _ : 부분 적용 함수 : 함수를 적용할 때 인자를 전부 넘기지 않았음.

val b = sum(1, _ : Int, 3)

b (5 ) 

sum (1, 5, 3)

 

동일 결과

 

someNumbers.foreach( x => println(x))

 

5. 클로저

(x : Int ) => x + 1

보다

(x : Int ) = > x + more

more : 자유변수

x : 바운드 변수

클로저 라는 이름은 함수 리터럴의 본문에 있는 모든 자유 변수에 대한 바인딩 을 포획해서 자유 변수가 없게 닫는 (closing) 행위에서 따온 말이다

closed term : x +1 

open term : x + more

more의 바인딩을 capture 해야함.

그렇게 만들어진 함수에는 캡처한 more 변수에 대한 참조가 들어 있음.

스칼라에서는 클로저가 변화를 감지한다.

 

포획한 인자가 스택이 아닌 힙에 있기 때문에 메소드보다 더 올래 살아남음

 

 

6. 반복 파라메터, 이름 붙인 인자, 디폴트 인자

반복 파라메터 repeated parameter : 길이가 변하는 인자 목록 : args : String *

배열을 반복 인자로 전달 : echo ( arr : _ * )

이름 붙인 인자 

순서와 관계없이 이름에 매핑

디폴트 인자 값

파라미터의 디폴트 (out : java.io.PrintStream = Console.out )

 

7. 꼬리 재귀

재귀가 stack 호출 때문에 느려질수 있지만 꼬리 재귀는 goto로 연결되어 성능적인 degradation이 없음.

재귀 형태가 좀 더 직관적인 코드일 경우 사용하는 것 추천

꼬리 재귀는 재구 호출 때 마다 스택을 만들지 않고 같은 스택 프레임ㅇ르 재활용함.

스칼라는 동일한 함수를 직접 재귀 호출하는 경우에만 최적화를 수행한다.

 

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH11. 스칼라의 계층구조  (0) 2020.03.29
Scala CH10. 상속과 구성  (0) 2020.03.29
Scala CH07. 내장 제어 구문  (0) 2020.03.29
Scala CH06. 함수형 객체  (0) 2020.03.29
Scala CH05. 기본 타입과 연산  (0) 2020.03.29
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

optional

if, while, for, try, match, 함수 호출

 

if : 값 리턴 가능

 

val filename =

if (!args.isEmpty) args(0) else "default.txt"

 

루프의 결과 타입은 Unit

유닛 값 = ()

 

while, do while

 

for 표현식

컬렉션 이터레이션

 

필터링

 

for {

file ← fileList

if file.isFile

if file.getName.endsWith(".scala")

} println(file)

 

중첩 이터레이션

 

for {

file ← fileList

if file.getName.endsWith(".scala")

line ← fileLines(file)

if line.trim.matches(pattern)

} println(file + ":" + line)

 

for 중에 변수 바인딩

 

for {

file < - fileList

line < - lineList(file)

trimmedLine = line.trim

if trimmedLine.matched(pattern)

} println(line)

 

새로운 컬렉션

def scalaFileList = 

for {

file < - fileList

if file.getName.endsWith(".scala")

} yield file

 

try 표현식으로 예외 다루기

예외 발생 : throw new IllegalArgumentException

throw의 타입은 Nothing 이라는 타입

try catch finally 도 결과는 값

 

try  안의 값이 최종 값

finally는 예외처리 과정

 

match 표현식

val firstArg = if (args.length > 0) args(0) else ""

 

firstArg match {

case "salt" => println("p1")

case "chips" => println('"p2")

case _ => println("default")

}

 

자바와 다른 점 : break 없음.

상수는 다 사용 가능, 즉 문자열도 됨.

 

스칼라에서는 break와 continue 제외

 

break 유사 : scala.util.control.Break 클래스의 break 메소드 : breakable지정한 곳으로 이동

 

변수 : 지역변수

자바와 다르게 동일 변수 하위 스코프에서 중복 정의 가능, 단 상위 스콥 변수는 shadow 됨

 

object PrintMultiTable extends App {

  def printMultiTable() = {

    var i = 1

    while (i <= 10) {

      var j = 1

      while (j <= 10) {

        var prod = (i * j).toString

        var k = prod.length

        while (k < 4) {

          print(" ")

          k += 1

        }

        print(prod)

        j += 1

      }

      println()

      i += 1

    }

  }

  printMultiTable()

 

  def makeRowSeq(row:Int) =

    for (col <- 1 to 10) yield {

      val prod = (row * col).toString

      var padding = " " * (4 - prod.length)

      padding + prod

    }

  def makeRow(row: Int) = makeRowSeq(row).mkString

 

  def multiTable = {

    val tableSeq =

      for (row <- 1 to 10)

        yield makeRow(row)

    tableSeq.mkString("\n")

  }

  println(multiTable)

}

 

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH10. 상속과 구성  (0) 2020.03.29
Scala CH08. 함수와 클로저  (0) 2020.03.29
Scala CH06. 함수형 객체  (0) 2020.03.29
Scala CH05. 기본 타입과 연산  (0) 2020.03.29
Scala CH04. 클래스와 객체  (0) 2020.03.29
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

optional

스칼라는 클래스가 인자를 바로 받는다. 필드 정의 및 할당이 간소화 됨.

 

변경 불가능한 객제의 장점 :

  1. 추론이 쉽다.
  2. 전달을 자유롭게 한다.
  3. 동시성에 제약이 적다
  4. 안정한 해시 테이블 키다

단점
1.객제 변경을 위해서는 객제 복사 필요

2. 성능 이슈가 발생할 수 있다

 

클래스 주 생성자 는 모든 진입점

클래스 보조 생성자 this(...)

주 생성자만이 슈퍼 클래스의 생성자 호출 가능

 

스칼라 식별자

시작은 문자나 _

그 다음은 숫자 문자, 밑줄 모두 가능

 

스칼라도 camel case 적용

 

클래스나 트레이트는 대문자로 시작

 

상수 : 대문자 및  ㅡ 사용

 

연산자를 메소드로 정의

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

optional

String 제외하고 scala 패키지의 멤버

type
range

String java.lang.String Char sequence
Byte scala.Byte 8bit
Short scala.Short 16bit
Int scala.Int 32bit
Long scala.Long 64bit
Char scala.Char 16bit
Float scala.Float 32bit
Double scala.Double 64bit
Boolean scala.Boolean  

 

심볼 리터럴

 'ident = Symbol("ident")

불리언 리터럴

 

String interpolation

s"Hello, $name"

$기호 뒤에 원하는 표현식 입력

s"The answer is $(6 * 7)"

raw, f 두가지 추가 인터폴레이터 제공

f"$(math.PI)%.5f"

 

연산자는 메소드다

+ infix operator

모든 메소드는 연산자가 될 수 있다

스칼라는 prefix, postfix 연산자도 제공

전위나 후위 연산자는 단항 unary operator 임

전위 연산자로 쓰일수 았는 식별자는 +, -, !, ~ 4 개이다

후위 연산자

s.toLowerCase

 

산술연산 : +, -, *, /, %

관계연산 : <, >, <=, >=

논리연산 : 논리곱 &&, &, 논리합 ||, |

 

&& : short circuit

비트연산 : &, |, ^, ~

>>, <<

>>> 부호없는 오른쪽 시프트

 

객체동일성

=, !=

양쪽이 null이 아니면 equals 호출

==  : 자바 원시 타입 값 비고, 객체 : 참조 비교

eq : 스칼라 원시 타입 값 비고, 객체 : 참조 비교

= 할당 연산 가장 우선 순위가 낮다

 

풍부한 래퍼

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

 

스칼라 접근 수준은 전체공개
결과 타입 unit인 메소드는 프로시져

문장 끝 세미콜론 생략
줄의 시작보다는 줄의 끝에 연산자 배치

스칼라 클래스에는 static이 없다
스칼라는 싱글톤 객체 사용

싱글톤은 object로 시작

자바 객체와 스칼라 싱글톤 다른이유
싱글톤은 파라메터못 받음
싱글톤 클래스는 new 인스턴스가 안됨

싱글톤 객체를 합성한 클래스의 인스턴스 구현학이를 정적 변수가 참조어떤 싱글통 객체와 같을 때
이때 이 객체를 동반객체 라고 한다

독립 싱글톤 객체
독립 객체동반 객체 없는 경우 독립객체

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

 

파라메터화 ( parameterization ) 는 인스턴스를 생성할 때 그 인스턴스를 설정한다는 의미

val bigValue = new BigInteger("12345);

인스턴스화 ("12345" 라는 String으로 )

 

val 

어떤 변수를 val 로 지정하면, 그 변수를 재 할당 할 수 없다.

하지만 그 변수가 나타내는 객체는 잠재적으로 여전히 변경 가능하다

val arrayList = new Array[String](3);

arrayList = new Array[String](4); // 컴파일 에러

arrayList(0) = "aaa";

arrayList(0) = "bbb";

 

메소드가 파라미터 하나만 요구하는 경우, 그 메소드를 점(.) 과 괄호 없이 호출 할 수 있다.

 

배열 자체는 못 변경해도 원소는 변경 가능하다

 

0 to 2 ( (0).to(2)

Console println 10

호출 대상 객체를 명시적으로 지정할 때만 이런 문법을 사용할 수 있음.

 

스칼라에서는 기술적으로 연산자 오버로드를 제공하지 않는다.

스칼라에는 실제로 전통적인 의미의 연산자가 없다.

1 + 2

는 (1).+(2) 로 된 것임.

 

배열접근

arrayList(1)

arrayList.apply(i) 로 변경됨

즉 배열에 대한 접근도 메소드에 대한 접근

arrayList(1) = "Hello"

 

arrayList.update(0, "Hello")

 

::: list의 merge

 

콘즈 : cons

연산자 ::

연산자의 왼쪽으로 작용함.

 

빈리스트 : Nil

 

튜플은 유용한 객체

변경불가능, 다른 타입의 원소를 입력 가능

여러 반환 값을 담기에 유용

튜플은 Tuple22번째 까지만 지원

 

collection map : trait

sub straint : 변경가능 trait , 변경 불가 trait

train 는 인터페이스와 비슷하다

스칼라에서는 트레이트를 확장 혹은 mix in

'독서관련 > Programming in Scala' 카테고리의 다른 글

Scala CH07. 내장 제어 구문  (0) 2020.03.29
Scala CH06. 함수형 객체  (0) 2020.03.29
Scala CH05. 기본 타입과 연산  (0) 2020.03.29
Scala CH04. 클래스와 객체  (0) 2020.03.29
Scala CH01. 확장 가능한 언어  (0) 2020.03.29
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e
』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

optional

 

  • scala
    1. scalable
    2. object oriented
    3. functional
    4. statically typed language
    5. library abstraction
    6. trait : pluggable like interface ( 하지만 메소드 정의 가능 (java 8 디폴트 메서드), 필드도 정의 가능)
    7. mixin : like multiple inheritance
    8. function literal

 

언어를 사용자가 필요한 방향으로 고칠수 있게 허용

함수 값도 객체다

함수 타입은 서브클래스가 상속할 수 있는 클래스다

함수와 객체의 통합

순수한 객체 지향 언어 (모든 값은 객체이며, 모든 연산은 메소드 호출)

스칼라는 함수형이다

함수형 프로그래밍의 기초는 lambda 계산법

OOP 관점에서 

객체 : 데이터와 연산

객체의 미결 측면

객체가 아닌 값 존재 : primative type

어떤 객체의 멤버도 아닌 필드, 메소드 : static filed and method

 

함수형 프로그래밍 조건

  1. 일급함수

함수 : first class 즉 정수나 문자열 같음

함수를 다른 함수에 인자로 넘기거나, 

함수의 결과를 함수로, 

함수를 변수에 저장

다른 함수의 내부에서 함수 정의

함수를 1급 계층으로 만들면, 한수가 내부에서 함수를 반환하면, 그 함수 값 안에서 모든 변수를 단은 환경을 저장해 두어야 문제가 생기지 않는다. 이 처리를 위해서 클로저

2. 변경 불가능 immutable 데이터, 메소드 side effect 가 없어야 함, 입력값 함수 출력값, referentially transparent

메소드는 인자를 받아서 결과를 반환하는 방식에서 새로운 값을 반환하는 것 이외에는 다른 영향을 시스템에 주지 않아야 함.

주어진 입력에 대해서 영향을 주지 않음.

s.replace

 

함수를 일반화 하면 표현력이 늘어남

 

 

scala가 프로그래밍 언어로 좋은 이유

  1. 스칼라는 호환성이 좋다.
    1. 스칼라는 jvm의 바이트 코드로 컴파일 된다
    2. 스칼라 코드는 자바 메소드를 호출할 수 있고, 자바 필드에 접근할 수 있고, 자바 클래스 상속하거나, 자바 인터페이스를 구현할 수 있다
    3. 대부분의 스칼라 코드는 자바 라이브러리를 사용함.
  2. 스칼라는 간결하다
    1. 코드수가 자바대비 절반정도
    2. class Myclass(index : Int, name : String) : 클래스 선언, private 2개 선언, 2개까지 생성자 선언 완료 : 간결함
  3. 스칼라는 고수준이다
    1. 저차원 (데이터 직접 접근)이 아닌 고차원 질의 에 의한 처리
    2. val nameHasUpperCase = name.exists(_.isUpper)
    3. function literal : _.isUppper
  4. 스칼라는 정적타입언어이다
    1. 제네릭, 교집합, 추상타입
    2. 정적타입 시스템 이지만, 타입 추론, 패턴매치, 타입합성
    3. 프로퍼티 검증 - 타입 테스트 코드
    4. 안전한 리팩토링 - 변경하면 컴파일 안되는 부분 표시
    5. 문서화 

 

타입이란 : 프로그램에서 어떤 변수 등이 취할 수 있는 값의 범위와 그 변수에 적용할 수 있는 연산의 종류를 정하고, 컴파일러나 인터프리터가 이를 미리 검증해주는 것

자바와 차이나는 점 : 자바는 변수 선언형태 (타입 변수 String aaa)인데 스칼라는 (변수 타입 aaa String) 임, 변수 타입의 형태는 타입 추론에 유리하며, 생략하기가 쉬움.

 

 

+ Recent posts