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 |
---|
| = Scala = |
| |
| * https://docs.scala-lang.org/ |
| * 기본적으로 Java를 다뤄봤다는 전제하에 실용적인 측면으로만 기술. |
| * 2.12 이상 버전을 기준으로 기술. |
| |
| {{tag>Language Scala JVM Object_Oriented_Programming Functional_Programming}} |
| |
| = 환경 설정 = |
| * JDK 1.8 이상 설치 |
| |
| == Command Line == |
| https://www.scala-lang.org/download/ |
| |
| === SBT (Scala Build Tools) === |
| Scala의 Ant/Maven/Gradle와 비교될 수 있음. 직접으로 프로젝트 생성, 배포 관여. |
| |
| ==== Create templates ==== |
| |
| https://www.scala-sbt.org/1.x/docs/sbt-new-and-Templates.html |
| |
| <sxh shell> |
| sbt new scala/scala-seed.g8 |
| </sxh> |
| |
| |
| ==== Fat Jar ==== |
| |
| https://github.com/sbt/sbt-assembly |
| |
| <sxh shell> |
| # Build (Fat Jar 생성) |
| ## build.sbt에 |
| ## addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.6") |
| ## 추가 필요 |
| ### 생성된 jar 위치 : target/scala-[version]/*.jar |
| sbt assembly |
| |
| </sxh> |
| |
| |
| |
| === Scala Binaries (선택 사항) === |
| * REPL(scala), 단독 Compile(scalac)을 이용하기 위해 필요 |
| |
| <sxh shell> |
| # 실행 |
| scala |
| |
| scala> 1 |
| res0: Int = 1 |
| |
| # 종료 |
| scala> :quit |
| ## 혹은 |
| scala> :q |
| </sxh> |
| |
| |
| == IDE == |
| - Intellij에 기본적으로 SBT 내장. Intellij만 설치하면 된다. |
| |
| === REPL 실행 방법 === |
| 왼쪽 Project Tree에서 아무 Class에 오른쪽으로 누르고 "Run Scala Console". |
| = Hello, world! = |
| |
| * 가변 인자 args를 입력 받아 "Hello, world!"를 출력하는 예제 작성 |
| |
| == Script == |
| |
| * Compile 하지 않고, Shell script 처럼 사용 |
| |
| <sxh scala ; title:script.scala> |
| // class, main 함수를 선언하지 않고도 가변인자 args 사용 가능! |
| args.foreach(print) |
| println() |
| </sxh> |
| |
| <sxh bash> |
| > scala script.scala Hello , world ! |
| Hello, world! |
| </sxh> |
| |
| |
| == Compile == |
| |
| https://www.scala-lang.org/documentation/your-first-lines-of-scala.html |
| |
| |
| === App trait 상속 O === |
| |
| <sxh scala ; title:HelloWorld.scala> |
| object HelloWorld extends App { |
| // class, main 함수를 선언하지 않고도 가변인자 args 사용 가능! |
| args.foreach(print) |
| println() |
| } |
| </sxh> |
| |
| <sxh shell> |
| > scalac HelloWorld.scala |
| > scala HelloWorld Hello , world ! |
| </sxh> |
| |
| |
| === App trait 상속 X === |
| |
| <sxh scala ; title:HelloWorld.scala> |
| |
| object HelloWorld { |
| def main(args: Array[String]): Unit = { |
| args.foreach(print) |
| println() |
| } |
| } |
| </sxh> |
| |
| <sxh shell> |
| > scalac HelloWorld.scala |
| > scala HelloWorld Hello , world ! |
| </sxh> |
| |
| |
| |
| = 문법 = |
| |
| == Identifier == |
| 식별자 규칙 및 관례적 명명법 서술. |
| |
| * 연산자 정의(Operator Defination) Method와 암시적 변환([[language:scala#Implicit Parameters and Conversions|Implicit Parameters and Conversions]])는 간결하고 유연한 표현을 정의할 수 있지만, 잘못 사용하면 가독성을 떨어뜨리는 결과를 낳으니 주의가 필요하다. |
| |
| |
| === Alphanumeric Identifier === |
| |
| # Java와 동일하게 영숫자 혹은 '_', '$'가 가능하나 '$' Scala Compiler가 내부적으로 생성하는 식별자에 사용하는 예약 문자이므로 충돌 가능성이 있기 때문에 사용해서는 안된다. |
| # 상수는 첫글자만 대문자로, Camel Case 표현을 한다. (Java 표현으로 MAX_VALUE라면, Scala는 MaxValue) |
| |
| === Operator Identifier === |
| |
| * Java와 가장 큰 차이점 중 하나. 이를 이용하면 Operator Overloading이 가능. Method에만 적용 가능. |
| * +, :, ?, ~, #, +, ++, ::(List에서 요소 덧붙임), :::(List에 다른 List 추가), <?>, :-> 등 사용 가능. 이 때 길이 제한이 없다. |
| |
| |
| * Overloading 예시 |
| |
| <sxh scala> |
| def *** (v: Int) = math.pow(v, 3) |
| </sxh> |
| |
| ==== 연산자 우선순위와 결합법칙 ==== |
| |
| **:**(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.:::(c).:::(a) |
| </sxh> |
| |
| |
| === 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`) |
| </sxh> |
| |
| == 변수 == |
| * val : Immutable |
| * var : mutable |
| |
| |
| |
| == Type == |
| |
| === Type Hierarchy === |
| |
| {{https://docs.scala-lang.org/resources/images/tour/unified-types-diagram.svg}} |
| |
| |<100%>| |
| ^ Any |모든 type의 supertype. equals, hashCode, toString 메서드가 정의됨. | |
| ^ AnyVal |value type을 표현, non-nullable. Double, Float, Long, Int, Short, Byte, Char, Unit, Boolean의 상위 타입. | |
| ^ AnyRef |referenced type을 표현, java.lang.Object에 대응. | |
| ^ Nothing |모든 type의 subtype (i.e. bottom type)으로 value를 가지고 있지 않다. | |
| ^ Null |모든 reference type의 subtype. keyword literal 'null'을 값으로 가지고 있다. | |
| |
| |
| === Literals === |
| |
| ==== Integer Literals ==== |
| |
| * C/Java와 달리 8진(0으로 시작하는 정수형)을 지원하지 않음. 10진과 16진만 지원. |
| |
| ==== String Literals ==== |
| |
| ===== Raw Sring ===== |
| 큰따옴표 3개(<nowiki>"""</nowiki>)를 이용하여 그 내부 문자열을 "날 것" 그대로 표현하는 문자열. (공백, 개행 전부 포함) |
| |
| <sxh scala> |
| object Test extends App { |
| // Java에서의 일부 특수문자 표현. 가독성이 좋지 않음. |
| val str1 = "Hello\n\"world\""; |
| println(str1) |
| |
| /** |
| * Hello |
| * "world" |
| */ |
| |
| |
| // 공백, 개행 모두 그래도 출력 |
| val str2 = |
| """Hello, |
| "world"! |
| """ |
| println(str2) |
| |
| /** |
| * Hello |
| * "world" |
| */ |
| |
| |
| // |를 넣으면 문자열의 시작을 알림. 이후 stripMargin을 하면 이전 공백 제거 |
| val str3 = |
| """ |Hello, |
| |"world!" |
| """.stripMargin |
| println(str3) |
| |
| /** |
| * Hello |
| * "world" |
| */ |
| } |
| </sxh> |
| |
| ===== String interpolation ===== |
| 문자열 내부에 표현식(expression)을 내장시키는 것. 이로써 문자열을 이어붙이지 않고 가독성이 좋아짐. |
| * s : "$val" 혹은 "${expression}"과 함께 쓰여 mapping. |
| * raw : 문자열 그대로 출력. |
| * f : printf 형태의 형식 지정. |
| |
| |
| |<100%>| |
| ^ ^ Description ^ Example ^ |
| | s | "$val" 혹은 "${expression}"과 함께 쓰여 mapping. | <sxh scala> |
| val str = "9 * 9 = " |
| println(s"$str ${9 * 9}") |
| /** |
| * 9 * 9 = 81 |
| */ |
| </sxh> | |
| | raw | 문자열 그대로 출력. | <sxh scala> |
| println(raw"A\nB\\C\tD\bE") |
| |
| /** |
| * A\nB\\C\tD\bE |
| */ |
| </sxh> | |
| | f | printf 형태의 형식 지정. \\ ${expression}%[format] | <sxh scala> |
| println(f"${math.Pi}%.7f") |
| println(f"${3 * 3}%03d") |
| |
| /** |
| * 3.1415927 |
| * 009 |
| */ |
| </sxh> | |
| |
| |
| ==== Symbol literals ==== |
| |
| * 작은 따옴표(') + (알파벳 + 숫자) |
| * 단순 식별자가 필요할 때 사용. Java의 enum이나 [[language:clojure#keyword|Clojure의 Keyword]]와 비슷. |
| * Scala의 Strong Type 언어이기 때문에 Weak Type 형태로 사용할 때 사용. |
| * [[language:clojure#keyword|Clojure의 Keyword]]처럼 자기 자신을 반환 |
| * .name 필드로 문자열 변환 가능. |
| |
| |
| === Array === |
| |
| * 모든 것을 Method가 있는 객체로 다루기 때문에 Parameter에 해당하는 호출은 **()**을 사용한다. |
| |
| <sxh scala> |
| val fruits = new Array[String](2) |
| // val fruits: String = new Array[String](2) |
| |
| fruits(0) = "apple" |
| // fruites.update(0, "apple") |
| |
| fruits(1) = "banana" |
| // fruites.update(1, "banana") |
| </sxh> |
| |
| <sxh scala> |
| val fruits = Array("apple", "banana") |
| // val fruits = Array.apply("apple", "banana") |
| </sxh> |
| |
| |
| == Operator == |
| |
| === Operator = Method! === |
| |
| Scala에서 모든 연산자는 Method!! 실질적으로 Scala는 Method를 호출한다. |
| |
| <sxh scala> |
| /** Binary Operator **/ |
| |
| val sum = 1 + 2 |
| // 1.+(2) |
| |
| "Hello, world!" indexOf 'o' |
| // "Hello, world!".indexOf('o') |
| |
| "Hello, world!" indexOf ('o', 5) |
| // "Hello, world!".indexOf('o', 5) |
| |
| |
| |
| /** Unary Operator **/ |
| |
| /*** 전위 표기법 ***/ |
| // +,0,!,~ 4가지. |
| |
| val a = -2.0 |
| // (2.0).unary_- |
| |
| |
| /*** 후위 표기법 ***/ |
| // 인자를 취하지 않는 Method를 '.'이나 괄호 없이 호출 |
| |
| val str = "Hello, world!" toLowercase |
| // val str = "Hello, world!".toLowercase() |
| </sxh> |
| |
| |
| === == & eq === |
| Java와 동일하다. \\ <nowiki>==</nowiki>, !=은 원시 타입에서 값(value)이 같은지 비교, eq/ne는 JVM Heap에서 참조(reference) 비교 |
| |
| <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> ("He" + "llo") == "Hello" |
| res11: Boolean = true |
| </sxh> |
| |
| |
| === 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> "hello" capitalize |
| res6: String = Hello |
| |
| scala> "Hello, World" drop 7 |
| res7: String = World |
| </sxh> |
| |
| |
| == 내장 제어 구문 == |
| |
| **전체적으로 재귀나 내장함수(reduce, map, filter, reduce)로 대체가 가능하기 때문에 사용을 자제하자. 제어 구문은 함수형 표현에 적합하지 않다.** |
| |
| * 키워드로써 break, continue는 존재하지 않는다. (함수형 언어에 어울리지 않으므로.) |
| * 굳이 break를 쓰고 싶다면 scala.util.control.Breaks 에 있는 것을 사용할 수 있다...만... |
| * 제어 구문들도 함수이므로 var/val에 할당할 수 있다. (이 때, 기본적으로 바로 계산한다. lazy loading이 필요하면 lazy를 맨 앞에 붙이자.) |
| * while은 대체로 중간 결과를 저장하는 변수 var가 필요하기 때문에 사용을 자제하자. |
| |
| |
| === for === |
| |
| foreach 문만 존재한다. 그러나 대체로 반복하는 경우 내장 함수(foreach, reduce, map, filter 등)이 존재하기 때문에 특별한 경우가 아니면 큰 필요성이 없다. |
| |
| <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(".").listFiles() |
| if file.isFile |
| if file.getName.endsWith(".scala")) |
| println(file) |
| |
| /* 중첩 및 임시 변수 Binding */ |
| // 구구단 출력 |
| for {x <- (1 to 9) |
| y <- (1 to 9) |
| result = x * y} |
| println(f"$x * $y = $result%02d") |
| </sxh> |
| |
| |
| ==== for-yield ==== |
| |
| <EXPRESSION>의 값을 Collection로 반환. (//map// 과 같은 효과) |
| |
| <code scala> |
| for (<IDENTIFIER> <- <ITERATOR>) yield <EXPRESSION> |
| </code> |
| |
| <sxh scala> |
| // 인자가 없기 때문에 ()를 생략한 형태. |
| def timesTable = for { |
| x <- (1 to 9) |
| y <- (1 to 9) |
| result = x * y} |
| yield f"$x%2d * $y%2d = $result%2d" |
| |
| println(timesTable) |
| |
| // Vector( 1 * 1 = 1, 1 * 2 = 2, ... , 9 * 8 = 72, 9 * 9 = 81) |
| </sxh> |
| |
| === try-catch === |
| |
| * try, finally는 단 하나의 식만 포함한다면 중괄호 생략 가능. |
| |
| <sxh scala> |
| try { |
| val f = new FileReader(".") |
| } catch { |
| case ex: FileNotFoundException => ex.printStackTrace() |
| case ex: IOException => ex.printStackTrace() |
| } finally println("done") |
| </sxh> |
| |
| |
| * 다른 제어 구조, 함수와 마찬가지로 결과로 값을 반환한다. 이 때, 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에는 값을 사용하지 말자. |
| </sxh> |
| |
| |
| === match === |
| case의 대안. Java와 달리 타입에 상관없이 어떤 상수값이라도 사용 가능. 이 때, match 앞에 오는 비교대상의 타입을 기본적으로 다른다. (그렇게 되면 case로 비교하는 값들도 그 타입을 따른다.) '_'은 default에 대응하며 "완전히 알려지지 않은 값"을 의미. |
| |
| <sxh scala> |
| object Client extends App { |
| def matchTest(x: Any): Any = x match { |
| case 1 => "one" |
| case "two" => 2 |
| case y: Int => "scala.Int" |
| case _ => 'unknown |
| } |
| |
| println(matchTest(3)) // scala.Int |
| } |
| </sxh> |
| |
| |
| == Functions == |
| |
| * 재귀 함수인 경우 반드시 Return Type 명시, 그외에는 생략 가능. |
| * 함수 길이 긴 경우 사람이 읽기 쉽게 Return Type을 명시하는 게 좋음. |
| * "return" 구문은 선택. 생략 가능. |
| * 본론 문장이 하나면 괄호 생략 가능 |
| * Retury Type의 "Unit"은 Java의 void. 즉, Side-effect를 위해서만 실행하는 함수. |
| * 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> |
| |
| |
| <sxh scala> |
| // Unit 함수. Return 값이 없음을 의미. |
| // Type 추론이 되므로 Return Type에 Unit 생략 가능. |
| def greeting(): Unit = println("Hello, world!") |
| </sxh> |
| |
| |
| === Local functions === |
| 함수의 중첩이 가능. |
| |
| <sxh scala ; title:지역함수> |
| def test1(x: Int) = { |
| def test1(y: Int) = x * y |
| test1(10) |
| } |
| |
| println(test1(5)) // 50 |
| </sxh> |
| |
| |
| === Placeholder syntax === |
| 위치 표시자. |
| |
| <sxh scala> |
| /* 고차함수를 더 간략하게 작성하는 방법 */ |
| |
| val values = 1 to 10 |
| |
| values.filter((x: Int) => x > 5) |
| |
| // 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, 1)) // 2 |
| </sxh> |
| |
| === Partitially applied function === |
| |
| <sxh scala> |
| def sum(a: Int, b: Int, c: Int) = a + b + c |
| |
| // sum 함수를 대입. 이 때, _의 의미는 sum의 모든 인자를 의미. |
| val a = sum _ |
| println(a(1, 2, 3)) // = a.apply(1, 2, 3) |
| |
| // 아래와 같이 부분적인 인자 적용도 가능. |
| val b = sum(1, _:Int, 3) |
| println(b(2)) // = b.apply(2) |
| </sxh> |
| |
| === 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 |
| </sxh> |
| |
| |
| === Paramter & Argument === |
| |
| <sxh scala> |
| /* repeated parameter. 가변 인자(variable paramter)와 동일 개념 */ |
| def test1 (args: String*) = args.foreach(println) |
| test1("Hello", "world", "!") |
| test1(Array("Hello", "world", "!"): _*) |
| |
| /* 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)) |
| </sxh> |
| |
| |
| === Currying === |
| |
| 함수를 1급 객체로 취급하기 때문에 가능. |
| 다중 인수를 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것. |
| |
| <sxh scala> |
| def curriedSum(x: Int)(y: Int) = x + y |
| //def curriedSum(x: Int) = (y: Int) => x + y |
| |
| println(curriedSum(1)(2)) |
| </sxh> |
| |
| === 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) |
| </sxh> |
| |
| |
| == Collections == |
| |
| * https://docs.scala-lang.org/overviews/collections-2.13/overview.html |
| * https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html |
| |
| 기본적으로 불변(Immutable) 상태를 가진다. |
| |
| |
| |
| === Seq === |
| |
| 선형 자료 구조. |
| |
| * 아래 두 관점으로 적합한 자료구조를 선택. |
| * "Indexing"이 필요한가? |
| * "mutable state"가 필요한가? |
| |
| ==== Immutable ==== |
| |
| {{ :language:scala.collection.immutable.seq.png |}} |
| |
| * immutable.LazyList는 immutable.Stream을 대체. immutable.Stream 는 2.13부터 Deprecated. |
| * immutable.ArraySeq 는 2.13부터 추가되었는데, 왠만하면 성능면에서 사실상 상수 시간(Constant time)을 가지는 Vector를 사용하는게 이득. |
| |
| |
| ===== List ===== |
| |
| Singly Linked List. 앞부분에 추가/삭제 유리. 임의 접근은 불리. |
| |
| * ::: : List를 이어 붙여 새로운 List 반환 |
| * :: : Cons(콘즈) 라고 부르며, 새 원소를 기존 List 앞에 삽입한 새로운 List 반환 <sxh scala ; gutter:false> |
| [새 원소] :: [기존 List] |
| </sxh> |
| |
| * mutable 상태는 collecdtion.mutable.Buffer |
| |
| <sxh scala ; title:초기화> |
| val test1 = List(1, 2, 3) |
| val test1 = 1 :: 2 :: 3 :: Nil |
| </sxh> |
| |
| <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, 2), 2, 3) |
| </sxh> |
| |
| |
| ===== List vs. Vector ===== |
| |
| [[https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html|전체 Collections 성능 비교]] |
| |
| ^ ^ head ^ tail ^ apply ^ update ^ prepend ^ append ^ |
| | List | C | C | L | L | C | L | |
| | Vector | eC | eC | eC | eC | eC | eC | |
| |
| |
| * C (Constant) : 연산에 상수 시간이 걸린다. (빠름) |
| * eC (Effectively Constant) : 연산에 사실상 상수 시간이 걸리는데, 이는 Vector의 최대 길이나 Hash key 분포와 같은 일부 가정에 따라 달라질 수 있다. |
| * L (Linear) : 연산에 선형 시간이 걸린다. 즉, Collections 크기에 비례하여 시간이 걸린다. |
| |
| |
| **결론적으로 head가 임의 위치 접근이 필요한 경우 Vector 선택.** |
| |
| |
| ==== Mutable ==== |
| |
| {{ :language:scala.collection.mutable.seq.png |}} |
| |
| |
| === Set === |
| |
| * 같은 이름의 mutable, immutable 패키지가 있으니 주의. |
| |
| <sxh scala> |
| import scala.collection.mutable |
| |
| val set1 = Set("a", "b") |
| |
| // 원소 추가 |
| // Collection이 val인 경우, mutable package의 import 필요! |
| /// set1.+=("b") 와 같은 표현 |
| set1 += "b" |
| </sxh> |
| |
| |
| === Map === |
| |
| <sxh scala> |
| // immutable이기 때문에 원소를 추가할 수 없음. 이어붙이는 연산을 해야 함. |
| |
| val map1 = Map("A" -> 1, "B" -> 2, "C" -> 3) |
| val map2 = Map("D" -> 4, "E" -> 5, "F" -> 6) |
| val map3 = map1 ++ map2 |
| |
| println(map3) |
| |
| // 기본 구현체는 immutable.HashMap |
| // HashMap(E -> 5, F -> 6, A -> 1, B -> 2, C -> 3, D -> 4) |
| </sxh> |
| |
| <sxh scala ; highlight: [1]> |
| import scala.collection.mutable |
| |
| val map1 = Map[String, Int]() |
| |
| map1 += ("One" -> 1) |
| map1 += ("Two" -> 2) |
| |
| map1("One") |
| </sxh> |
| |
| <sxh scala> |
| val map1 = Map("One" -> 1, "Two", 2) |
| map1("One") |
| </sxh> |
| |
| |
| ==== TrieMap vs. ConcurrentHashMap ==== |
| |
| * scala.collection.concurrent.TrieMap |
| * java.util.concurrent.ConcurrentHashMap |
| |
| 둘 다 동시성을 보장하는 Thread-Safe Map. |
| |
| |
| * Reference |
| * https://stackoverflow.com/questions/29499381/what-is-a-triemap-and-what-is-its-advantages-disadvantages-compared-to-a-hashmap |
| * https://www.researchgate.net/publication/221643801_Concurrent_Tries_with_Efficient_Non-Blocking_Snapshots |
| |
| |
| |
| === Tuple === |
| |
| 엄격히 말하면 Collections에 포함되지 않음. |
| |
| * Tuple22 까지, 즉 22개의 Argument까지 지원. |
| |
| <sxh scala> |
| val tp = (123, "Hello", 1.23) |
| // val tp = Tuple3[Int, String, Double](123, "Hello", 1.23) |
| |
| print(tp._1) |
| print(tp._2) |
| print(tp._3) |
| </sxh> |
| |
| |
| == 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 === |
| |
| |<100%>| |
| ^ Modifier ^ Class ^ Companion ^ Subclass ^ Package ^ |
| | public (default) | Y | Y | Y | Y | |
| | protected | Y | Y | Y | N | |
| | private | Y | Y | N | N | |
| |
| * Y : Field/Method 접근 가능 |
| * N : Field/Method 접근 불가 |
| |
| |
| === Class === |
| |
| * Java와 달리 Class 이름과 Class가 작성된 파일 이름이 같을 필요가 없으나 권장. |
| |
| <sxh scala ; title:기본> |
| class Person { |
| // member 변수와 Getter/Setter 서술 |
| } |
| </sxh> |
| |
| ==== Constructor ==== |
| |
| ===== Primary Constructor ===== |
| |
| * 주 생성자만이 Super Class의 생성자 호출 가능! |
| |
| <sxh scala ; title:Constructor> |
| // 아래와 같이 class를 선언하면 member 변수들은 private val |
| // 이후 값을 변경할 수 없음. |
| class Person (name: String, age: Int) { |
| def getName: String = name |
| def getAge : Int = age |
| } |
| </sxh> |
| |
| <sxh scala ; title:Constructor+Getter> |
| // 아래와 같이 class를 선언하면 member 변수들은 public val |
| /// val을 선언함으로써 Getter 생성. ".name", ".age"로 접근. |
| // 바로 멤버 변수에 접근할 수 있고, 이후 값을 변경할 수 없음. |
| |
| // Class 뒤의 인자들을 "Class Parameter"라고 지칭한다. |
| // Scala Compiler는 내부적으로 Class Parameter를 기반으로 Primary Constructor(주 생성자)를 생성한다. |
| /// 기본적으로 Scala의 Constructor는 Class Parameter를 생성하지 않는다. val을 붙이면 생성한다. |
| class Person (val name: String, val age: Int) |
| </sxh> |
| |
| <sxh scala ; title:Constructor_특성> |
| // Scala Compiler는 |
| // Class 내부에 있으면서 |
| // Field나 Method 정의에 들어 있지 않은 코드를 |
| // Primary Constructor에 삽입한다. |
| |
| /// 객체 생성과 동시에 println 메시지 출력. |
| class Person (name: String, age: Int) { |
| println("Created!" + name + " / " + age) |
| } |
| </sxh> |
| |
| |
| ===== Auxiliary Constructor ===== |
| |
| * 모든 보조 생성자는 __<fc red>**반.드.시.**</fc> 같은 클래스에 속한 다른 생성자를 호출__하는 코드로 시작해야 한다. 결국 주 생성자를 호출하게 만드는 효과가 있다. |
| * 주 생성자만이 Super Class의 생성자 호출 가능! |
| |
| <sxh scala ; highlight:[2]> |
| class Person (val name: String, val age: Int) { |
| def this(age: Int) = this("meteor", age); |
| |
| override def toString: String = name + " / " + age |
| } |
| </sxh> |
| |
| |
| ==== Checking Preconditions ==== |
| |
| * 유효성 검사 때문에 생성되는 try ~ catch, if ~ else 에서 벗어날 수 있다! |
| * 조건을 만족시키지 못하면 IllegalArgumentException 발생. 메시지가 없으면 "requirement failed" |
| |
| <sxh scala ; highlight:[2,3]> |
| class Person (name: String, age: Int) { |
| require(name != null, "name is not nullable.") |
| require(age > 0) |
| override def toString: String = name + " / " + age |
| } |
| </sxh> |
| |
| |
| ==== Operator Defination ==== |
| Operator Overloading. |
| |
| <sxh scala ; highlight:[2]> |
| class Person (val name: String, val age: Int) { |
| def +(p: Person) = this.toString + "\n" + p.toString |
| |
| override def toString: String = name + " / " + age |
| } |
| </sxh> |
| |
| <sxh scala> |
| object Client extends App { |
| val p1 = new Person("hyuk", 30) |
| val p2 = new Person("meteor", 26) |
| println(p1 + p2) |
| } |
| </sxh> |
| |
| |
| ==== Method Overloading ===== |
| |
| Java와 동일하나 Scala는 Type을 유추하기 때문에 적합한 Method의 Parameter Type 일치 결과가 없다면 'ambiguous reference' 오류를 출력. 그리고 Overloading보다 [[https://docs.scala-lang.org/tour/default-parameter-values.html|DEFAULT PARAMETER VALUES]]를 이용을 권장. Method 수를 줄일 수 있다. |
| |
| |
| ==== 'apply' Method ==== |
| |
| 'Default Method' 또는 'Injector Method'라고도 불린다. 이는 __Method 이름없이 **괄호**__를 사용하여 호출하는 기능을 갖고 있다. |
| |
| <sxh scala> |
| val l = List(1, 2, 3) |
| |
| // 같은 의미 |
| println(s"${l.apply(1)} ${l(1)}") |
| </sxh> |
| |
| 위 예제의 'List.apply(index)' 처럼 상식적으로 이해할 수 있는, 자연스러운 연산에 적용되어야 한다. |
| |
| |
| === object === |
| |
| * class 대신 **object**로 시작. Java의 static class에 대한 대안. |
| * Instance를 생성할 수 없다. 그러므로 당연히 Constructor도 선언 불가. |
| * "Singleton"으로 작동하므로 첫 접근이 되지 않는 한 인스턴스가 만들어지지 않는다. |
| * 대개 특정 객체에 상관없는 Side-effect Method들(대개 Service 목적의 Util 성질을 가진 공통 사용 Method)을 담아두고 사용하는 Method 사용. |
| |
| <sxh scala ; title:Person.scala> |
| // Companion class |
| class Person (val name: String, val age: Int) |
| |
| // Companion object |
| object Person { |
| def print(person: Person) :Unit = println(person.name + " / " + person.age) |
| } |
| </sxh> |
| |
| <sxh scala> |
| object Client extends App { |
| val person: Person = new Person("Amy", 11) |
| Person.print(person) |
| } |
| |
| /* Amy / 11 */ |
| </sxh> |
| |
| |
| ==== Companion object ==== |
| |
| 어떤 class **이름**이 같은 object. 그 피대상인 class는 "Companion class"라고 한다. Companion object와 [[language:scala#'apply' Method|'apply' Method]]를 이용하여 Companion class의 [[design_pattern:factory_method_pattern|Factory Pattern]] 을 적용할 때 사용. |
| 이 때, object의 field들이 "private"으로 선언되어 있더라도 접근 가능. |
| |
| Companion class와 Companion object는 **<fc red>__반드시__</fc> 같은 소스 파일**에 있어야 한다! |
| |
| <sxh scala> |
| object DBConnection { |
| private val url = "jdbc://localhost" |
| private val user = "franken" |
| private val password = "berry" |
| |
| def apply() = new DBConnection() |
| } |
| |
| class DBConnection { |
| private val props = Map( |
| "url" -> DBConnection.url, |
| "user" -> DBConnection.user, |
| "password" -> DBConnection.password |
| ) |
| } |
| |
| // val connection = DBConnection() |
| </sxh> |
| |
| |
| === case class === |
| |
| * 자동으로 유용한 Method들 생성. (Kotlin의 'data class'와 동일한 개념) |
| * Data를 관리하는데 유용. 즉, DTO로 유용. |
| * 자동으로 [[language:scala#companion object|Companion Object]] 생성. "new" 필요하지 않음. |
| |
| * 기본적으로 생성자의 Parameter들은 'val'. |
| |
| * Compile하면 '**[소스이름].class(class)**', '**[소스이름]$.class(object)**'이 생성된다. |
| |
| |
| * 자동으로 생성되는 Methods |
| |
| ^ 이름 ^ 위치 ^ 설명 ^ |
| | apply | object |case class를 Instance로 만드는 Factory method | |
| | unapply | object |Instance의 field들을 Tuple로 추출하여 **[[language:scala#match|Pattern Matching]]**에 case class instance를 사용할 수 있도록 함 | |
| | copy | class | **Deep copy!** | |
| | equals | class | Reference가 아닌 구조적 동등성(모든 필드가 일치)으로 비교. '=='로도 호출 할 수 있다. | |
| | hashCode | class | Instance의 field들의 Hash code 반환. hash 기반 collections에서 동등성에 이용. | |
| | toString | class | Field들을 String으로 전환 | |
| |
| |
| <sxh scala> |
| object Practice extends App { |
| val person = Person("Luke", 33) |
| val copy = person.copy() |
| // Equals? |
| println(person == copy) |
| |
| person.name = "James" |
| person.age = 11 |
| // Deep copy? |
| println(copy) |
| |
| // Pattern Matching |
| val p = person match { |
| case Person(_, 11) => "Original!" |
| case Person(_, 33) => "Copy!" |
| } |
| println(p) |
| } |
| |
| case class Person(var name: String, var age: Int) |
| </sxh> |
| |
| * Reference |
| * https://docs.scala-lang.org/overviews/scala-book/case-classes.html |
| * https://www.oreilly.com/library/view/scala-cookbook/9781449340292/ch04s15.html |
| |
| |
| |
| === Composition and Inheritance ==== |
| |
| ==== Override ==== |
| Java의 경우 1.5 기준으로 @Override를 명시할 수 있으나 필수사항이 아닌데 반면, Scala는 강제한다. 이로 인해 "우연한 Override"로 "fragile base class"가 생성되는 문제를 줄여준다. |
| |
| <sxh scala> |
| class Person (val name: String, val age: Int) { |
| override def toString: String = name + " / " + age |
| } |
| </sxh> |
| |
| |
| ==== Abstract Class ==== |
| Java와 비교하여 |
| |
| * method에 abstract를 붙이면 안된다. |
| * abstract는 class 혹은 abstract class를 mix-in한 trait의 method만 붙일 수 있다. |
| * 본체가 없으면 abstract method로 인식한다. (이는 trait도 동일) |
| * 본체가 있는 concrete method를 구현할 수 있다. |
| * **Field가 파라미터 없는 메소드를 Override 할 수 있다.** |
| |
| <sxh scala ; highlight:[2, 10]> |
| abstract class Abstract { |
| /* abstract가 필요하지 않다. */ |
| def values: Array[Int] |
| def sum = values.sum |
| } |
| |
| class Detail(conts: Array[Int]) extends Abstract { |
| /* 아래 동작은 같다. */ |
| //override def values: Array[Integer] = conts |
| val values: Array[Int] = conts |
| } |
| </sxh> |
| |
| ==== Parameter field 정의 ==== |
| 같은 이름의 생성자 Paramter와 Field를 동시 정의하는 단축 표기. 특정 Field에 할당하는 이름 중복을 피하기 위한 Boilerplace 제거. |
| |
| <sxh scala ; highlight:[12]> |
| abstract class Abstract { |
| def values: Array[Int] |
| } |
| |
| // 아래와 같이 같은 목적이지만 중복된 이름을 피하기 위해 불필요한 부분이 작성된다. |
| // 할당과 동시에 바로 getter 생성 |
| class Detail(vals: Array[Int]) extends Abstract { |
| override def values: Array[Int] = vals |
| } |
| |
| // 이를 아래와 같이 작성할 수 있음. |
| class Detail2(var values: Array[Int]) extends Abstract |
| </sxh> |
| |
| ==== Super class의 Constuctor 호출 ==== |
| Java의 경우 상속을 받으면 반드시 부모 Class의 생성자와 같은 형태의 생성자를 호출하고 super()를 하는 과정을 아래와 같이 간략화할 수 있다. |
| |
| <sxh scala ; highlight:[14]> |
| abstract class Abstract { |
| def values: Array[Int] |
| def sum = values.sum |
| } |
| |
| class Detail(val values: Array[Int]) extends Abstract |
| /* |
| class Detail(vals: Array[Int]) extends Abstract { |
| override def values: Array[Int] = vals |
| } |
| */ |
| |
| /* Super class(Detail)의 생성자 호출. */ |
| class MoreDetail(x: Int) extends Detail(Array(x)) { |
| } |
| </sxh> |
| |
| |
| = Trait = |
| Java의 Interface의 역할을 하는 한 단위. |
| |
| * 두 가지를 제외하고 Class가 할 수 있는 일을 다 할 수 있다. (Field 정의, 본체가 있는 concrete method 구현등등) |
| * 할 수 없는 두 가지 : 생성자 Parameter 구현, super를 이용한 Method 정적 바인딩 |
| * Mix-in 대상은 Trait뿐만이 아니라 Class, Abstract Class 모두 다 된다. |
| * 첫 번째 상속/구성할 대상이 Class든 Trait든 무조건 extends를 사용한다. 단, Class와 Trait를 혼합사용(Mix-in)할 경우 Class를 먼저 기술한다. 이후 Trait를 with를 이용하여 Mix-in 한다. |
| * 즉, 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 = "Hello, world!" |
| def func() = println(greeting) |
| } |
| |
| class C1 { |
| } |
| |
| class D extends C1 with T1 with T2 { |
| override def func(): Unit = println("안녕, 세상아!") |
| } |
| </sxh> |
| |
| |
| == 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) |
| } |
| </sxh> |
| |
| |
| === 여러 Trait를 mix-in 했을 때 우선 순위 === |
| |
| 가장 오른쪽에 있는 Trait의 효과부터 적용된다. |
| |
| <sxh scala> |
| trait Base { |
| override def toString: String = "Base" |
| } |
| |
| |
| trait A extends Base { |
| override def toString: String = s"A->${super.toString}" |
| } |
| |
| trait B extends Base { |
| override def toString: String = s"B->${super.toString}" |
| } |
| |
| trait C extends Base { |
| override def toString: String = s"C->${super.toString}" |
| } |
| |
| |
| class MixIn extends A with B with C { |
| override def toString: String = s"MixIn->${super.toString}" |
| } |
| |
| // println(new MixIn()) |
| /// MixIn->C->B->A->Base |
| </sxh> |
| |
| |
| = Type parameterization = |
| |
| == Bounded Type == |
| |
| Type Parameter를 특정 Class나 거의 Sub type 또는 Base type으로 **<fc red>제한</fc>**하는 방법. |
| |
| === Upper bound === |
| |
| Type을 Base type 또는 Sub type 중 하나로 제한한다. |
| |
| https://docs.scala-lang.org/tour/upper-type-bounds.html |
| |
| <sxh> |
| <identifier> <: <upper bound type> |
| </sxh> |
| |
| <sxh scala ; highlight:[8]> |
| object Practice extends App { |
| def check[A <: Parent](u: A): Unit = { |
| println(s"${u.name}") |
| } |
| |
| // Runtime Error |
| // inferred type arguments [GrandParent] do not conform to method check's type parameter bounds [A <: Parent] |
| check(new GrandParent("Jake")) |
| |
| check(new Parent("Fred")) |
| check(new Child("Mike")) |
| } |
| |
| class GrandParent(val name: String) |
| class Parent(name: String) extends GrandParent(name) |
| class Child(name: String) extends Parent(name) |
| </sxh> |
| |
| === Lower bound === |
| |
| Type을 해당 Type으로 제한하거나 또는 해당 Type이 확장되는 Base type 중 하나로 제한한다. |
| |
| <sxh> |
| <identifier> >: <lower bound type> |
| </sxh> |
| |
| |
| == Type variance == |
| |
| Type parameter를 **<fc red>덜 제한적</fc>**으로 만드는 방법. |
| 타입 가변성(Type variance)은 Type parameter가 Base type이나 Sub type을 충족하도록 적응하는 방법을 지정한다. |
| |
| |<100%>| |
| ^ Scala ^ Java ^ 설명 ^ 예시 ^ |
| | +T | ? extends T | Covariance (공변성) | **X[T<sub>sub</sub>]**는 **X[T]**의 Sub type | |
| | -T | ? super T | Contravariance (반공변성) | **X[T<sup>super</sup>]**는 **X[T]**의 Sub type | |
| | T | T | Invariance (무공변성) | **X[T]**는 **X[T]**. X[T<sub>sub</sub>] 혹은 X[T<sup>super</sup>]가 X[T]를 대체할 수 없음. | |
| |
| * Effective Java에서 나오는 [[https://www.oracle.com/technetwork/server-storage/ts-5217-159242.pdf|PECS (Producer extends, Consumer super)]] 원리와 같은 개념. |
| * Producer는 getter, Consumer는 setter |
| * Scala의 [[https://www.scala-lang.org/api/2.12.1/scala/Function1.html|Function1]] 혹은 Java의 [[https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html|Function]]을 보면 쉽게 유추 가능. |
| * 첫번째 인자 **-T1**는 Producer, 두번째 인자 **+R**는 반환값으로 Consumer. |
| * 반환해야 하는(생산하는) 값이 Super type인데, 인자로 받은(제공하는) 값이 Sub type이면 Super type의 Method를 호출할 수 없음. |
| * 이런 이유로 T1은 동등한 레벨의 타입이거나 상위 타입(Super type)이어야 한다. |
| * "상위 추상화 타입 -> 하위 구체화 타입" |
| |
| <sxh scala> |
| // https://github.com/deanwampler/programming-scala-book-code-examples/blob/master/src/main/scala/progscala3/objectsystem/variance/FunctionVariance.scala |
| |
| class CSuper { def msuper() = println("CSuper") } |
| class C extends CSuper { def m() = println("C") } |
| class CSub extends C { def msub() = println("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를 받도록 타입을 정의했는데, C를 상속받은 CSub를 인자로 받는다면 |
| * C가 알지못하는 Method가 호출될 수 있다! |
| */ |
| val f5: C => C = (c: CSub) => new CSuper |
| } |
| </sxh> |
| |
| |
| |
| = Abstract types = |
| |
| https://docs.scala-lang.org/tour/abstract-type-members.html |
| |
| <sxh scala ; highlight:[7,8,13,15,21,23]> |
| import java.io._ |
| import scala.io.Source |
| |
| // https://github.com/deanwampler/programming-scala-book-code-examples/blob/master/src/main/scala/progscala3/typelessdomore/AbstractTypes.scala |
| // 위 소스를 응용한 예제 |
| abstract class BulkReader { |
| type In // 구체적인 타입을 지정하지 않음. |
| val source: In // 바로 위에서 지정한 Type인 "In"을 사용 |
| 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() |
| } |
| } |
| </sxh> |
| |
| == 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 된 함수의 인자를 명시적으로 지정하지 않아도 자신의 네임스페이스의 기본값을 사용하는 방법. 변수나 함수 매개변수에 "implicit"를 앞에 붙인다. |
| |
| "Dependency Injection"의 시각으로 볼 수 있다. |
| |
| |
| <sxh scala> |
| object Printer { |
| def print(num: Double)(implicit format: String) = println(format.format(num)) |
| } |
| |
| // fmt가 주입된다. |
| object Practice extends App { |
| implicit val fmt = "%.7f" |
| Printer.print(1.11) |
| } |
| </sxh> |
| |
| |
| == Implicit Class == |
| |
| 암시적 Type 변환. 어느 Instance의 Type이 특정 Instance의 Type인 것처럼 자동 전환하여 그 특정 Instance의 Method를 가진 것처럼 보이게 할 수 있다. |
| |
| C#, Kotlin의 Extension Methods과 비슷한 기능. |
| |
| 서로를 고려하지 않고 독립적으로 개발된 소프트웨어/라이브러리를 하나로 묶으려고 할 때 사용. |
| |
| * 생성 규칙 |
| * object/class/trait 안에 정의되어야 한다. |
| * Implicit를 선언하지 않는 인자를 받아야 한다. |
| * 같은 namespace에서 object/class/trait 이름이 같아서는 안된다. |
| * case Class는 사용할 수 없다. (자동으로 생성되는 Companion Object 때문) |
| |
| <sxh scala ; title:Case1_문제제기 ; highlight:[7,8]> |
| 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> |
| |
| <sxh scala ; title:Case1에_대한_해결_방법 ; highlight:[2,16]> |
| 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) |
| } |
| </sxh> |
| |
| |