This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| language:scala [2021/02/01 19:18] – removed ledyx | language:scala [2021/03/24 08:18] (current) – [Command Line] 프로젝트 생성/배포 내용 보강 ledyx | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | = Scala = | ||
| + | |||
| + | * https:// | ||
| + | * 기본적으로 Java를 다뤄봤다는 전제하에 실용적인 측면으로만 기술. | ||
| + | * 2.12 이상 버전을 기준으로 기술. | ||
| + | |||
| + | {{tag> | ||
| + | |||
| + | = 환경 설정 = | ||
| + | * JDK 1.8 이상 설치 | ||
| + | |||
| + | == Command Line == | ||
| + | https:// | ||
| + | |||
| + | === SBT (Scala Build Tools) === | ||
| + | Scala의 Ant/ | ||
| + | |||
| + | ==== Create templates ==== | ||
| + | |||
| + | https:// | ||
| + | |||
| + | <sxh shell> | ||
| + | sbt new scala/ | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Fat Jar ==== | ||
| + | |||
| + | https:// | ||
| + | |||
| + | <sxh shell> | ||
| + | # Build (Fat Jar 생성) | ||
| + | ## build.sbt에 | ||
| + | ## addSbtPlugin(" | ||
| + | ## 추가 필요 | ||
| + | ### 생성된 jar 위치 : target/ | ||
| + | sbt assembly | ||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | === Scala Binaries (선택 사항) === | ||
| + | * REPL(scala), | ||
| + | |||
| + | <sxh shell> | ||
| + | # 실행 | ||
| + | scala | ||
| + | |||
| + | scala> 1 | ||
| + | res0: Int = 1 | ||
| + | |||
| + | # 종료 | ||
| + | scala> :quit | ||
| + | ## 혹은 | ||
| + | scala> :q | ||
| + | </ | ||
| + | |||
| + | |||
| + | == IDE == | ||
| + | - Intellij에 기본적으로 SBT 내장. Intellij만 설치하면 된다. | ||
| + | |||
| + | === REPL 실행 방법 === | ||
| + | 왼쪽 Project Tree에서 아무 Class에 오른쪽으로 누르고 "Run Scala Console" | ||
| + | = Hello, world! = | ||
| + | |||
| + | * 가변 인자 args를 입력 받아 " | ||
| + | |||
| + | == Script == | ||
| + | |||
| + | * Compile 하지 않고, Shell script 처럼 사용 | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | // class, main 함수를 선언하지 않고도 가변인자 args 사용 가능! | ||
| + | args.foreach(print) | ||
| + | println() | ||
| + | </ | ||
| + | |||
| + | <sxh bash> | ||
| + | > scala script.scala Hello , world ! | ||
| + | Hello, world! | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Compile == | ||
| + | |||
| + | https:// | ||
| + | |||
| + | |||
| + | === App trait 상속 O === | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | object HelloWorld extends App { | ||
| + | // class, main 함수를 선언하지 않고도 가변인자 args 사용 가능! | ||
| + | args.foreach(print) | ||
| + | println() | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <sxh shell> | ||
| + | > scalac HelloWorld.scala | ||
| + | > scala HelloWorld Hello , world ! | ||
| + | </ | ||
| + | |||
| + | |||
| + | === App trait 상속 X === | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | |||
| + | object HelloWorld { | ||
| + | def main(args: Array[String]): | ||
| + | args.foreach(print) | ||
| + | println() | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <sxh shell> | ||
| + | > scalac HelloWorld.scala | ||
| + | > scala HelloWorld Hello , world ! | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | = 문법 = | ||
| + | |||
| + | == Identifier == | ||
| + | 식별자 규칙 및 관례적 명명법 서술. | ||
| + | |||
| + | * 연산자 정의(Operator Defination) Method와 암시적 변환([[language: | ||
| + | |||
| + | |||
| + | === Alphanumeric Identifier === | ||
| + | |||
| + | # Java와 동일하게 영숫자 혹은 ' | ||
| + | # 상수는 첫글자만 대문자로, | ||
| + | |||
| + | === Operator Identifier === | ||
| + | |||
| + | * Java와 가장 큰 차이점 중 하나. 이를 이용하면 Operator Overloading이 가능. Method에만 적용 가능. | ||
| + | * +, :, ?, ~, #, +, ++, :: | ||
| + | |||
| + | |||
| + | * Overloading 예시 | ||
| + | |||
| + | <sxh scala> | ||
| + | def *** (v: Int) = math.pow(v, 3) | ||
| + | </ | ||
| + | |||
| + | ==== 연산자 우선순위와 결합법칙 ==== | ||
| + | |||
| + | **:**(Cons; Construct)로 끝나는 연산자는 right-associative. 이외 나머지는 left-associative | ||
| + | |||
| + | <sxh> | ||
| + | // left-associative | ||
| + | a * b * c | ||
| + | = (a * b) * c | ||
| + | = a.*(b).*(c) | ||
| + | |||
| + | // right-associative | ||
| + | a ::: b ::: c | ||
| + | = a ::: (b ::: c) | ||
| + | = b.::: | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Mixed Identifier === | ||
| + | |||
| + | Alphanumeric + Operator. 예를 들어 myvar_=. Scala Compiler가 Property를 지원하기 위해 내부적으로 생성. | ||
| + | |||
| + | |||
| + | === Literal Identifier === | ||
| + | |||
| + | 역따옴표(`)를 사용하며 Runtime에서 인식할 수 있는 문자열 생성. 이를 이용하면 예약어도 변수나 Method 이름으로 지정 가능. | ||
| + | |||
| + | <sxh scala> | ||
| + | // 아래 코드는 Compile 및 실행에 문제가 없다. | ||
| + | |||
| + | val `val` = 3; | ||
| + | def show(`var` : Int) = println(`var`); | ||
| + | |||
| + | show(`val`) | ||
| + | </ | ||
| + | |||
| + | == 변수 == | ||
| + | * val : Immutable | ||
| + | * var : mutable | ||
| + | |||
| + | |||
| + | |||
| + | == Type == | ||
| + | |||
| + | === Type Hierarchy === | ||
| + | |||
| + | {{https:// | ||
| + | |||
| + | |< | ||
| + | ^ Any | ||
| + | ^ AnyVal | ||
| + | ^ AnyRef | ||
| + | ^ Nothing |모든 type의 subtype (i.e. bottom type)으로 value를 가지고 있지 않다. | ||
| + | ^ Null |모든 reference type의 subtype. keyword literal ' | ||
| + | |||
| + | |||
| + | === Literals === | ||
| + | |||
| + | ==== Integer Literals ==== | ||
| + | |||
| + | * C/Java와 달리 8진(0으로 시작하는 정수형)을 지원하지 않음. 10진과 16진만 지원. | ||
| + | |||
| + | ==== String Literals ==== | ||
| + | |||
| + | ===== Raw Sring ===== | ||
| + | 큰따옴표 3개(< | ||
| + | |||
| + | <sxh scala> | ||
| + | object Test extends App { | ||
| + | // Java에서의 일부 특수문자 표현. 가독성이 좋지 않음. | ||
| + | val str1 = " | ||
| + | println(str1) | ||
| + | |||
| + | /** | ||
| + | * Hello | ||
| + | * " | ||
| + | */ | ||
| + | |||
| + | |||
| + | // 공백, 개행 모두 그래도 출력 | ||
| + | val str2 = | ||
| + | """ | ||
| + | " | ||
| + | """ | ||
| + | println(str2) | ||
| + | |||
| + | /** | ||
| + | * Hello | ||
| + | * " | ||
| + | */ | ||
| + | |||
| + | |||
| + | // |를 넣으면 문자열의 시작을 알림. 이후 stripMargin을 하면 이전 공백 제거 | ||
| + | val str3 = | ||
| + | """ | ||
| + | |" | ||
| + | """ | ||
| + | println(str3) | ||
| + | |||
| + | /** | ||
| + | * Hello | ||
| + | * " | ||
| + | */ | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== String interpolation ===== | ||
| + | 문자열 내부에 표현식(expression)을 내장시키는 것. 이로써 문자열을 이어붙이지 않고 가독성이 좋아짐. | ||
| + | * s : " | ||
| + | * raw : 문자열 그대로 출력. | ||
| + | * f : printf 형태의 형식 지정. | ||
| + | |||
| + | |||
| + | |< | ||
| + | ^ ^ Description | ||
| + | | s | ||
| + | val str = "9 * 9 = " | ||
| + | println(s" | ||
| + | /** | ||
| + | * 9 * 9 = 81 | ||
| + | */ | ||
| + | </ | ||
| + | | raw | ||
| + | println(raw" | ||
| + | |||
| + | /** | ||
| + | * A\nB\\C\tD\bE | ||
| + | */ | ||
| + | </ | ||
| + | | f | printf 형태의 형식 지정. \\ ${expression}%[format] | ||
| + | println(f" | ||
| + | println(f" | ||
| + | |||
| + | /** | ||
| + | * 3.1415927 | ||
| + | * 009 | ||
| + | */ | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Symbol literals ==== | ||
| + | |||
| + | * 작은 따옴표(' | ||
| + | * 단순 식별자가 필요할 때 사용. Java의 enum이나 [[language: | ||
| + | * Scala의 Strong Type 언어이기 때문에 Weak Type 형태로 사용할 때 사용. | ||
| + | * [[language: | ||
| + | * .name 필드로 문자열 변환 가능. | ||
| + | |||
| + | |||
| + | === Array === | ||
| + | |||
| + | * 모든 것을 Method가 있는 객체로 다루기 때문에 Parameter에 해당하는 호출은 **()**을 사용한다. | ||
| + | |||
| + | <sxh scala> | ||
| + | val fruits = new Array[String](2) | ||
| + | // val fruits: String = new Array[String](2) | ||
| + | |||
| + | fruits(0) = " | ||
| + | // fruites.update(0, | ||
| + | |||
| + | fruits(1) = " | ||
| + | // fruites.update(1, | ||
| + | </ | ||
| + | |||
| + | <sxh scala> | ||
| + | val fruits = Array(" | ||
| + | // val fruits = Array.apply(" | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Operator == | ||
| + | |||
| + | === Operator = Method! === | ||
| + | |||
| + | Scala에서 모든 연산자는 Method!! 실질적으로 Scala는 Method를 호출한다. | ||
| + | |||
| + | <sxh scala> | ||
| + | /** Binary Operator **/ | ||
| + | |||
| + | val sum = 1 + 2 | ||
| + | // 1.+(2) | ||
| + | |||
| + | " | ||
| + | // " | ||
| + | |||
| + | " | ||
| + | // " | ||
| + | |||
| + | |||
| + | |||
| + | /** Unary Operator **/ | ||
| + | |||
| + | /*** 전위 표기법 ***/ | ||
| + | // +,0,!,~ 4가지. | ||
| + | |||
| + | val a = -2.0 | ||
| + | // (2.0).unary_- | ||
| + | |||
| + | |||
| + | /*** 후위 표기법 ***/ | ||
| + | // 인자를 취하지 않는 Method를 ' | ||
| + | |||
| + | val str = " | ||
| + | // val str = " | ||
| + | </ | ||
| + | |||
| + | |||
| + | === == & eq === | ||
| + | Java와 동일하다. \\ < | ||
| + | |||
| + | <sxh scala> | ||
| + | scala> List(1, 2, 3) == List(1, 2, 3) | ||
| + | res8: Boolean = true | ||
| + | |||
| + | scala> List(1, 2, 3) eq List(1, 2, 3) | ||
| + | res9: Boolean = false | ||
| + | |||
| + | scala> List(1, 2, 3) ne List(1, 2, 3) | ||
| + | res10: Boolean = true | ||
| + | |||
| + | // 주의! | ||
| + | scala> (" | ||
| + | res11: Boolean = true | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Wrapper === | ||
| + | |||
| + | <sxh scala> | ||
| + | scala> 111 max 222 | ||
| + | res0: Int = 222 | ||
| + | |||
| + | scala> 111 min 222 | ||
| + | res1: Int = 111 | ||
| + | |||
| + | scala> -math.Pi abs | ||
| + | res2: Double = 3.141592653589793 | ||
| + | |||
| + | scala> -math.Pi round | ||
| + | res3: Long = -3 | ||
| + | |||
| + | scala> (1.0 / 0) isInfinity | ||
| + | res4: Boolean = true | ||
| + | |||
| + | scala> 1 to 100 | ||
| + | res5: scala.collection.immutable.Range.Inclusive = Range 1 to 100 | ||
| + | |||
| + | scala> " | ||
| + | res6: String = Hello | ||
| + | |||
| + | scala> " | ||
| + | res7: String = World | ||
| + | </ | ||
| + | |||
| + | |||
| + | == 내장 제어 구문 == | ||
| + | |||
| + | **전체적으로 재귀나 내장함수(reduce, | ||
| + | |||
| + | * 키워드로써 break, continue는 존재하지 않는다. (함수형 언어에 어울리지 않으므로.) | ||
| + | * 굳이 break를 쓰고 싶다면 scala.util.control.Breaks 에 있는 것을 사용할 수 있다...만... | ||
| + | * 제어 구문들도 함수이므로 var/val에 할당할 수 있다. (이 때, 기본적으로 바로 계산한다. lazy loading이 필요하면 lazy를 맨 앞에 붙이자.) | ||
| + | * while은 대체로 중간 결과를 저장하는 변수 var가 필요하기 때문에 사용을 자제하자. | ||
| + | |||
| + | |||
| + | === for === | ||
| + | |||
| + | foreach 문만 존재한다. 그러나 대체로 반복하는 경우 내장 함수(foreach, | ||
| + | |||
| + | <sxh scala> | ||
| + | /* Collections */ | ||
| + | |||
| + | //10 ~ 100 | ||
| + | for (i <- 10 to 100) | ||
| + | println(i) | ||
| + | | ||
| + | //10 ~ 99 (최대값 제외) | ||
| + | for (i <- 3 until 10) | ||
| + | println(i) | ||
| + | |||
| + | |||
| + | /* Iterator Guard / Filter. if 표현식이 true일 때만 반복 */ | ||
| + | for (file <- new java.io.File(" | ||
| + | if file.isFile | ||
| + | if file.getName.endsWith(" | ||
| + | println(file) | ||
| + | |||
| + | /* 중첩 및 임시 변수 Binding */ | ||
| + | // 구구단 출력 | ||
| + | for {x <- (1 to 9) | ||
| + | y <- (1 to 9) | ||
| + | | ||
| + | println(f" | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== for-yield ==== | ||
| + | |||
| + | < | ||
| + | |||
| + | <code scala> | ||
| + | for (< | ||
| + | </ | ||
| + | |||
| + | <sxh scala> | ||
| + | // 인자가 없기 때문에 ()를 생략한 형태. | ||
| + | def timesTable = for { | ||
| + | x <- (1 to 9) | ||
| + | y <- (1 to 9) | ||
| + | | ||
| + | yield f" | ||
| + | |||
| + | println(timesTable) | ||
| + | | ||
| + | // Vector( 1 * 1 = 1, 1 * 2 = 2, ... , 9 * 8 = 72, 9 * 9 = 81) | ||
| + | </ | ||
| + | |||
| + | === try-catch === | ||
| + | |||
| + | * try, finally는 단 하나의 식만 포함한다면 중괄호 생략 가능. | ||
| + | |||
| + | <sxh scala> | ||
| + | try { | ||
| + | val f = new FileReader(" | ||
| + | } catch { | ||
| + | case ex: FileNotFoundException => ex.printStackTrace() | ||
| + | case ex: IOException => ex.printStackTrace() | ||
| + | } finally println(" | ||
| + | </ | ||
| + | |||
| + | |||
| + | * 다른 제어 구조, 함수와 마찬가지로 결과로 값을 반환한다. 이 때, finally 절의 값은 버려진다. | ||
| + | * finally에는 값을 반환하지 말고 Side-effect(결과 완료 알림, 파일 저장등)만 이용하자. | ||
| + | |||
| + | <sxh scala> | ||
| + | // 결과 : 2 | ||
| + | // Java 형식으로 finally 절 안에 return문을 명시적으로 사용하면 try/cath의 결과를 덮어쓰게 된다. | ||
| + | // 잘 생각해보면 Java에서 finally는 어떤 결과로든 항상 실행되는 구문이기 때문. | ||
| + | def test1(): Int = try return 1 finally return 2 | ||
| + | |||
| + | // 결과 : 1 | ||
| + | // Scala 형식으로 작성하면 의도된대로 가장 처음에 만나는 값을 반환한다. | ||
| + | def test2(): Int = try 1 finally 2 | ||
| + | | ||
| + | // 결론적으로 finally에는 값을 사용하지 말자. | ||
| + | </ | ||
| + | |||
| + | |||
| + | === match === | ||
| + | case의 대안. Java와 달리 타입에 상관없이 어떤 상수값이라도 사용 가능. 이 때, match 앞에 오는 비교대상의 타입을 기본적으로 다른다. (그렇게 되면 case로 비교하는 값들도 그 타입을 따른다.) ' | ||
| + | |||
| + | <sxh scala> | ||
| + | object Client extends App { | ||
| + | def matchTest(x: | ||
| + | case 1 => " | ||
| + | case " | ||
| + | case y: Int => " | ||
| + | case _ => ' | ||
| + | } | ||
| + | |||
| + | println(matchTest(3)) // scala.Int | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Functions == | ||
| + | |||
| + | * 재귀 함수인 경우 반드시 Return Type 명시, 그외에는 생략 가능. | ||
| + | * 함수 길이 긴 경우 사람이 읽기 쉽게 Return Type을 명시하는 게 좋음. | ||
| + | * " | ||
| + | * 본론 문장이 하나면 괄호 생략 가능 | ||
| + | * Retury Type의 " | ||
| + | * parameter는 val이므로 값을 재할당할 수 없다. (예를 들어 Call-by-reference를 이용하려는 경우) | ||
| + | * 함수 안에 함수를 정의할 수 있다. | ||
| + | * 인자가 없는 메소드는 소괄호를 생략할 수 있다. 그러나 관례로서 Side-Effect가 있거나 함수 본래 작업 이외의 부가적인 작업이 존재할 경우 소괄호를 붙인다. | ||
| + | |||
| + | <sxh scala> | ||
| + | def sum(x: Int, y: Int): Int = { | ||
| + | x + y | ||
| + | } | ||
| + | |||
| + | // 위와 같은 표현 | ||
| + | def sum(x: Int, y: Int) = x + y | ||
| + | </ | ||
| + | |||
| + | |||
| + | <sxh scala> | ||
| + | // Unit 함수. Return 값이 없음을 의미. | ||
| + | // Type 추론이 되므로 Return Type에 Unit 생략 가능. | ||
| + | def greeting(): Unit = println(" | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Local functions === | ||
| + | 함수의 중첩이 가능. | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | def test1(x: Int) = { | ||
| + | def test1(y: Int) = x * y | ||
| + | | ||
| + | } | ||
| + | |||
| + | println(test1(5)) // 50 | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Placeholder syntax === | ||
| + | 위치 표시자. | ||
| + | |||
| + | <sxh scala> | ||
| + | /* 고차함수를 더 간략하게 작성하는 방법 */ | ||
| + | |||
| + | val values = 1 to 10 | ||
| + | |||
| + | values.filter((x: | ||
| + | |||
| + | // filter의 인자는 Int를 받도록 명시되어 있으므로 생략 가능 | ||
| + | values.filter(x => x > 5) | ||
| + | |||
| + | // 위치표시자(_)를 이용하면 더 간략한 표현 가능. | ||
| + | values.filter(_ > 5) | ||
| + | |||
| + | // 아래와 같은 응용도 된다. | ||
| + | values.foreach(println _) // values.foreach(x => println(x)) | ||
| + | // 그런데 foreach의 인자가 명확한 위치가 나타나기 때문에 생략 가능 | ||
| + | values.foreach(println) | ||
| + | | ||
| + | val func1 = (_:Int) + (_:Int) | ||
| + | println(func1(1, | ||
| + | </ | ||
| + | |||
| + | === Partitially applied function === | ||
| + | |||
| + | <sxh scala> | ||
| + | def sum(a: Int, b: Int, c: Int) = a + b + c | ||
| + | |||
| + | // sum 함수를 대입. 이 때, _의 의미는 sum의 모든 인자를 의미. | ||
| + | val a = sum _ | ||
| + | println(a(1, | ||
| + | |||
| + | // 아래와 같이 부분적인 인자 적용도 가능. | ||
| + | val b = sum(1, _:Int, 3) | ||
| + | println(b(2)) // = b.apply(2) | ||
| + | </ | ||
| + | |||
| + | === Closure === | ||
| + | |||
| + | <sxh scala> | ||
| + | /* Java의 경우 final(immuitable)로 선언해야만 접근 가능한 것에 비해 var(mutable)도 사용 가능. */ | ||
| + | /* Javascript와 동일. */ | ||
| + | |||
| + | /* 변수 바인딩 */ | ||
| + | var sum = 0 | ||
| + | (1 to 10).foreach(sum += _) ;; 바깥 범위(scope)에 있는 sum의 값에 누적. | ||
| + | |||
| + | println(sum) // 55 | ||
| + | | ||
| + | |||
| + | /* 함수 바인딩 */ | ||
| + | def scope1(x: Int) = { | ||
| + | /*def scope2(y: Int) = x + y | ||
| + | scope2 _*/ | ||
| + | |||
| + | // 위와 같은 표현 | ||
| + | x + (_: Int) | ||
| + | } | ||
| + | |||
| + | // 각 scope1에 바인딩된 바깥쪽 x를 다르게 설정 | ||
| + | val closure1 = scope1(1) | ||
| + | val closure2 = scope1(2) | ||
| + | |||
| + | println(closure1(10)) // 11 | ||
| + | println(closure2(10)) // 12 | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Paramter & Argument === | ||
| + | |||
| + | <sxh scala> | ||
| + | /* repeated parameter. 가변 인자(variable paramter)와 동일 개념 */ | ||
| + | def test1 (args: String*) = args.foreach(println) | ||
| + | test1(" | ||
| + | test1(Array(" | ||
| + | |||
| + | /* named argument. 순서 상관없이 인자의 이름으로 전달 */ | ||
| + | def test2 (v1: Int, v2: Int) = v1 + v2 | ||
| + | println(test2(v2 = 1, v1 = 9)) | ||
| + | |||
| + | /* default argument. 인자의 값이 없을 경우 default로 값을 미리 지정 */ | ||
| + | def test3 (v1: Int = 1, v2: Int) = v1 + v2 | ||
| + | println(test3(v2 = 1)) | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Currying === | ||
| + | |||
| + | 함수를 1급 객체로 취급하기 때문에 가능. | ||
| + | 다중 인수를 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것. | ||
| + | |||
| + | <sxh scala> | ||
| + | def curriedSum(x: | ||
| + | //def curriedSum(x: | ||
| + | |||
| + | println(curriedSum(1)(2)) | ||
| + | </ | ||
| + | |||
| + | === By-name parameters === | ||
| + | |||
| + | 값, 함수 모두 " | ||
| + | |||
| + | 이 역시 함수를 1급 객체로 취급하기 때문에 가능. | ||
| + | paramter의 Lazy evalution. 성능상의 이점을 가진다. | ||
| + | |||
| + | <sxh scala> | ||
| + | def test1(b: Boolean) = b | ||
| + | test1(2 > 1) | ||
| + | |||
| + | // by-name parameter. lazy evaluation. | ||
| + | def test2_1(b: () => Boolean) = b | ||
| + | test2_1(() => 2 > 1) | ||
| + | |||
| + | // 개선 | ||
| + | def test2_2(b: => Boolean) = b | ||
| + | test2_2(2 > 1) | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Collections == | ||
| + | |||
| + | * https:// | ||
| + | * https:// | ||
| + | |||
| + | 기본적으로 불변(Immutable) 상태를 가진다. | ||
| + | |||
| + | |||
| + | |||
| + | === Seq === | ||
| + | |||
| + | 선형 자료 구조. | ||
| + | |||
| + | * 아래 두 관점으로 적합한 자료구조를 선택. | ||
| + | * " | ||
| + | * " | ||
| + | |||
| + | ==== Immutable ==== | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | * immutable.LazyList는 immutable.Stream을 대체. immutable.Stream 는 2.13부터 Deprecated. | ||
| + | * immutable.ArraySeq 는 2.13부터 추가되었는데, | ||
| + | |||
| + | |||
| + | ===== List ===== | ||
| + | |||
| + | Singly Linked List. 앞부분에 추가/ | ||
| + | |||
| + | * ::: : List를 이어 붙여 새로운 List 반환 | ||
| + | * :: : Cons(콘즈) 라고 부르며, 새 원소를 기존 List 앞에 삽입한 새로운 List 반환 <sxh scala ; gutter: | ||
| + | [새 원소] :: [기존 List] | ||
| + | </ | ||
| + | |||
| + | * mutable 상태는 collecdtion.mutable.Buffer | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | val test1 = List(1, 2, 3) | ||
| + | val test1 = 1 :: 2 :: 3 :: Nil | ||
| + | </ | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | val test1 = List(1, 2) ::: List(3, 4) ::: List(5, 6) | ||
| + | // List(1, 2, 3, 4, 5, 6) | ||
| + | |||
| + | val test2 = 1 :: List(2, 3) | ||
| + | // List(1, 2, 3) | ||
| + | |||
| + | val test3 = List(1, 2) :: List(2, 3) | ||
| + | // List(List(1, | ||
| + | </ | ||
| + | |||
| + | |||
| + | ===== List vs. Vector ===== | ||
| + | |||
| + | [[https:// | ||
| + | |||
| + | ^ ^ head ^ tail ^ apply ^ update ^ prepend ^ append ^ | ||
| + | | List | ||
| + | | Vector | | ||
| + | |||
| + | |||
| + | * C (Constant) : 연산에 상수 시간이 걸린다. (빠름) | ||
| + | * eC (Effectively Constant) : 연산에 사실상 상수 시간이 걸리는데, | ||
| + | * L (Linear) : 연산에 선형 시간이 걸린다. 즉, Collections 크기에 비례하여 시간이 걸린다. | ||
| + | |||
| + | |||
| + | **결론적으로 head가 임의 위치 접근이 필요한 경우 Vector 선택.** | ||
| + | |||
| + | |||
| + | ==== Mutable ==== | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | |||
| + | === Set === | ||
| + | |||
| + | * 같은 이름의 mutable, immutable 패키지가 있으니 주의. | ||
| + | |||
| + | <sxh scala> | ||
| + | import scala.collection.mutable | ||
| + | |||
| + | val set1 = Set(" | ||
| + | |||
| + | // 원소 추가 | ||
| + | // Collection이 val인 경우, mutable package의 import 필요! | ||
| + | /// set1.+=(" | ||
| + | set1 += " | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Map === | ||
| + | |||
| + | <sxh scala> | ||
| + | // immutable이기 때문에 원소를 추가할 수 없음. 이어붙이는 연산을 해야 함. | ||
| + | |||
| + | val map1 = Map(" | ||
| + | val map2 = Map(" | ||
| + | val map3 = map1 ++ map2 | ||
| + | |||
| + | println(map3) | ||
| + | |||
| + | // 기본 구현체는 immutable.HashMap | ||
| + | // HashMap(E -> 5, F -> 6, A -> 1, B -> 2, C -> 3, D -> 4) | ||
| + | </ | ||
| + | |||
| + | <sxh scala ; highlight: [1]> | ||
| + | import scala.collection.mutable | ||
| + | |||
| + | val map1 = Map[String, Int]() | ||
| + | |||
| + | map1 += (" | ||
| + | map1 += (" | ||
| + | |||
| + | map1(" | ||
| + | </ | ||
| + | |||
| + | <sxh scala> | ||
| + | val map1 = Map(" | ||
| + | map1(" | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== TrieMap vs. ConcurrentHashMap ==== | ||
| + | |||
| + | * scala.collection.concurrent.TrieMap | ||
| + | * java.util.concurrent.ConcurrentHashMap | ||
| + | |||
| + | 둘 다 동시성을 보장하는 Thread-Safe Map. | ||
| + | |||
| + | |||
| + | * Reference | ||
| + | * https:// | ||
| + | * https:// | ||
| + | |||
| + | |||
| + | |||
| + | === Tuple === | ||
| + | |||
| + | 엄격히 말하면 Collections에 포함되지 않음. | ||
| + | |||
| + | * Tuple22 까지, 즉 22개의 Argument까지 지원. | ||
| + | |||
| + | <sxh scala> | ||
| + | val tp = (123, " | ||
| + | // val tp = Tuple3[Int, String, Double](123, | ||
| + | |||
| + | print(tp._1) | ||
| + | print(tp._2) | ||
| + | print(tp._3) | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Class & Object == | ||
| + | |||
| + | === 기초적으로 알아 두어야 할 사항 === | ||
| + | |||
| + | * Procedure : Side-effect만을 위해서 실행되는 Method (≒ Return Type이 없는, Unit인 Method) | ||
| + | * static Member가 없다. 대신, Singleton Object를 제공한다. | ||
| + | * Java는 Field, Method, Type, Package의 4가지 Namespace를 갖지만 Scala는 Field, Method를 2가지 Namespace 갖는다. | ||
| + | * Field가 Parameter없는 Method를 Override할 수 있다. (서로 동일시 한다.) | ||
| + | * 이 같은 성질로 같은 Class에 같은 이름의 Field와 Method를 정의할 수 없다. | ||
| + | * Package도 Field 및 Method처럼 Namespace를 공유하는 이유는 Singleton Object에서 Field와 Method를 import하기 위해서다. | ||
| + | |||
| + | |||
| + | === Access Modifier === | ||
| + | |||
| + | |< | ||
| + | ^ Modifier | ||
| + | | public (default) | ||
| + | | protected | ||
| + | | private | ||
| + | |||
| + | * Y : Field/ | ||
| + | * N : Field/ | ||
| + | |||
| + | |||
| + | === Class === | ||
| + | |||
| + | * Java와 달리 Class 이름과 Class가 작성된 파일 이름이 같을 필요가 없으나 권장. | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | class Person { | ||
| + | // member 변수와 Getter/ | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Constructor ==== | ||
| + | |||
| + | ===== Primary Constructor ===== | ||
| + | |||
| + | * 주 생성자만이 Super Class의 생성자 호출 가능! | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | // 아래와 같이 class를 선언하면 member 변수들은 private val | ||
| + | // 이후 값을 변경할 수 없음. | ||
| + | class Person (name: String, age: Int) { | ||
| + | def getName: String = name | ||
| + | def getAge : Int = age | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | // 아래와 같이 class를 선언하면 member 변수들은 public val | ||
| + | /// val을 선언함으로써 Getter 생성. " | ||
| + | // 바로 멤버 변수에 접근할 수 있고, 이후 값을 변경할 수 없음. | ||
| + | |||
| + | // Class 뒤의 인자들을 "Class Parameter" | ||
| + | // Scala Compiler는 내부적으로 Class Parameter를 기반으로 Primary Constructor(주 생성자)를 생성한다. | ||
| + | /// 기본적으로 Scala의 Constructor는 Class Parameter를 생성하지 않는다. val을 붙이면 생성한다. | ||
| + | class Person (val name: String, val age: Int) | ||
| + | </ | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | // Scala Compiler는 | ||
| + | // Class 내부에 있으면서 | ||
| + | // Field나 Method 정의에 들어 있지 않은 코드를 | ||
| + | // Primary Constructor에 삽입한다. | ||
| + | |||
| + | /// 객체 생성과 동시에 println 메시지 출력. | ||
| + | class Person (name: String, age: Int) { | ||
| + | println(" | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ===== Auxiliary Constructor ===== | ||
| + | |||
| + | * 모든 보조 생성자는 __<fc red> | ||
| + | * 주 생성자만이 Super Class의 생성자 호출 가능! | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | class Person (val name: String, val age: Int) { | ||
| + | def this(age: Int) = this(" | ||
| + | |||
| + | override def toString: String = name + " / " + age | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Checking Preconditions ==== | ||
| + | |||
| + | * 유효성 검사 때문에 생성되는 try ~ catch, if ~ else 에서 벗어날 수 있다! | ||
| + | * 조건을 만족시키지 못하면 IllegalArgumentException 발생. 메시지가 없으면 " | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | class Person (name: String, age: Int) { | ||
| + | require(name != null, "name is not nullable." | ||
| + | require(age > 0) | ||
| + | override def toString: String = name + " / " + age | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Operator Defination ==== | ||
| + | Operator Overloading. | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | class Person (val name: String, val age: Int) { | ||
| + | def +(p: Person) = this.toString + " | ||
| + | |||
| + | override def toString: String = name + " / " + age | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <sxh scala> | ||
| + | object Client extends App { | ||
| + | val p1 = new Person(" | ||
| + | val p2 = new Person(" | ||
| + | println(p1 + p2) | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Method Overloading ===== | ||
| + | |||
| + | Java와 동일하나 Scala는 Type을 유추하기 때문에 적합한 Method의 Parameter Type 일치 결과가 없다면 ' | ||
| + | |||
| + | |||
| + | ==== ' | ||
| + | |||
| + | ' | ||
| + | |||
| + | <sxh scala> | ||
| + | val l = List(1, 2, 3) | ||
| + | |||
| + | // 같은 의미 | ||
| + | println(s" | ||
| + | </ | ||
| + | |||
| + | 위 예제의 ' | ||
| + | |||
| + | |||
| + | === object === | ||
| + | |||
| + | * class 대신 **object**로 시작. Java의 static class에 대한 대안. | ||
| + | * Instance를 생성할 수 없다. 그러므로 당연히 Constructor도 선언 불가. | ||
| + | * " | ||
| + | * 대개 특정 객체에 상관없는 Side-effect Method들(대개 Service 목적의 Util 성질을 가진 공통 사용 Method)을 담아두고 사용하는 Method 사용. | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | // Companion class | ||
| + | class Person (val name: String, val age: Int) | ||
| + | |||
| + | // Companion object | ||
| + | object Person { | ||
| + | def print(person: | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <sxh scala> | ||
| + | object Client extends App { | ||
| + | val person: Person = new Person(" | ||
| + | Person.print(person) | ||
| + | } | ||
| + | |||
| + | /* Amy / 11 */ | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Companion object ==== | ||
| + | |||
| + | 어떤 class **이름**이 같은 object. 그 피대상인 class는 " | ||
| + | 이 때, object의 field들이 " | ||
| + | |||
| + | Companion class와 Companion object는 **<fc red> | ||
| + | |||
| + | <sxh scala> | ||
| + | object DBConnection { | ||
| + | private val url = " | ||
| + | private val user = " | ||
| + | private val password = " | ||
| + | |||
| + | def apply() = new DBConnection() | ||
| + | } | ||
| + | |||
| + | class DBConnection { | ||
| + | private val props = Map( | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | ) | ||
| + | } | ||
| + | |||
| + | // val connection = DBConnection() | ||
| + | </ | ||
| + | |||
| + | |||
| + | === case class === | ||
| + | |||
| + | * 자동으로 유용한 Method들 생성. (Kotlin의 'data class' | ||
| + | * Data를 관리하는데 유용. 즉, DTO로 유용. | ||
| + | * 자동으로 [[language: | ||
| + | |||
| + | * 기본적으로 생성자의 Parameter들은 ' | ||
| + | |||
| + | * Compile하면 ' | ||
| + | |||
| + | |||
| + | * 자동으로 생성되는 Methods | ||
| + | |||
| + | ^ 이름 | ||
| + | | apply | object |case class를 Instance로 만드는 Factory method | ||
| + | | unapply | ||
| + | | copy | class | **Deep copy!** | | ||
| + | | equals | ||
| + | | hashCode | class | Instance의 field들의 Hash code 반환. hash 기반 collections에서 동등성에 이용. | ||
| + | | toString | class | Field들을 String으로 전환 | | ||
| + | |||
| + | |||
| + | <sxh scala> | ||
| + | object Practice extends App { | ||
| + | val person = Person(" | ||
| + | val copy = person.copy() | ||
| + | // Equals? | ||
| + | println(person == copy) | ||
| + | |||
| + | person.name = " | ||
| + | person.age = 11 | ||
| + | // Deep copy? | ||
| + | println(copy) | ||
| + | |||
| + | // Pattern Matching | ||
| + | val p = person match { | ||
| + | case Person(_, 11) => " | ||
| + | case Person(_, 33) => " | ||
| + | } | ||
| + | println(p) | ||
| + | } | ||
| + | |||
| + | case class Person(var name: String, var age: Int) | ||
| + | </ | ||
| + | |||
| + | * Reference | ||
| + | * https:// | ||
| + | * https:// | ||
| + | |||
| + | |||
| + | |||
| + | === Composition and Inheritance ==== | ||
| + | |||
| + | ==== Override ==== | ||
| + | Java의 경우 1.5 기준으로 @Override를 명시할 수 있으나 필수사항이 아닌데 반면, Scala는 강제한다. 이로 인해 " | ||
| + | |||
| + | <sxh scala> | ||
| + | class Person (val name: String, val age: Int) { | ||
| + | override def toString: String = name + " / " + age | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Abstract Class ==== | ||
| + | Java와 비교하여 | ||
| + | |||
| + | * method에 abstract를 붙이면 안된다. | ||
| + | * abstract는 class 혹은 abstract class를 mix-in한 trait의 method만 붙일 수 있다. | ||
| + | * 본체가 없으면 abstract method로 인식한다. (이는 trait도 동일) | ||
| + | * 본체가 있는 concrete method를 구현할 수 있다. | ||
| + | * **Field가 파라미터 없는 메소드를 Override 할 수 있다.** | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | abstract class Abstract { | ||
| + | /* abstract가 필요하지 않다. */ | ||
| + | def values: Array[Int] | ||
| + | def sum = values.sum | ||
| + | } | ||
| + | |||
| + | class Detail(conts: | ||
| + | /* 아래 동작은 같다. */ | ||
| + | //override def values: Array[Integer] = conts | ||
| + | val values: Array[Int] = conts | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Parameter field 정의 ==== | ||
| + | 같은 이름의 생성자 Paramter와 Field를 동시 정의하는 단축 표기. 특정 Field에 할당하는 이름 중복을 피하기 위한 Boilerplace 제거. | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | abstract class Abstract { | ||
| + | def values: Array[Int] | ||
| + | } | ||
| + | |||
| + | // 아래와 같이 같은 목적이지만 중복된 이름을 피하기 위해 불필요한 부분이 작성된다. | ||
| + | // 할당과 동시에 바로 getter 생성 | ||
| + | class Detail(vals: | ||
| + | override def values: Array[Int] = vals | ||
| + | } | ||
| + | |||
| + | // 이를 아래와 같이 작성할 수 있음. | ||
| + | class Detail2(var values: Array[Int]) extends Abstract | ||
| + | </ | ||
| + | |||
| + | ==== Super class의 Constuctor 호출 ==== | ||
| + | Java의 경우 상속을 받으면 반드시 부모 Class의 생성자와 같은 형태의 생성자를 호출하고 super()를 하는 과정을 아래와 같이 간략화할 수 있다. | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | abstract class Abstract { | ||
| + | def values: Array[Int] | ||
| + | def sum = values.sum | ||
| + | } | ||
| + | |||
| + | class Detail(val values: Array[Int]) extends Abstract | ||
| + | /* | ||
| + | class Detail(vals: | ||
| + | override def values: Array[Int] = vals | ||
| + | } | ||
| + | */ | ||
| + | |||
| + | /* Super class(Detail)의 생성자 호출. */ | ||
| + | class MoreDetail(x: | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | = Trait = | ||
| + | Java의 Interface의 역할을 하는 한 단위. | ||
| + | |||
| + | * 두 가지를 제외하고 Class가 할 수 있는 일을 다 할 수 있다. (Field 정의, 본체가 있는 concrete method 구현등등) | ||
| + | * 할 수 없는 두 가지 : 생성자 Parameter 구현, super를 이용한 Method 정적 바인딩 | ||
| + | * Mix-in 대상은 Trait뿐만이 아니라 Class, Abstract Class 모두 다 된다. | ||
| + | * 첫 번째 상속/ | ||
| + | * 즉, Java의 Interface와 달리 Class도 상속이 가능하다! 이는 해당 부모 Class의 Method를 Override가 필요한 상황에서 사용한다. | ||
| + | |||
| + | <sxh scala> | ||
| + | object Client extends App { | ||
| + | val x:D = new D | ||
| + | x.func | ||
| + | // " | ||
| + | } | ||
| + | |||
| + | trait T1 { | ||
| + | def func(): Unit | ||
| + | } | ||
| + | |||
| + | trait T2 { | ||
| + | private val greeting = " | ||
| + | def func() = println(greeting) | ||
| + | } | ||
| + | |||
| + | class C1 { | ||
| + | } | ||
| + | |||
| + | class D extends C1 with T1 with T2 { | ||
| + | override def func(): Unit = println(" | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Stackable Modifications == | ||
| + | |||
| + | <sxh scala ; highlight: [2, 21-22]> | ||
| + | object Client extends App { | ||
| + | val queue = new BasicIntQueue with Doubling | ||
| + | queue.put(10) | ||
| + | |||
| + | println(queue.get()) | ||
| + | // 20 | ||
| + | } | ||
| + | |||
| + | abstract class IntQueue { | ||
| + | def get(): Int | ||
| + | def put(x: Int) | ||
| + | } | ||
| + | |||
| + | class BasicIntQueue extends IntQueue { | ||
| + | private val buf = new ArrayBuffer[Int] | ||
| + | override def get(): Int = buf.remove(0) | ||
| + | override def put(x: Int) = buf += x | ||
| + | } | ||
| + | |||
| + | trait Doubling extends IntQueue { | ||
| + | // trait에서 super로 접근할 때는 override abstract가 필요. | ||
| + | override abstract def put(x: Int): Unit = super.put(x * 2) | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | === 여러 Trait를 mix-in 했을 때 우선 순위 === | ||
| + | |||
| + | 가장 오른쪽에 있는 Trait의 효과부터 적용된다. | ||
| + | |||
| + | <sxh scala> | ||
| + | trait Base { | ||
| + | override def toString: String = " | ||
| + | } | ||
| + | |||
| + | |||
| + | trait A extends Base { | ||
| + | override def toString: String = s" | ||
| + | } | ||
| + | |||
| + | trait B extends Base { | ||
| + | override def toString: String = s" | ||
| + | } | ||
| + | |||
| + | trait C extends Base { | ||
| + | override def toString: String = s" | ||
| + | } | ||
| + | |||
| + | |||
| + | class MixIn extends A with B with C { | ||
| + | override def toString: String = s" | ||
| + | } | ||
| + | |||
| + | // println(new MixIn()) | ||
| + | /// MixIn-> | ||
| + | </ | ||
| + | |||
| + | |||
| + | = Type parameterization = | ||
| + | |||
| + | == Bounded Type == | ||
| + | |||
| + | Type Parameter를 특정 Class나 거의 Sub type 또는 Base type으로 **<fc red> | ||
| + | |||
| + | === Upper bound === | ||
| + | |||
| + | Type을 Base type 또는 Sub type 중 하나로 제한한다. | ||
| + | |||
| + | https:// | ||
| + | |||
| + | <sxh> | ||
| + | < | ||
| + | </ | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | object Practice extends App { | ||
| + | def check[A <: Parent](u: A): Unit = { | ||
| + | println(s" | ||
| + | } | ||
| + | |||
| + | // Runtime Error | ||
| + | // inferred type arguments [GrandParent] do not conform to method check' | ||
| + | check(new GrandParent(" | ||
| + | |||
| + | check(new Parent(" | ||
| + | check(new Child(" | ||
| + | } | ||
| + | |||
| + | class GrandParent(val name: String) | ||
| + | class Parent(name: | ||
| + | class Child(name: String) extends Parent(name) | ||
| + | </ | ||
| + | |||
| + | === Lower bound === | ||
| + | |||
| + | Type을 해당 Type으로 제한하거나 또는 해당 Type이 확장되는 Base type 중 하나로 제한한다. | ||
| + | |||
| + | <sxh> | ||
| + | < | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Type variance == | ||
| + | |||
| + | Type parameter를 **<fc red>덜 제한적</ | ||
| + | 타입 가변성(Type variance)은 Type parameter가 Base type이나 Sub type을 충족하도록 적응하는 방법을 지정한다. | ||
| + | |||
| + | |< | ||
| + | ^ | ||
| + | | +T | ? extends T | Covariance (공변성) | ||
| + | | -T | ||
| + | | | ||
| + | |||
| + | * Effective Java에서 나오는 [[https:// | ||
| + | * Producer는 getter, Consumer는 setter | ||
| + | * Scala의 [[https:// | ||
| + | * 첫번째 인자 **-T1**는 Producer, 두번째 인자 **+R**는 반환값으로 Consumer. | ||
| + | * 반환해야 하는(생산하는) 값이 Super type인데, 인자로 받은(제공하는) 값이 Sub type이면 Super type의 Method를 호출할 수 없음. | ||
| + | * 이런 이유로 T1은 동등한 레벨의 타입이거나 상위 타입(Super type)이어야 한다. | ||
| + | * " | ||
| + | |||
| + | <sxh scala> | ||
| + | // https:// | ||
| + | |||
| + | class CSuper | ||
| + | class C extends CSuper { def m() = println(" | ||
| + | class CSub | ||
| + | |||
| + | object AbsTypes extends App { | ||
| + | // 추상적인 것 -> 구체적인 것 | ||
| + | val f1: C => C = (c: C) => new C | ||
| + | val f2: C => C = (c: CSuper) => new CSub | ||
| + | val f3: C => C = (c: CSuper) => new C | ||
| + | val f4: C => C = (c: C) => new CSub | ||
| + | | ||
| + | // Error! | ||
| + | /** 인자로 C를 받도록 타입을 정의했는데, | ||
| + | | ||
| + | */ | ||
| + | val f5: C => C = (c: CSub) => new CSuper | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | = Abstract types = | ||
| + | |||
| + | https:// | ||
| + | |||
| + | <sxh scala ; highlight: | ||
| + | import java.io._ | ||
| + | import scala.io.Source | ||
| + | |||
| + | // https:// | ||
| + | // 위 소스를 응용한 예제 | ||
| + | abstract class BulkReader { | ||
| + | type In // 구체적인 타입을 지정하지 않음. | ||
| + | val source: In // 바로 위에서 지정한 Type인 " | ||
| + | def read: String | ||
| + | } | ||
| + | |||
| + | class StringBulkReader | ||
| + | (val source: String) | ||
| + | extends BulkReader { | ||
| + | type In = String // 추상 타입 구체화 | ||
| + | |||
| + | override def read: String = source | ||
| + | } | ||
| + | |||
| + | class FileBulkReader | ||
| + | (val source: File) // 추상 타입의 실질 타입 정의 | ||
| + | extends BulkReader { | ||
| + | type In = File // 추상 타입 구체화 | ||
| + | |||
| + | override def read: String = { | ||
| + | val source1 = Source.fromFile(source) | ||
| + | try source1.mkString finally source1.close() | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | == Parameterized types vs. Abstract types == | ||
| + | |||
| + | 어떤 상황에서 무엇이 적절한가? | ||
| + | |||
| + | * Parameterized types : Type parameter가 Parameterized types와 관련이 없는 경우 | ||
| + | * List[A]에서 A는 String, Int 무엇이든 상관 없음. | ||
| + | | ||
| + | * Abstract types : 타입 멤버가 객체의 **동작**과 일치하는 경우 | ||
| + | * 위 예시를 보면 " | ||
| + | |||
| + | |||
| + | = Implicit Parameters and Conversions = | ||
| + | |||
| + | == Implicit Parameters == | ||
| + | |||
| + | Currying 혹은 Lazy 된 함수의 인자를 명시적으로 지정하지 않아도 자신의 네임스페이스의 기본값을 사용하는 방법. 변수나 함수 매개변수에 " | ||
| + | |||
| + | " | ||
| + | |||
| + | |||
| + | <sxh scala> | ||
| + | object Printer { | ||
| + | def print(num: Double)(implicit format: String) = println(format.format(num)) | ||
| + | } | ||
| + | |||
| + | // fmt가 주입된다. | ||
| + | object Practice extends App { | ||
| + | implicit val fmt = " | ||
| + | Printer.print(1.11) | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | == Implicit Class == | ||
| + | |||
| + | 암시적 Type 변환. 어느 Instance의 Type이 특정 Instance의 Type인 것처럼 자동 전환하여 그 특정 Instance의 Method를 가진 것처럼 보이게 할 수 있다. | ||
| + | |||
| + | C#, Kotlin의 Extension Methods과 비슷한 기능. | ||
| + | |||
| + | 서로를 고려하지 않고 독립적으로 개발된 소프트웨어/ | ||
| + | |||
| + | * 생성 규칙 | ||
| + | * object/ | ||
| + | * Implicit를 선언하지 않는 인자를 받아야 한다. | ||
| + | * 같은 namespace에서 object/ | ||
| + | * case Class는 사용할 수 없다. (자동으로 생성되는 Companion Object 때문) | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | object Client extends App { | ||
| + | val uc = new UnaryCalculator(2) | ||
| + | | ||
| + | val op1 = uc ** 3 | ||
| + | //val op1 = uc.**(3) | ||
| + | |||
| + | val op2 = 3 ** uc // Error! | ||
| + | //val op2 = {3}.**(uc), 정수 3은 ** Method를 가지고 있지 않음. | ||
| + | | ||
| + | println(op1 + " / " + op2) | ||
| + | } | ||
| + | |||
| + | class UnaryCalculator (val v: Int) { | ||
| + | def ** (`pow`: Int) = math.pow(v, `pow`) | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | <sxh scala ; title: | ||
| + | object Client extends App { | ||
| + | implicit def intToUnaryCalculator (initVal: Int) = new UnaryCalculator(initVal) | ||
| + | |||
| + | val uc = new UnaryCalculator(2) | ||
| + | | ||
| + | val op1 = uc ** 3 | ||
| + | //val op = uc.**(3) | ||
| + | |||
| + | val op2 = 3 ** uc | ||
| + | //val op = {3}.**(uc) | ||
| + | |||
| + | println(op1 + " / " + op2) // 8.0 / 9.0 | ||
| + | } | ||
| + | |||
| + | class UnaryCalculator (val v: Int) { | ||
| + | def ** (`pow`: UnaryCalculator) = math.pow(v, `pow`.v) | ||
| + | } | ||
| + | </ | ||
| + | |||