Skip to content

TheOpenCloudEngine/coding-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

5 Commits
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Scala

์ด ๊ฐ€์ด๋“œ๋ผ์ธ์€ ์—”์ง€๋‹ˆ์–ด๋ง ํŒ€ ๋ฐ ๊ด‘๋ฒ”์œ„ํ•œ ์˜คํ”ˆ ์†Œ์Šค ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ๊ฒฝํ—˜์„ ๋ฐ”ํƒ•์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ๋Š” ์ €์ž์— ์˜ํ•ด ํ•œ ๋ฒˆ ์“ฐ์—ฌ์ง€์ง€๋งŒ, ๋งŽ์€ ๋‹ค๋ฅธ ์—”์ง€๋‹ˆ์–ด๋“ค์€ ๊ทธ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ์ˆ˜์ •ํ•˜๊ณ  ์ฝ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๋ฒ„๊ทธ๋“ค์€ ๋ณดํ†ต ์ฝ”๋“œ์˜ ๋ณ€๊ฒฝ์œผ๋กœ๋ถ€ํ„ฐ ๋‚˜์˜ต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€ ๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์šฐ๋ฆฌ์˜ ์ฝ”๋“œ๋ฅผ ์ตœ์ ํ™” ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•œ ์ตœ์„ ์˜ ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Scala๋Š” ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๋ฉฐ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํŽ˜๋Ÿฌ๋‹ค์ž„์— ์ ์šฉ ๊ฐ€๋Šฅํ•œ ์–ธ์–ด์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์•„๋ž˜์˜ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ํ†ตํ•ด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ”„๋กœ์ ํŠธ๋ฅผ ๋น ๋ฅธ ์†๋„๋กœ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํŒ€์ด๋‚˜ ํšŒ์‚ฌ์˜ ์š”๊ตฌ์‚ฌํ•ญ ๋“ฑ์— ๋”ฐ๋ผ์„œ ์ผ๋ถ€ ๋‹ค๋ฅด๊ฒŒ ์ ์šฉ ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชฉ์ฐจ

  1. ๋ฌธ์„œ ์—ญ์‚ฌ
  2. ๊ตฌ๋ฌธ ์Šคํƒ€์ผ
  3. Scala ์–ธ์–ด์˜ ๊ธฐ๋Šฅ
  4. ๋™์‹œ์„ฑ ์ œ์–ด
  5. ์„ฑ๋Šฅ
  6. Java ํ˜ธํ™˜์„ฑ
  7. ํ…Œ์ŠคํŠธ
  8. ๊ธฐํƒ€

๊ตฌ๋ฌธ ์Šคํƒ€์ผ

๋ช…๋ช… ๊ทœ์น™

์šฐ๋ฆฌ๋Š” ์ฃผ๋กœ Java์™€ Scala์˜ ํ‘œ์ค€ ๋ช…๋ช… ๊ทœ์น™์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

  • Class, trait, ๊ฐ์ฒด๋Š” ๋ช…๋ช…๊ทœ์น™ ์ฆ‰ ๋‚™ํƒ€๋“ฑ ํ‘œ๊ธฐ๋ฒ•(PascalCase) ์„ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    class ClusterManager
    
    trait Expression
  • Package๋Š” Java์˜ ๋ช…๋ช… ๊ทœ์น™์„ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋‘ ์†Œ๋ฌธ์ž๋กœ ASCII ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

    package com.databricks.resourcemanager
  • ๋ฉ”์†Œ๋“œ/ํ•จ์ˆ˜๋Š” ๋‚™ํƒ€๋“ฑ ํ‘œ๊ธฐ๋ฒ• (camelCase)์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ชจ๋“  ์ƒ์ˆ˜๋Š” ๋Œ€๋ฌธ์ž๋กœ ํ‘œ๊ธฐ ํ•˜๊ณ , ์—ฐ๊ด€๋œ ๊ฐ์ฒด์— ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.

    object Configuration {
      val DEFAULT_PORT = 10000
    }
  • Enumeration ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๋Š” ์—ด๊ฑฐํ˜• ํด๋ž˜์Šค ํ˜น์€ ๊ฐ์ฒด(object)๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ, ํด๋ž˜์Šค ์ด๋ฆ„์€ ๋‚™ํƒ€๋“ฑ ํ‘œ๊ธฐ๋ฒ• (PascalCase)์œผ๋กœ ์“ฐ๊ณ , ์—ด๊ฑฐํ˜• ๊ฐ’๋“ค์€ ๋ฐ‘์ค„ ๋ฌธ์ž _ ๋กœ ๊ตฌ๋ถ„๋œ ๋‹จ์–ด๋ฅผ ๋Œ€๋ฌธ์ž๋กœ ์จ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค:

      private object ParseState extends Enumeration {
      type ParseState = Value
    
      val PREFIX,
          TRIM_BEFORE_SIGN,
          SIGN,
          TRIM_BEFORE_VALUE,
          VALUE,
          VALUE_FRACTIONAL_PART,
          TRIM_BEFORE_UNIT,
          UNIT_BEGIN,
          UNIT_SUFFIX,
          UNIT_END = Value
    }
  • Annotation ๋˜ํ•œ ๋‚™ํƒ€๋“ฑ ํ‘œ๊ธฐ๋ฒ• (PascalCase)์„ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ๋ผ์ธ์ด Scala์˜ ๊ณต์‹ ๊ฐ€์ด๋“œ๋ผ์ธ๊ณผ ๋‹ค๋ฆ„์„ ์ฃผ์˜ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

    final class MyAnnotation extends StaticAnnotation

๋ณ€์ˆ˜ ๋ช…๋ช… ๊ทœ์น™

  • ๋ณ€์ˆ˜๋Š” ๋‚™ํƒ€๋“ฑ ํ‘œ๊ธฐ๋ฒ• (PascalCase)์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ , ๋ช…๋ฐฑํžˆ ๋ณ€์ˆ˜์˜ ์˜๋ฏธ๊ฐ€ ์„ค๋ช… ๋  ์ˆ˜ ์žˆ๋Š” ์ž๋ช…ํ•œ ์ด๋ฆ„์„ ์‚ฌ์šฉ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    val serverPort = 1000
    val clientPort = 2000sca
  • ์ง€์—ฝ์ ์ธ ๊ณต๊ฐ„์—์„œ ๋ณ€์ˆ˜ ์ด๋ฆ„์ด ํ•˜๋‚˜์˜ ๊ธ€์ž๋กœ ๋ช…๋ช… ๋˜๋Š” ๊ฒƒ์€ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, "i" ๋Š” ๊ธธ์ง€ ์•Š์€ ์ˆœํ™˜๋ฌธ ์—์„œ (์˜ˆ๋ฅผ ๋“ค์–ด, 10 ๋ผ์ธ์˜ ์ฝ”๋“œ) ๊ทธ ์ˆœํ™˜๋ฌธ ์•ˆ์—์„œ์˜ ์ธ๋ฑ์Šค๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ์ž์ฃผ ์‚ฌ์šฉ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, "l" (Larry์˜ ๋งจ ์•ž์ž)๋ฅผ ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด, "l", "1", "|" ๊ทธ๋ฆฌ๊ณ  "I" ์€ ๊ตฌ๋ถ„ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ ์ž…๋‹ˆ๋‹ค.

๋ผ์ธ ๊ธธ์ด

  • ๋ผ์ธ ๊ธธ์ด๋Š” 100์ž๋ฅผ ๋„˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋‹จ, import๋‚˜ URL์˜ ๊ฒฝ์šฐ๋Š” ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค. (๊ทธ๋ ‡๋‹ค ํ•˜๋”๋ผ๋„ 100์ž์˜ ์ œ์•ฝ์„ ์ง€์ผœ์ฃผ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค).

30 ๊ทœ์น™

"ํ•œ ๊ฐœ์˜ ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ 30๊ฐœ ์ด์ƒ์˜ ํ•˜์œ„ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ํฌํ•จ ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค." - Refactoring in Large Software Projects.

์ผ๋ฐ˜์ ์œผ๋กœ:

  • ํ•จ์ˆ˜๋Š” 30์ค„ ์ด์ƒ์˜ ๋ผ์ธ์„ ์ดˆ๊ณผํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋‹น 30๊ฐœ ์ด์ƒ์˜ ํ•จ์ˆ˜๋ฅผ ๊ฐ–์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๊ณต๋ฐฑ ๋ฐ ๋“ค์—ฌ์“ฐ๊ธฐ

  • ์—ฐ์‚ฐ์ž ๋ฐ ํ• ๋‹น ์—ฐ์‚ฐ์ž ์•ž ๋’ค์—๋Š” 1์นธ ๊ณต๋ฐฑ์„ ๋‘๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    def add(int1: Int, int2: Int): Int = int1 + int2
  • ์ฝค๋งˆ ๋’ค์—๋Š” 1์นธ ๊ณต๋ฐฑ์„ ๋‘๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    Seq("a", "b", "c") // ์ด์™€ ๊ฐ™์ด ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    
    Seq("a","b","c") // ์ฝค๋งˆ ๋’ค์—๋Š” ๊ณต๋ฐฑ์„ ์ƒ๋žตํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ฝœ๋ก  ๋’ค์—๋Š” 1์นธ ๊ณต๋ฐฑ์„ ๋‘๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    // ์•„๋ž˜ ์˜ˆ์™€ ๊ฐ™์ด ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    def getConf(key: String, defaultValue: String): String = {
      // ์ฝ”๋“œ
    }
    
    // ์ฝœ๋ก  ์•ž์—๋Š” ๊ณต๋ฐฑ์„ ๋‘์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    def calculateHeaderPortionInBytes(count: Int) : Int = {
      // ์ฝ”๋“œ
    }
    
    // ์ฝœ๋ก  ๋’ค์—๋Š” ๊ณต๋ฐฑ์„ ์ƒ๋žตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    def multiply(int1:Int, int2:Int): Int = int1 * int2
  • 2์นธ ๊ณต๋ฐฑ ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.

    if (true) {
      println("Wow!")
    }
  • ํ•จ์ˆ˜ ์„ ์–ธ์—์„œ ํŒŒ๋ผ๋ฉ”ํ„ฐ๊ฐ€ ๋‘ ์ค„์— ๋งž์ง€ ์•Š์•„ ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ํ•˜๋Š” ๊ฒฝ์šฐ, ๊ฐ ์ธ์ž์— 4์นธ ๊ณต๋ฐฑ์„ ์‚ฌ์šฉํ•˜๊ณ  ๊ฐ ๋ผ์ธ์— ๋ฐฐ์น˜ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜ ํƒ€์ž…์€ ๋‹ค์Œ ์ค„์— ๋ฐฐ์น˜๋˜๊ฑฐ๋‚˜ ๊ฐ™์€ ๋ผ์ธ์— ๋ฐฐ์น˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ผ์ธ์— ์“ฐ๋Š” ๊ฒฝ์šฐ, 2์นธ ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.

    def newAPIHadoopFile[K, V, F <: NewInputFormat[K, V]](
        path: String,
        fClass: Class[F],
        kClass: Class[K],
        vClass: Class[V],
        conf: Configuration = hadoopConfiguration): RDD[(K, V)] = {
      // method body
    }
    
    def newAPIHadoopFile[K, V, F <: NewInputFormat[K, V]](
        path: String,
        fClass: Class[F],
        kClass: Class[K],
        vClass: Class[V],
        conf: Configuration = hadoopConfiguration)
      : RDD[(K, V)] = {
      // method body
    }
  • ํด๋ž˜์Šค์˜ ํ•ด๋”๊ฐ€ ๋‘ ์ค„์— ๋งž์ง€ ์•Š์„ ๋•Œ๋Š”, ๊ฐ ์ธ์ž์— 4์นธ ๊ณต๋ฐฑ์„ ์‚ฌ์šฉํ•˜๊ณ  ๊ฐ ๋ผ์ธ์— ๋ฐฐ์น˜ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, extends๋ฅผ 2์นธ ๊ณต๋ฐฑ ๋’ค์— ๋ฐฐ์น˜ํ•˜๊ณ , ๊ทธ ๋’ค์— ํ•œ ๊ฐœ์˜ ๋นˆ ์ค„์„ ์ž…๋ ฅ ํ•ฉ๋‹ˆ๋‹ค.

    class Foo(
        val param1: String,  // 4 space indent for parameters
        val param2: String,
        val param3: Array[Byte])
      extends FooInterface  // 2 space here
      with Logging {
    
      def firstMethod(): Unit = { ... }  // blank line above
    }
  • ํ•จ์ˆ˜์™€ ํด๋ž˜์Šค ์ƒ์„ฑ์ž ํ˜ธ์ถœ์ด ๋‘ ์ค„์— ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋Š”, ๊ฐ ์ธ์ž์— 2์นธ ๊ณต๋ฐฑ์„ ์‚ฌ์šฉํ•˜๊ณ  ๊ฐ ๋ผ์ธ์— ๋ฐฐ์น˜ ํ•ฉ๋‹ˆ๋‹ค.

    foo(
      someVeryLongFieldName,  // 2 space indent here
      andAnotherVeryLongFieldName,
      "this is a string",
      3.1415)
    
    new Bar(
      someVeryLongFieldName,  // 2 space indent here
      andAnotherVeryLongFieldName,
      "this is a string",
      3.1415)
  • ์ˆ˜์ง ์ •๋ ฌ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ค‘์š”์น˜ ์•Š์€ ์ฝ”๋“œ์— ์ง‘์ค‘ํ•˜๊ฒŒ ํ•˜๊ณ , ์ฐจํ›„์— ์ฝ”๋“œ ์ˆ˜์ •์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    // Don't align vertically
    val plus     = "+"
    val minus    = "-"
    val multiply = "*"
    
    // Do the following
    val plus = "+"
    val minus = "-"
    val multiply = "*"

๋นˆ ์ค„

  • ๋นˆ ์ค„์€ ์•„๋ž˜์˜ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:
    • ์—ฐ์†๋˜๋Š” ๋ณ€์ˆ˜, ์ƒ์„ฑ์ž, ํ•จ์ˆ˜ ๋˜๋Š” ๋‚ด๋ถ€ ํด๋ž˜์Šค๋“ค ์‚ฌ์ด ๋นˆ ์ค„์ด ์‚ฝ์ž… ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • ์˜ˆ์™ธ: ์—ฐ์†๋˜๋Š” ๋ณ€์ˆ˜ ์„ ์–ธ ์‚ฌ์ด ์•„๋ฌด๋Ÿฐ ์ฝ”๋“œ๋„ ์—†๋‹ค๋ฉด ๋นˆ ์ค„์€ ์˜ต์…˜์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋นˆ ์ค„๋“ค์€ ๋…ผ๋ฆฌ์ ์ธ ๊ทธ๋ฃน์„ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํ•จ์ˆ˜ ์•ˆ์—์„œ ๋นˆ ์ค„์„ ์‚ฝ์ž…ํ•˜์—ฌ ๋…ผ๋ฆฌ์ ์ธ ๊ทธ๋ฃน์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ฒซ ๋ฒˆ์งธ ๋งด๋ฒ„ ์•ž์ด๋‚˜ ๋งˆ์ง€๋ง‰ ๋งด๋ฒ„ ๋’ค์— ๋นˆ ์ค„์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•œ๊ฐœ ๋˜๋Š” ๋‘๊ฐœ์˜ ๋นˆ ์ค„์„ ์‚ฌ์šฉํ•˜์—ฌ class ํ˜น์€ object ์„ ์–ธ๋“ค์„ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๊ณผ๋„ํ•œ ์ˆ˜์˜ ๋นˆ ์ค„์€ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ด„ํ˜ธ

  • I/O ์ ‘๊ทผ์ด๋‚˜ ์ƒํƒœ ๋ณ€ํ˜•์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ๊ฐ–๊ณ  ์žˆ๊ฑฐ๋‚˜ side-effect๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋Š” ๊ด„ํ˜ธ์™€ ํ•จ๊ป˜ ์„ ์–ธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    class Job {
      // Wrong: killJob changes state. Should have ().
      def killJob: Unit
    
      // Correct:
      def killJob(): Unit
    }
  • ํ•จ์ˆ˜ ํ˜ธ์ถœ์ž๋Š” ๋ฐ˜๋“œ์‹œ ํ•จ์ˆ˜์˜ ์ •์˜๋ฅผ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•จ์ˆ˜๊ฐ€ ๊ด„ํ˜ธ ์—†์ด ์„ ์–ธ๋˜์—ˆ๋‹ค๋ฉด ๊ด„ํ˜ธ ์—†์ด ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ๋‹จ์ง€ ๋ฌธ๋ฒ•์ ์ธ ๋ฌธ์ œ์ผ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ apply๋ฅผ ํ˜ธ์ถœ ํ•  ๋•Œ์—๋„ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    class Foo {
      def apply(args: String*): Int
    }
    
    class Bar {
      def foo: Foo
    }
    
    new Bar().foo  // This returns a Foo
    new Bar().foo()  // This returns an Int!

์ค‘๊ด„ํ˜ธ

ํ•œ ์ค„ ์กฐ๊ฑด๋ถ€ ์‹์ด๋‚˜ ์ˆœํ™˜๋ฌธ์—๋„ ์ค‘๊ด„ํ˜ธ๋ฅผ ๋„ฃ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, if/else๋ฌธ์˜ ๊ฒฝ์šฐ์—๋Š” ํ•œ ์ค„๋กœ ํ‘œ๊ธฐ ํ•˜๊ฑฐ๋‚˜, side-effect๊ฐ€ ์—†๋Š” 3ํ•ญ ์—ฐ์‚ฐ์ž๋กœ ํ‘œ๊ธฐ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Correct:
if (true) {
  println("Wow!")
}

// Correct:
if (true) statement1 else statement2

// Correct:
try {
  foo()
} catch {
  ...
}

// Wrong:
if (true)
  println("Wow!")

// Wrong:
try foo() catch {
  ...
}

Long ์ •์ˆ˜

Long ์ •์ˆ˜์˜ ์ ‘๋ฏธ์‚ฌ๋Š” L๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ€๋” l๊ณผ 1์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ํž˜๋“ค ๋•Œ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

val longValue = 5432L  // Do this

val longValue = 5432l  // Do NOT do this

๋ฌธ์„œ ์Šคํƒ€์ผ

Scala ์ฃผ์„ ์Šคํƒ€์ผ ๋Œ€์‹  Java ์ฃผ์„ ์Šคํƒ€์ผ์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

/** This is a correct one-liner, short description. */

/**
 * This is correct multi-line JavaDoc comment. And
 * this is my second line, and if I keep typing, this would be
 * my third line.
 */

/** In Spark, we don't use the ScalaDoc style so this
  * is not correct.
  */

ํด๋ž˜์Šค ๋‚ด์˜ ์ˆœ์„œ

๋งŒ์•ฝ Class์˜ ์ •์˜๊ฐ€ ๊ธธ๊ณ  ๋งŽ์€ ํ•จ์ˆ˜๋“ค์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ถ„ํ•  ํ•˜๊ณ , ์•„๋ž˜์™€ ๊ฐ™์€ ์ฃผ์„ ํ—ค๋”๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌ๋ถ„ ํ•ฉ๋‹ˆ๋‹ค.

class DataFrame {

  ///////////////////////////////////////////////////////////////////////////
  // DataFrame operations
  ///////////////////////////////////////////////////////////////////////////

  ...

  ///////////////////////////////////////////////////////////////////////////
  // RDD operations
  ///////////////////////////////////////////////////////////////////////////

  ...
}

๋ฌผ๋ก , ์ด ์˜ˆ์™€ ๊ฐ™์€ Class์˜ ๊ธธ์ด๋Š” ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๋‚ด๋ถ€ ๊ตฌํ˜„์ด ์•„๋‹Œ ๊ณต๊ฐœ๋˜์–ด์žˆ๋Š” API๋ฅผ ๋งŒ๋“ค ๋•Œ ์œ„์™€ ๊ฐ™์€ ํ˜•์‹ ์‚ฌ์šฉ ๋ฉ๋‹ˆ๋‹ค.

Imports

  • ์™€์ผ๋“œ ์นด๋“œ๋ฅผ ์ด์šฉํ•œ import๋Š” ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, 6๊ฐœ ์ด์ƒ ๊ฐ™์€ ํŽ˜ํ‚ค์ง€์—์„œ importํ•˜๋Š” ๊ฒฝ์šฐ ํ˜น์€ implicit ํ•จ์ˆ˜๋“ค์„ importํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ํ—ˆ์šฉ ๋ฉ๋‹ˆ๋‹ค. ์™€์ผ๋“œ์นด๋“œ import๋Š” ์™ธ๋ถ€(import ๋œ ํŽ˜ํ‚ค์ง€)์˜ ๋ณ€ํ™”์— ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • import๋ฅผ ํ•  ๋•Œ๋Š” ์ƒ๋Œ€ ๊ฒฝ๋กœ๊ฐ€ ์•„๋‹Œ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ƒ๋Œ€๊ฒฝ๋กœ util.Random ๊ฐ€ ์•„๋‹Œscala.util.Random ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ๋˜ํ•œ, import๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์ˆœ์„œ๋กœ ์ •๋ ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

    • java.* ์™€ javax.*
    • scala.*
    • Third-party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (org.*, com.*, etc)
    • ํ”„๋กœ์ ํŠธ ํŽ˜ํ‚ค์ง€ (com.databricks.* ํ˜น์€ Spark์—์„œ ์ž‘์—…ํ•˜๋Š” ๊ฒฝ์šฐ org.apache.spark)
  • ๊ฐ๊ฐ์˜ ๊ทธ๋ฃน ์•ˆ์—์„œ, import๋Š” ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ๋กœ ์ •๋ ฌ ํ•ฉ๋‹ˆ๋‹ค.

  • IntelliJ์˜ import ์ตœ์ ํ™” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ config๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค:

    java
    javax
    _______ blank line _______
    scala
    _______ blank line _______
    all other imports
    _______ blank line _______
    com.databricks  // or org.apache.spark if you are working on Spark
    

ํŒจํ„ด ๋งค์นญ

  • ํ•จ์ˆ˜ ์ „์ฒด๊ฐ€ ํŒจํ„ด ๋งค์นญ์„ ํ•˜๋Š” ํ•จ์ˆ˜๋ผ๋ฉด match ๋ฅผ ํ•จ์ˆ˜์˜ ์ •์˜๋กœ์จ ๊ฐ™์€ ์ค„์— ๋†“์Šต๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์ด ๋“ค์—ฌ์“ฐ๊ธฐ์˜ ๋ ˆ๋ฒจ์„ ํ•œ๋‹จ๊ณ„ ์ค„์ด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    def test(msg: Message): Unit = msg match {
      case ...
    }
  • ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ ํ•  ๋•Œ, ์•„๋ž˜์™€ ๊ฐ™์€ ์ค‘๊ด„ํ˜ธ ์•ˆ์— (ํ˜น์€ partial function ์•ˆ์—) ํ•œ ๊ฐœ์˜ case ๋งŒ ์žˆ๋‹ค๋ฉด, ๊ฐ™์€ ์ค„์— ๋„ฃ์–ด ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ํ•ฉ๋‹ˆ๋‹ค.

    list.zipWithIndex.map { case (elem, i) =>
      // ...
    }

    ๋งŒ์•ฝ ์—ฌ๋Ÿฌ ๊ฐœ์˜ case ๋ฌธ์ด ์กด์žฌํ•œ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.

    list.map {
      case a: Foo =>  ...
      case b: Bar =>  ...
    }
  • ๋งŒ์•ฝ ์–ด๋–ค ๊ฐ์ฒด์˜ ํƒ€์ž…์„ ํŒจํ„ด ๋งค์นญ ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ๋ผ๋ฉด, ์ „์ฒด ์ธ์ž๋ฅผ ํ™•์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด, ์ด ๊ฒƒ์€ ๋ฆฌํŽ™ํ† ๋ง์„ ๋” ํž˜๋“ค๊ฒŒ ๋งŒ๋“ค๊ณ  ์ฝ”๋“œ์˜ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    case class Pokemon(name: String, weight: Int, hp: Int, attack: Int, defense: Int)
    case class Human(name: String, hp: Int)
    
    // ์•„๋ž˜ ์˜ˆ์™€ ๊ฐ™์ด ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด,
    // 1. ์ƒˆ๋กœ์šด ํ•„๋“œ๊ฐ€ Pokemon์— ์ถ”๊ฐ€๊ฐ€ ๋  ๋•Œ, ์šฐ๋ฆฌ๋Š” ์ด ํŒจํ„ด ๋งค์นญ ๋˜ํ•œ ๋ฐ”๊ฟ”์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    // 2. ํŠนํžˆ, ๊ฐ™์€ ๋ฐ์ดํ„ฐ ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ์—ฌ๋Ÿฌ๊ฒŒ ๊ฐ–๋Š” ๊ฒฝ์šฐ, ์ธ์ž๋ฅผ ์ž˜๋ชป ๋งค์นญ ํ•˜๊ธฐ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.
    targets.foreach {
      case target @ Pokemon(_, _, hp, _, defense) =>
        val loss = sys.min(0, myAttack - defense)
        target.copy(hp = hp - loss)
      case target @ Human(_, hp) =>
        target.copy(hp = hp - myAttack)
    }
    
    // Do this:
    targets.foreach {
      case target: Pokemon =>
        val loss = sys.min(0, myAttack - target.defense)
        target.copy(hp = target.hp - loss)
      case target: Human =>
        target.copy(hp = target.hp - myAttack)
    }

์ค‘์œ„ ํ‘œ๊ธฐ

ํŠน์ˆ˜ ๋ฌธ์ž ํ•จ์ˆ˜ (symbolic methods)๋ฅผ ์ œ์™ธํ•˜๊ณ ๋Š” ์ค‘์œ„ ํ‘œ๊ธฐ๋ฅผ ํ”ผํ•ฉ๋‹ˆ๋‹ค.

// Correct
list.map(func)
string.contains("foo")

// Wrong
list map (func)
string contains "foo"

// But overloaded operators should be invoked in infix style
arrayBuffer += elem

์ต๋ช… ํ•จ์ˆ˜

์ต๋ช… ํ•จ์ˆ˜๋ฅผ ์œ„ํ•œ ์—ฌ๋ถ„์˜ ์†Œ๊ด„ํ˜ธ ๋ฐ ์ค‘๊ด„ํ˜ธ๋ฅผ ํ”ผํ•ฉ๋‹ˆ๋‹ค.

// Correct
list.map { item =>
  ...
}

// Correct
list.map(item => ...)

// Wrong
list.map(item => {
  ...
})

// Wrong
list.map { item => {
  ...
}}

// Wrong
list.map({ item => ... })

Scala ์–ธ์–ด์˜ ๊ธฐ๋Šฅ

์ผ€์ด์Šค ํด๋ž˜์Šค์™€ ๋ถˆ๋ณ€์„ฑ

์ผ€์ด์Šค ํด๋ž˜์Šค๋Š” ์ผ๋ฐ˜ ํด๋ž˜์Šค ์ž…๋‹ˆ๋‹ค๋งŒ, ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ž๋™์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ํ•ญ๋ชฉ๋“ค์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

  • ์ƒ์„ฑ์ž์˜ ํŒŒ๋ผ๋ฉ”ํ„ฐ๋“ค์„ ์œ„ํ•œ ํผ๋ธ”๋ฆญ getter๋“ค
  • ๋ณต์ œ ์ƒ์„ฑ์ž
  • ์ž๋™ toString/hash/equals ๊ตฌํ˜„

์ผ€์ด์Šค ํด๋ž˜์Šค๋ฅผ ์œ„ํ•œ ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฉ”ํ„ฐ๋“ค์€ ๊ฐ€๋ณ€์„ฑ์„ ๊ฐ–์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์‹ , ๋ณต์ œ ์ƒ์„ฑ์ž๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฐ€๋ณ€ ํŒŒ๋ผ๋ฉ”ํ„ฐ๋ฅผ ๊ฐ–๋Š” ํด๋ž˜์Šค๋“ค์€ ์˜ค๋ฅ˜์˜ ๋ฐœ์ƒ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•ด์‰ฌ๋งต์€ ๋ณ€๊ฒฝ ๋˜๊ธฐ ์ „์˜ ํ•ด์‰ฌ์ฝ”๋“œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” ์ž˜๋ชป๋œ ๋ฒ„ํ‚ท์— ๊ฐ์ฒด๋ฅผ ๋†“์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

// This is OK
case class Person(name: String, age: Int)

// This is NOT OK
case class Person(name: String, var age: Int)

// ๊ฐ’์„ ๋ฐ”๊พธ๊ธฐ ์œ„ํ•ด์„œ๋Š”, ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ณต์ œ ์ƒ์„ฑ์ž๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
val p1 = Person("Peter", 15)
val p2 = p1.copy(age = 16)

apply ํ•จ์ˆ˜

Class ์•ˆ์—์„œ์˜ apply ํ•จ์ˆ˜๋Š” ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ์ €ํ•˜ ์‹œํ‚ต๋‹ˆ๋‹ค. ํŠนํžˆ, Scala์— ์ต์ˆ™ํ•˜์ง€ ์•Š์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ๋Š” ๋”์šฑ ์ƒ์†Œ ํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ IDE๊ฐ€ ํ˜ธ์ถœ์„ ๋”ฐ๋ผ๊ฐ€๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ตœ์•…์˜ ๊ฒฝ์šฐ, ๊ด„ํ˜ธ ํ•ญ๋ชฉ์˜ ์˜ˆ์ œ์—์„œ ๋ณด์ด๋“ฏ์ด ์˜ˆ์ƒ์น˜ ๋ชป ํ•œ ๋ฐฉํ–ฅ์œผ๋กœ ์ฝ”๋“œ์˜ ์ •ํ™•์„ฑ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ–๋Š” ๊ฐ์ฒด์— ํŽ™ํ† ๋ฆฌ ํŒจํ„ด์œผ๋กœ์จ apply ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์€ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ, apply ํ•จ์ˆ˜๋Š” ๊ฐ™์€ ์ด๋ฆ„์˜ classํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

object TreeNode {
  // This is OK
  def apply(name: String): TreeNode = ...

  // This is bad because it does not return a TreeNode
  def apply(name: String): String = ...
}

override ์ˆ˜์ •์ž

ํ•ญ์ƒ ํ•จ์ˆ˜๋ฅผ ์œ„ํ•œ override ์ˆ˜์ •์ž๋Š” ์ถ”์ƒ ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋Š” ๊ฒฝ์šฐ์ด๊ฑด ์‹ค์ œ ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•˜๋Š” ๊ฒฝ์šฐ์ด๊ฑด ํ•ญ์ƒ ๋ถ™์—ฌ์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. Scala ์ปดํŒŒ์ผ๋Ÿฌ๋Š” override ์ถ”์ƒ ํ•จ์ˆ˜๋“ค์— ์žˆ์–ด์„œ๋Š” ์ˆ˜์ •์ž๋ฅผ ์š”๊ตฌํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ํ•จ์ˆ˜๋ฅผ ์œ„ํ•œ override ์ˆ˜์ •์ž๋Š” ์ถ”์ƒ ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•˜๋“  ์‹ค์ œ ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•˜๋“  ํ•ญ์ƒ ๋ถ™์—ฌ์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

trait Parent {
  def hello(data: Map[String, String]): Unit = {
    print(data)
  }
}

class Child extends Parent {
  import scala.collection.Map

  // The following method does NOT override Parent.hello,
  // because the two Maps have different types.
  // If we added "override" modifier, the compiler would've caught it.
  def hello(data: Map[String, String]): Unit = {
    print("This is supposed to override the parent method, but it is actually not!")
  }
}

ํŠœํ”Œ ์ถ”์ถœ

ํŠœํ”Œ ์ถ”์ถœ์€ (๋ฐ”์ธ๋”ฉ ์ œ๊ฑฐ) ๋‘ ๊ฐœ์˜ ๋ณ€์ˆ˜๋ฅผ ํ•˜๋‚˜์˜ ํ‘œํ˜„์‹์—์„œ ์„ ์–ธ ๋ฐ ๋Œ€์ž… ํ•  ์ˆ˜ ์žˆ๋Š” ํŽธํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

val (a, b) = (1, 2)

๊ทธ๋Ÿฌ๋‚˜ ์ƒ์„ฑ์ž์—์„œ ์ด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ง์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค (ํŠนํžˆ a ์™€ b ๊ฐ€ transient์˜ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ํ‘œ๊ธฐ๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ). Scala ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์—ฌ๋ถ„์˜ Tuple2 ํ•„๋“œ๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋Š” transient๊ฐ€ ์•„๋ž˜ ์˜ˆ์ œ์—์„œ ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

class MyClass {
  // This will NOT work because the compiler generates a non-transient Tuple2
  // that points to both a and b
  @transient private val (a, b) = someFuncThatReturnsTuple2()
}

Call by Name

Call by name ์€ ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. () => T์„ ๋ช…์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ฐฐ๊ฒฝ: Scala๋Š” ํ•จ์ˆ˜์˜ ์ธ์ž๊ฐ€ by-name์œผ๋กœ ์ •์˜๋˜๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋Š” ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ ํ•ฉ๋‹ˆ๋‹ค.

def print(value: => Int): Unit = {
  println(value)
  println(value + 1)
}

var a = 0
def inc(): Int = {
  a += 1
  a
}

print(inc())

์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” inc()๊ฐ€ print์—๊ฒŒ ๊ฐ’ 1์ด ์•„๋‹Œ ํ•จ์ˆ˜ (closure) ๋กœ์จ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  print์—์„œ ๋‘ ๋ฒˆ ์‹คํ–‰์ด ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ์ ์€ ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ๋Š” call-by-name๊ณผ call-by-value๋ฅผ ๊ตฌ๋ถ„ ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์ด ํ‘œํ˜„์ด print๊ฐ€ ํ˜ธ์ถœ ๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜์—ˆ๋Š”์ง€ ์•„๋‹Œ์ง€(ํ˜น์€ ์—ฌ๋Ÿฌ๋ฒˆ ์‹คํ–‰์ด ๋  ๊ฒƒ์ด๋ผ๋Š” ๊ฒƒ ๊นŒ์ง€๋„)๋ฅผ ํ™•์‹  ํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ๊ทผ๋ณธ์ ์œผ๋กœ ์œ„ํ—˜ํ•˜๊ณ  side-effect๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์ค‘ ๋งค๊ฐœ ๋ณ€์ˆ˜ ํ‘œ๊ธฐ

๋‹ค์ค‘์˜ ๋ณ€์ˆ˜๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฌถ์–ด ํ‘œ๊ธฐํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์—ฐ์‚ฐ์ž์˜ ์˜ค๋ฒ„๋กœ๋”ฉ์„ ๋ณต์žกํ•˜๊ฒŒ ํ•˜๊ณ , Scala์— ์ต์ˆ™์น˜ ์•Š์€ ๊ฐœ๋ฐœ์ž๋“ค์„ ํ—ท๊ฐˆ๋ฆฌ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Avoid this!
case class Person(name: String, age: Int)(secret: String)

ํ•˜๋‚˜์˜ ์ฃผ์˜ํ•  ์˜ˆ์™ธ๋กœ๋Š” implicit์—์„œ ๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ •์˜ ํ•  ๋•Œ (๋ฆฌ์ŠคํŠธ๋กœ ๋ฌถ์€) ์‚ฌ์šฉ ๋˜๋Š” ๋‘๋ฒˆ์งธ ์ธ์ž ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋˜๋„๋ก์ด๋ฉด implicit์€ ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํŠน์ˆ˜ ๋ฌธ์ž ํ•จ์ˆ˜ (์˜คํผ๋ ˆ์ดํ„ฐ ์˜ค๋ฒ„๋ผ์ด๋”ฉ)

ํŠน์ˆ˜ ๋ฌธ์ž(์˜คํผ๋ ˆ์ดํ„ฐ) ๋ฅผ ํ•จ์ˆ˜ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, ์‚ฌ์น™์—ฐ์‚ฐ์— ์žˆ์–ด์„œ, ๊ธฐํ˜ธ์— ์•Œ๋งž๊ฒŒ ์ž‘์šฉ ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ๋ฅผ ๋“ค์–ด +, -, *, /). ๊ทธ ์™ธ์—๋Š” ์–ด๋–ค ํ™˜๊ฒฝ์—์„œ๋„ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉ ๋˜์–ด์„  ์•ˆ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ํ•จ์ˆ˜๋“ค์ด ์‚ฌ์šฉ ๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ฐ€๋…์„ฑ์ด ๋งค์šฐ ๋–จ์–ด์ง€๊ณ  ํ•จ์ˆ˜๋“ค์„ ์ดํ•ดํ•˜๊ธฐ ํž˜๋“ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ๋‘ ๊ฐ€์ง€ ์˜ˆ์ œ๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค:

// symbolic method names are hard to understand
channel ! msg
stream1 >>= stream2

// self-evident what is going on
channel.send(msg)
stream1.join(stream2)

ํƒ€์ž… ์ถ”๋ก 

Scala์˜ ํƒ€์ž… ์ถ”๋ก  (ํŠนํžˆ left-side ํƒ€์ž… ์ถ”๋ก ) ๊ณผ ํ•จ์ˆ˜ (closure) ์ถ”๋ก ์€ ์ฝ”๋“œ๋ฅผ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช‡ ๊ฐ€์ง€ ๊ฒฝ์šฐ๋Š” ๋ช…์‹œ์ ์ธ ํƒ€์ž…์ด ์ฃผ์–ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • Public ํ•จ์ˆ˜๋Š” ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์ด ์ฃผ์–ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ž˜๋ชป๋œ ํƒ€์ž…์„ ์ถ”๋ก  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Implicit ํ•จ์ˆ˜๋“ค์€ ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์ด ์ฃผ์–ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด Scala ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์ฆ๋ถ„ ์ปดํŒŒ์ผ์—์„œ ์‹คํŒจ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ณ€์ˆ˜ ํ˜น์€ ํƒ€์ž…์ด ์ƒ๋žต๋œ ํ•จ์ˆ˜(closure)๋Š” ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์ด ์ฃผ์–ด์ ธ์•ผํ•ฉ๋‹ˆ๋‹ค. ์ข‹์€ ๋ฆฌํŠธ๋จธ์Šค ํ…Œ์ŠคํŠธ๋Š” ๋ช…์‹œ์ ์ธ ํƒ€์ž…๋“ค์ด ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ์–ด๋“ค์ด 3์ดˆ ๋‚ด์— ํƒ€์ž…์„ ํ™•์ธ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๋Š” ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Return ์˜ˆ์•ฝ์–ด

Return์„ ํ•จ์ˆ˜(closure)์— ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. return ์€ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ scala.runtime.NonLocalReturnControl ์„ ์œ„ํ•ด try/catch ๋ฅผ ํ•˜๋„๋ก ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ปดํŒŒ์ผ๋Ÿฌ์˜ ํ–‰๋™์œผ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค:

def receive(rpc: WebSocketRPC): Option[Response] = {
  tableFut.onComplete { table =>
    if (table.isFailure) {
      return None // Do not do that!
    } else { ... }
  }
}

์ด .onComplete ํ•จ์ˆ˜๋Š” ์ต๋ช…์˜ ํ•จ์ˆ˜(closure) { table => ... }๋ฅผ ๋ฐ›๊ณ  ๊ทธ ๊ฒƒ์„ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜(closure)๋Š” ๊ฒฐ๊ตญ NonLocalReturnControl ์„ ๋‚ด๋ฟœ๊ฒŒ ๋˜๊ณ , ์ด ๊ฒƒ์€ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ ์—์„œ ์žกํžˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ์—ฌ๊ธฐ์„œ ์‹คํ–‰๋œ ํ•จ์ˆ˜์—๊ฒŒ ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋ช‡ ๊ฐ€์ง€ ๊ฒฝ์šฐ์—๋Š” return ํ‚ค์›Œ๋“œ์˜ ์‚ฌ์šฉ์ด ๊ถŒ๊ณ ๋ฉ๋‹ˆ๋‹ค.

  • return ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ, ํ•œ ๋‹จ๊ณ„์˜ ๋“ค์—ฌ์“ฐ๊ธฐ ๋ ˆ๋ฒจ์„ ์ถ”๊ฐ€ ํ•˜์ง€ ์•Š๊ณ , ํ๋ฆ„ ์ œ์–ด๋ฅผ ๋‹จ์ˆœํ™” ์‹œํ‚ต๋‹ˆ๋‹ค.

    def doSomething(obj: Any): Any = {
      if (obj eq null) {
        return null
      }
      // do something ...
    }
  • return ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ, ํ”Œ๋ž˜๊ทธ ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๊ณ , ๋ฃจํ”„๋ฅผ ์ผ์ฐ ์ข…๋ฃŒ ํ•ฉ๋‹ˆ๋‹ค.

    while (true) {
      if (cond) {
        return
      }
    }

์žฌ๊ท€ ์šฉ๋ฒ• ๊ณผ ๊ผฌ๋ฆฌ ์žฌ๊ท€ ์šฉ๋ฒ•

์žฌ๊ท€๋Š” ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, ์ด ๋ฌธ์ œ๊ฐ€ ์ž์—ฐ์ ์œผ๋กœ ์žฌ๊ท€๋กœ ํ•ด๊ฒฐ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด, ๊ทธ๋ž˜ํ”„ ์ˆœํšŒ ํ˜น์€ ํŠธ๋ฆฌ ์ˆœํšŒ).

๊ผฌ๋ฆฌ ์žฌ๊ท€ ์šฉ๋ฒ•์ด ์ ์šฉ๋˜์–ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜์— ์žˆ์–ด์„œ๋Š”, @tailrec ์–ด๋…ธํƒœ์ด์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ด ๊ฒƒ์ด ๊ผฌ๋ฆฌ ์žฌ๊ท€ ์šฉ๋ฒ•์ด ์ ์šฉ ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค (์‚ฌ์‹ค์€, ํ•จ์ˆ˜(closure)์˜ ์‚ฌ์šฉ๊ณผ functional transformation๋“ฑ ์œผ๋กœ ๋งŽ์€ ๊ผฌ๋ฆฌ ์žฌ๊ท€ ์šฉ๋ฒ•์ด ์‚ฌ์šฉ ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค)).

๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋Š” ๊ฐ„๋‹จํ•œ ๋ฃจํ”„๋ฅผ ํ†ตํ•ด ์ถ”๋ก ํ•˜๋Š” ๊ฒƒ์ด ๋” ์‰ฝ์Šต๋‹ˆ๋‹ค. ๊ผฌ๋ฆฌ ์žฌ๊ท€๋ฅผ ํ†ตํ•ด ๋งŒ๋“ค์–ด์ง„ ํ•จ์ˆ˜๋Š” ๊ธธ๊ณ  ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด์„œ ์•„๋ž˜ ์˜ˆ์ œ๋Š” ๊ผฌ๋ฆฌ ์žฌ๊ท€์šฉ๋ฒ•๋ณด๋‹ค๋Š” ๊ฐ„๋‹จํ•œ ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Tail recursive version.
def max(data: Array[Int]): Int = {
  @tailrec
  def max0(data: Array[Int], pos: Int, max: Int): Int = {
    if (pos == data.length) {
      max
    } else {
      max0(data, pos + 1, if (data(pos) > max) data(pos) else max)
    }
  }
  max0(data, 0, Int.MinValue)
}

// Explicit loop version
def max(data: Array[Int]): Int = {
  var max = Int.MinValue
  for (v <- data) {
    if (v > max) {
      max = v
    }
  }
  max
}

Implicits

implicit์˜ ์‚ฌ์šฉ์€ ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, ์•„๋ž˜ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„œ๋Š” ์˜ˆ์™ธ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋„๋ฉ”์ธ-ํŠน์ •-์–ธ์–ด(DSL)๋ฅผ ๋นŒ๋“œ ํ•˜๋Š” ๊ฒฝ์šฐ
  • ์•”์‹œ์  ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ(์˜ˆ๋ฅผ ๋“ค์–ด. ClassTag, TypeTag)
  • ํŠน์ • ํด๋ž˜์Šค ์•ˆ์—์„œ ํƒ€์ž… ๋ณ€ํ™˜์˜ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ (์˜ˆ๋ฅผ ๋“ค์–ด,Scala ํ•จ์ˆ˜(closure) ์—์„œ Java ํ•จ์ˆ˜(closure)๋กœ์˜ ๋ณ€ํ™˜)

์šฐ๋ฆฌ๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ์‚ฌ๋žŒ์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ด ์ฝ”๋“œ๋ฅผ implicit์˜ ์ •์˜๋ฅผ ์ฝ์ง€ ์•Š๊ณ  ์ดํ•ด ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. implicit์€ ์ƒ๋‹นํžˆ ๋ณต์žกํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. Twitter์˜ Scala ๊ฐ€์ด๋“œ๋ผ์ธ์—์„œ๋Š” ์ด์™€ ๊ฐ™์ด ์–˜๊ธฐํ•ฉ๋‹ˆ๋‹ค:"๋งŒ์•ฝ ๋‹น์‹ ์ด implicit์„ ์‚ฌ์šฉ ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์ด๋ฅผ ์‚ฌ์šฉ ํ•˜์ง€ ์•Š๊ณ  ๊ฐ™์€ ๋ชฉ์ ์„ ๋‹ฌ์„ฑ ํ• ์ˆ˜ ์—†๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”."

๋งŒ์•ฝ ๊ผญ ์ด๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด (์˜ˆ๋ฅผ ๋“ค์–ด DSL์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด), implicit ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋กœ๋“œ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค๋ฅธ ์œ ์ €๊ฐ€ ์†์‰ฝ๊ฒŒ ๊ณจ๋ผ์„œ importํ•  ์ˆ˜ ์žˆ๋„๋ก implicit ํ•จ์ˆ˜๊ฐ€ ์ค‘๋ณต๋˜์ง€ ์•Š๋Š” ์ด๋ฆ„์„ ๊ฐ–๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

// Don't do the following, as users cannot selectively import only one of the methods.
object ImplicitHolder {
  def toRdd(seq: Seq[Int]): RDD[Int] = ...
  def toRdd(seq: Seq[Long]): RDD[Long] = ...
}

// Do the following:
object ImplicitHolder {
  def intSeqToRdd(seq: Seq[Int]): RDD[Int] = ...
  def longSeqToRdd(seq: Seq[Long]): RDD[Long] = ...
}

์‹ฌ๋ณผ ๋ฆฌํ„ฐ๋Ÿด

์‹ฌ๋ณผ ๋ฆฌํ„ฐ๋Ÿด์˜ ์‚ฌ์šฉ์€ ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์‹ฌ๋ณผ ๋ฆฌํ„ฐ๋Ÿด (์˜ˆ๋ฅผ ๋“ค์–ด 'column) ์€ ์‹ฌ๋ณผ ๋ฆฌํ„ฐ๋Ÿด ์ง€์› ์ค‘๋‹จ ๋ฐ ์‚ญ์ œ ์ œ์•ˆ์„œ์— ์˜ํ•˜์—ฌ ์Šค์นผ๋ผ 2.13๋ถ€ํ„ฐ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•„ํŒŒ์น˜ ์ŠคํŒŒํฌ์—์„œ๋Š” ๋„๋ฉ”์ธ ํŠนํ™” ์–ธ์–ด๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค๋งŒ, ์ด์ œ๋Š” ํ•ด๋‹น ๋ฌธ๋ฒ•์˜ ์‚ฌ์šฉ์„ ์ง€์šฐ๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. SPARK-29392๋ฅผ ์ฐธ๊ณ  ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์˜ˆ์™ธ ์ฒ˜๋ฆฌ (Try vs try)

  • Throwable ๋˜๋Š” Exception ์œ ํ˜•์„ ๋‹ค๋ฃจ์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. scala.util.control.NonFatal ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

    try {
      ...
    } catch {
      case NonFatal(e) =>
        // handle exception; note that NonFatal does not match InterruptedException
      case e: InterruptedException =>
        // handle InterruptedException
    }

    ์ด๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ NonLocalReturnControl๋ฅผ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ•˜์ง€ ์•Š๋„๋ก ํ•ด ์ค๋‹ˆ๋‹ค(Return ์˜ˆ์•ฝ์–ด ํ•ญ๋ชฉ์— ์„ค๋ช…๋˜์–ด ์žˆ๋Š” ๋Œ€๋กœ).

  • API ์•ˆ์—์„œ Try ๋ฅผ ์‚ฌ์šฉ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์–ด๋–ค ํ•จ์ˆ˜์—์„œ๋„ Try๋ฅผ ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ๋ช…์‹œ์ ์œผ๋กœ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๊ณ , Java์˜ try/catch ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ธ๋“ค๋ง ํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

    ๋ฐฐ๊ฒฝ: Scala๋Š” Try, Success ๊ทธ๋ฆฌ๊ณ  Failure๋ฅผ ํ†ตํ•ด์„œ ๋ชจ๋‚˜๋”•ํ•œ ์—๋Ÿฌ ํ•ธ๋“ค๋ฆฌ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋กœ์ง์˜ ์ฒด์ด๋‹์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์ด ๋ชจ๋‚˜๋”•ํ•œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์€ ์ข…์ข… ๋‹ค์ค‘ ๋ ˆ๋ฒจ์˜ ๋ณต์žก์„ฑ์„ ๊ฐ€ํ•˜๊ณ , ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ์ €ํ•˜ ์‹œํ‚จ๋‹ค๋Š” ๊ฒƒ์„ ๊ฒฝํ—˜์„ ํ†ตํ•ด ์•Œ๊ฒŒ ๋์Šต๋‹ˆ๋‹ค. ๋”๊ตฐ๋‹ค๋‚˜, ์ข…์ข… ์–ด๋Š ๋ถ€๋ถ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜์˜ค๊ณ , ์˜ˆ์ƒ์น˜ ๋ชป ํ•œ ์˜ˆ์™ธ๊ฐ€ ๋‚˜์˜ค๋Š”์ง€ ์•Œ๊ธฐ๊ฐ€ ํž˜๋“ญ๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” Try ์•ˆ์—์„œ ์ด ์—๋Ÿฌ์™€ ์˜ˆ์™ธ๊ฐ€ ์ธ์ฝ”๋”ฉ ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์šฐ๋ฆฌ๋Š” ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ์œ„ํ•ด Try์˜ ์‚ฌ์šฉ์„ ๊ถŒ๊ณ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํŠนํžˆ:

    ์ด ์˜ˆ์ œ์˜ ๊ฒฝ์šฐ:

    class UserService {
      /** Look up a user's profile in the user database. */
      def get(userId: Int): Try[User]
    }

    ์ด๋ ‡๊ฒŒ ์“ฐ์ด๋Š” ๊ฒƒ์ด ๋‚ซ์Šต๋‹ˆ๋‹ค:

    class UserService {
      /**
       * Look up a user's profile in the user database.
       * @return None if the user is not found.
       * @throws DatabaseConnectionException when we have trouble connecting to the database/
       */
      @throws(DatabaseConnectionException)
      def get(userId: Int): Option[User]
    }

    ๋‘๋ฒˆ์งธ๋Š” ํ™•์‹คํžˆ ์–ด๋–ค ์—๋Ÿฌ๋ฅผ ํ•ธ๋“ค๋ง ํ•˜๋Š”์ง€ ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ ์•Œ๊ธฐ๊ฐ€ ์‰ฝ์Šต๋‹ˆ๋‹ค.

Options

  • ๊ฐ’์ด ๋น„์–ด ์žˆ์„ ์ˆ˜ ์žˆ์„ ๋•Œ Option์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. null๊ณผ ๋Œ€์กฐ๋˜์–ด, Option์€ API์—์„œ ๋ช…์‹œ๋œ ๋Œ€๋กœ None๊ฐ’์„ ๊ฐ–์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Option์„ ์ƒ์„ฑ ํ•  ๋•Œ Some๋ณด๋‹ค๋Š” Option์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” null ๊ฐ’์œผ๋กœ ๋ถ€ํ„ฐ ์•ˆ์ „ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    def myMethod1(input: String): Option[String] = Option(transform(input))
    
    // This is not as robust because transform can return null, and then
    // myMethod2 will return Some(null).
    def myMethod2(input: String): Option[String] = Some(transform(input))
  • None์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ํ‘œํ˜„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹ , ๋ช…์‹œ์ ์œผ๋กœ ์˜ˆ์™ธ๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค.

  • Option์—์„œ์˜ ๊ฐ’์„ ํ™•์‹  ํ•  ์ˆ˜ ์žˆ์ง€ ์•Š๋Š” ์ด์ƒ, Option์—์„œ get์„ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ชจ๋‚˜๋“œ ์ฑ„์ด๋‹

Scala์˜ ๊ฐ•๋ ฅํ•œ ํŠน์ง•์ค‘ ํ•˜๋‚˜๋Š” ๋ชจ๋‚˜๋“œ ์ฑ„์ด๋‹ ์ž…๋‹ˆ๋‹ค. ๊ฑฐ์˜ ๋ชจ๋“  ๊ฒƒ(์˜ˆ๋ฅผ ๋“ค์–ด collections, Option, Futrue ํ˜น์€ Try) ์ด ๋ชจ๋‚˜๋“œ ์ฑ„์ด๋‹์„ ์ง€์›ํ•˜๊ณ  ๊ฐ™์ด ๋งž๋ฌผ๋ ค์„œ ๋™์ž‘ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ๋†€๋ผ์šธ ์ •๋„๋กœ ๊ฐ•๋ ฅํ•œ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ์ฑ„์ด๋‹์€ ํ•จ๋ถ€๋กœ ๋‚จ์šฉ๋˜์–ด์„œ๋Š” ์•ˆ๋ฉ๋‹ˆ๋‹ค. ํŠนํžˆ:

  • 3๊ฐœ ์ด์ƒ์˜ (๋‚ด๋ถ€๋ฅผ ํฌํ•จ)๋ชจ๋‚˜๋“œ ์ฑ„์ด๋‹์€ ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ ์ฝ”๋“œ์˜ ๋…ผ๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๋Š”๋ฐ 5์ดˆ ์ด์ƒ์ด ๊ฑธ๋ฆฐ๋‹ค๋ฉด, ๋ชจ๋‚˜๋“œ ์ฒด์ด๋‹์„ ์‚ฌ์šฉ ํ•˜์ง€ ์•Š๊ณ , ๊ฐ™์€ ์„ฑ๊ณผ๋ฅผ ์ด๋ฃฐ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด ๋ณผ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ flatMap ํ˜น์€ fold๊ฐ€ ์ด์— ํ•ด๋‹น ๋ฉ๋‹ˆ๋‹ค.
  • flatMap ํ›„์—๋Š” ๊ฑฐ์˜ ํ•ญ์ƒ ๋ชจ๋‚˜๋“œ ์ฑ„์ด๋‹์„ ์ด์–ด๊ฐ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค (์™œ๋ƒํ•˜๋ฉด ํƒ€์ž…์ด ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค).
  • if-else์— ๋ชจ๋‚˜๋“œ ์ฒด์ด๋‹์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ชจ๋‚˜๋“œ ์ฒด์ธ์€ ์ข…์ข… ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์ด ์ฃผ์–ด์ง„ ์ค‘๊ฐ„ ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ์‹์œผ๋กœ ์ฑ„์ด๋‹์„ ๋Š์–ด ๋” ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋„๋ก ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

class Person(val data: Map[String, String])
val database = Map[String, Person]
// Sometimes the client can store "null" value in the  store "address"

// A monadic chaining approach
def getAddress(name: String): Option[String] = {
  database.get(name).flatMap { elem =>
    elem.data.get("address")a
      .flatMap(Option.apply)  // handle null value
  }
}

// A more readable approach, despite much longer
def getAddress(name: String): Option[String] = {
  if (!database.contains(name)) {
    return None
  }

  database(name).data.get("address") match {
    case Some(null) => None  // handle null value
    case Some(addr) => Option(addr)
    case None => None
  }
}

๋‹ค๋ฅธ ์˜ˆ๋กœ๋Š” if-else๊ฐ€ ์ „์ฒด if-else์˜ ๋ชจ๋‚˜๋“œ ์ฒด์ธ์ธ์ง€ ์•„๋‹ˆ๋ฉด else ์ ˆ์˜ ๋ชจ๋‚˜๋“œ ์ฒด์ธ์ธ์ง€ ํ–‡๊ฐˆ๋ฆฌ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

// A monadic chaining approach
val condition: Boolean = ...sca
if (condition) {
  Seq(1, 2, 3)  // Results in List(1, 2, 3)
} else {
  Seq(1, 2, 3)  // Results in List(2, 3, 4)
}.map(_ + 1)

// A more readable approach.
val ret = if (condition) {
  Seq(1, 2, 3)
} else {
  Seq(1, 2, 3)
}
ret.map(_ + 1)  // Results in List(2, 3, 4)

๋™์‹œ์„ฑ ์ œ์–ด

Scala concurrent.Map

java.util.concurrent.ConcurrentHashMap ์ด scala.collection.concurrent.Map ๋ณด๋‹ค ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, scala.collection.concurrent.Map์˜ getOrElseUpdate ํ•จ์ˆ˜๋Š” atomicํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค (์ด๋Š” Scala 2.11.6์—์„œ ๊ณ ์ณ์กŒ์Šต๋‹ˆ๋‹ค. SI-7943). ์šฐ๋ฆฌ๊ฐ€ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋Š” ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Scala 2.10๊ณผ Scala 2.11์˜ ํฌ๋กœ์Šค ๋นŒ๋”ฉ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— scala.collection.concurrent.Map์˜ ์‚ฌ์šฉ์€ ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋™๊ธฐํ™”(synchronized) ๋ช…์‹œ vs Java ์ œ๊ณต ๋™์‹œ์„ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๋™์‹œ์„ฑ์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด์„œ 3๊ฐ€์ง€์˜ ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์„ž์–ด์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค, ์™œ๋ƒํ•˜๋ฉด ์ด๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋”์šฑ ๋ณต์žกํ•˜๊ฒŒ ํ•˜๊ณ  ๋ฐ๋“œ๋ฝ์„ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

  1. java.util.concurrent.ConcurrentHashMap: ๋ชจ๋“  ์ƒํƒœ๊ฐ€ map์— ์ €์žฅ ๋˜๊ณ , ๋นˆ๋ฒˆํ•œ ์ ‘๊ทผ์ด ์ผ์–ด ๋‚  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
private[this] val map = new java.util.concurrent.ConcurrentHashMap[String, String]
  1. java.util.Collections.synchronizedMap: ๋ชจ๋“  ์ƒํƒœ๊ฐ€ map์— ์ €์žฅ๋˜๊ณ , ๋นˆ๋ฒˆํ•œ ์ ‘๊ทผ์ด ์ผ์–ด๋‚˜์ง€ ์•Š์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์•„๋ฌด๋Ÿฐ ๋™์‹œ์„ฑ ์ ‘๊ทผ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, JVM JIT ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋™๊ธฐํ™”์˜ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ง€์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
private[this] val map = java.util.Collections.synchronizedMap(new java.util.HashMap[String, String])
  1. ๋ช…์‹œ์ ์œผ๋กœ ๋™๊ธฐํ™”๋ฅผ ํ•˜๋Š” ๋ฐฉ๋ฒ•: ์ด ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๋ณ€์ˆ˜๋“ค์„ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. 2๋ฒˆ๊ณผ ๋น„์Šทํ•˜๊ฒŒ JVM JIT ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋™๊ธฐํ™”์˜ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ง€์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
class Manager {
  private[this] var count = 0
  private[this] val map = new java.util.HashMap[String, String]
  def update(key: String, value: String): Unit = synchronized {
    map.put(key, value)
    count += 1
  }
  def getCount: Int = synchronized { count }
}

1๋ฒˆ์˜ ๊ฒฝ์šฐ์™€ 2๋ฒˆ์˜ ๊ฒฝ์šฐ, ๊ฐ’์„ ์ฝ๊ฑฐ๋‚˜ ์ดํ„ฐ๋ ˆ์ดํ„ฐ๋กœ ํ•ด๋‹น ์ฝœ๋ž™์…˜์— ์ ‘๊ทผ์‹œ, ์ด ๋ณดํ˜ธ๋œ ์˜์—ญ์—์„œ ๋น ์ ธ๋‚˜์˜ค๊ฒŒ ๋˜๋Š” ๊ฒƒ์„ ์ฃผ์˜ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ข…์ข… Map.keySet์ด๋‚˜ Map.values์„ ์‚ฌ์šฉ ํ•˜๋Š” ๊ฒฝ์šฐ ๋ฒŒ์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ฐ’์„ ์ฝ๊ฑฐ๋‚˜ ๊ฐ’๋“ค์ด ๋ฃจํ”„๋ฅผ ๋Œ์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

val map = java.util.Collections.synchronizedMap(new java.util.HashMap[String, String])

// This is broken!
def values: Iterable[String] = map.values

// Instead, copy the elements
def values: Iterable[String] = map.synchronized { Seq(map.values: _*) }

๋™๊ธฐํ™”(synchronized) ๋ช…์‹œ vs Atomic ๋ณ€์ˆ˜ vs @volatile

java.util.concurrent.atomic ํŽ˜ํ‚ค์ง€๋Š” ์›์‹œํƒ€์ž…์„ ์›์ž์ ์œผ๋กœ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” API๋ฅผ ์ œ๊ณต ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด AtomicBoolean, AtomicInteger ์™€ AtomicReference).

ํ•ญ์ƒ @volatile ๋ณด๋‹ค๋Š” ์ด๋ฅผ ์‚ฌ์šฉํ•œ ๋ณ€์ˆ˜๋“ค์„ ์‚ฌ์šฉ ํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ๊ณ ๋ฉ๋‹ˆ๋‹ค. ์ด๋“ค์€ ๋งŽ์€ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ์ฆ๊ฐ€์‹œ์ผœ ์ค๋‹ˆ๋‹ค. ์ด ๋ณ€์ˆ˜๋“ค์€ ๋‚ด๋ถ€์ ์œผ๋กœ @volatile์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ช…์‹œ์ ์ธ ๋™๊ธฐํ™” ๋ณด๋‹ค๋Š” Atomic ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ช‡ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค: (1) ์–ด๋–ค ๊ฐ์ฒด์˜ ๋ชจ๋“  ์ค‘์š”ํ•œ ๊ฐฑ์‹ ์ด ํ•˜๋‚˜์˜ ๋‹จ์ผ ๋ณ€์ˆ˜์— ์กด์žฌ ํ•  ๋•Œ ๊ทธ๋ฆฌ๊ณ  ๋™์‹œ์„ฑ ์ ‘๊ทผ์ด ์˜ˆ์ƒ ๋  ๋•Œ. ์ด ๋ณ€์ˆ˜๋“ค์€ ์›์ž์ ์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํšจ๊ณผ์ ์ธ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ˜น์€ (2) ๋™๊ธฐํ™”๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ getAndSet ํ•จ์ˆ˜๋กœ ํ‘œํ˜„ ๋  ์ˆ˜ ์žˆ์„ ๋•Œ. ์˜ˆ๋ฅผ ๋“ค์–ด:

// good: clearly and efficiently express only-once execution of concurrent code
val initialized = new AtomicBoolean(false)
...
if (!initialized.getAndSet(true)) {
  ...
}

// poor: less clear what is guarded by synchronization, may unnecessarily synchronize
val initialized = false
...
var wasInitialized = false
synchronized {
  wasInitialized = initialized
  initialized = true
}
if (!wasInitialized) {
  ...
}

Private ๋ณ€์ˆ˜

private ๋ณ€์ˆ˜๋“ค์ด ์™ธ๋ถ€ ๊ฐ™์€ ํด๋ž˜์Šค์˜ ๋‹ค๋ฅธ ๊ฐ์ฒด๋“ค๋กœ๋ถ€ํ„ฐ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์ฃผ์˜ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์ด๋ฅผ this.synchronized (ํ˜น์€ synchronized) ์œผ๋กœ ๋ณดํ˜ธํ•˜๋Š” ๊ฒƒ์€ ๊ธฐ์ˆ ์ ์œผ๋กœ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ๋Œ€์‹ , private[this]๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

// The following is still unsafe.
class Foo {
  private var count: Int = 0
  def inc(): Unit = synchronized { count += 1 }
}

// The following is safe.
class Foo {
  private[this] var count: Int = 0
  def inc(): Unit = synchronized { count += 1 }
}

๋™์‹œ์„ฑ ๋กœ์ง ๋ถ„๋ฆฌ

์ผ๋ฐ˜์ ์œผ๋กœ, ๋™์‹œ์„ฑ๊ณผ ๋™๊ธฐํ™” ๋กœ์ง์€ ์ตœ๋Œ€ํ•œ ๋ถ„๋ฆฌ๋˜๊ณ  ๋…๋ฆฝ์ ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค:

  • API๋ ˆ๋ฒจ์—์„œ ์œ ์ €์—๊ฒŒ ๋…ธ์ถœ๋œ ํ•จ์ˆ˜๋‚˜ ์ฝœ๋ฐฑํ•จ์ˆ˜์— ์ด ๋™๊ธฐํ™” ๋ณ€์ˆ˜๋“ค์„ ๋…ธ์ถœ ํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณต์žกํ•œ ๋ชจ๋“ˆ์—์„œ๋Š” ์ž‘์€ ๋‚ด๋ถ€ ๋ชจ๋“ˆ์„ ๋งŒ๋“ค์–ด ๋™์‹œ์„ฑ์„ ์œ„ํ•œ ๋ณ€์ˆ˜๋“ค์„ ๊ฐ–๊ณ  ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์„ฑ๋Šฅ

๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋Š” ๋ณดํ†ต ์„ฑ๋Šฅ์— ๋Œ€ํ•˜์—ฌ ํฌ๊ฒŒ ๊ณ ๋ ค๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•œ ์ฝ”๋“œ๋ฅผ ์œ„ํ•ด์„œ ๋ช‡ ๊ฐ€์ง€ ํŒ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Microbenchmarks

์ข‹์€ microbenchmark๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์•„์ฃผ ์–ด๋ ค์šด ์ผ ์ž…๋‹ˆ๋‹ค, ์™œ๋ƒํ•˜๋ฉด Scala ์ปดํŒŒ์ผ๋Ÿฌ์™€ JVM JIT ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋งŽ์€ ๋งˆ๋ฒ•๊ณผ ๊ฐ™์€ ์ผ์„ ์ฝ”๋“œ์— ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋Œ€๊ฒŒ์˜ ๊ฒฝ์šฐ microbenchmark๋Š” ์ธก์ •ํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒƒ์„ ์ธก์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

microbenchmark๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด jmh๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. "์ฃฝ์€ ์ฝ”๋“œ" ์ œ๊ฑฐ์™€ ์ƒ์ˆ˜๊ฐ’ ๋Œ€์ฒด ๊ทธ๋ฆฌ๊ณ  ๋ฃจํ”„ ํ’€๊ธฐ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ์ง์ ‘ ๋ชจ๋“  ์ƒ˜ํ”Œ์„ ์ฝ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ˆœํšŒ์™€ zipWithIndex

for๋‚˜ ํ˜น์€ functional transformations (์˜ˆ๋ฅผ ๋“ค์–ด, map ํ˜น์€ foreach) ๋ณด๋‹ค๋Š” while ๋ฃจํ”„๋ฅผ ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. For ๋ฃจํ”„๋‚˜ functional transformations์€ ์ƒ๋‹นํžˆ ๋Š๋ฆฝ๋‹ˆ๋‹ค(์ด์œ ๋Š” ๊ฐ€์ƒ ํ•จ์ˆ˜ ํ˜ธ์ถœ๊ณผ boxing ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค).

val arr = // array of ints
// zero out even positions
val newArr = list.zipWithIndex.map { case (elem, i) =>
  if (i % 2 == 0) 0 else elem
}

// This is a high performance version of the above
val newArr = new Array[Int](arr.length)
var i = 0
val len = newArr.length
while (i < len) {
  newArr(i) = if (i % 2 == 0) 0 else arr(i)
  i += 1
}

Option ๊ณผ null

์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•œ ์ฝ”๋“œ๋ฅผ ์œ„ํ•ด, ๊ฐ€์ƒ ํ•จ์ˆ˜ ํ˜ธ์ถœ๊ณผ boxing์„ ํ”ผํ•˜๋Š” Option๋ณด๋‹ค๋Š” null์˜ ์‚ฌ์šฉ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. Null์„ ๊ฐ–์„ ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜์—๋Š” Nullable ์ด๋ผ๊ณ  label์„ ํ™•์‹คํžˆ ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

class Foo {
  @javax.annotation.Nullable
  private[this] var nullableField: Bar = _
}

Scala Collection ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•œ ์ฝ”๋“œ๋ฅผ ์œ„ํ•ด, Scala์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๋ณด๋‹ค๋Š” Java์˜ collection ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” Scala์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ข…์ข… Java ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ณด๋‹ค ๋Š๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

private[this]

์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•œ ์ฝ”๋“œ๋ฅผ ์œ„ํ•ด, private ๋ณด๋‹ค๋Š” private[this]์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. private[this]๋Š” ์ ‘๊ทผ์ž ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  ํ•˜๋‚˜์˜ ๋ณ€์ˆ˜๋งŒ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ๊ฒฝํ—˜์œผ๋กœ๋Š” JVM JIT ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ํ•ญ์ƒ private ๋ณ€์ˆ˜๋ฅผ ํ•œ ๋ฒˆ์— (ํ•˜๋‚˜์˜ ์ •์˜๋กœ) ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•ด๋‹น ๋ณ€์ˆ˜์— ์ ‘๊ทผ ํ•  ๊ฐ€์ƒ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ์—†์• ๊ธฐ ์œ„ํ•ด์„œ private[this] ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

class MyClass {
  private val field1 = ...
  private[this] val field2 = ...

  def perfSensitiveMethod(): Unit = {
    var i = 0
    while (i < 1000000) {
      field1  // This might invoke a virtual method call
      field2  // This is just a field access
      i += 1
    }
  }
}

Java ํ˜ธํ™˜์„ฑ

์ด ํ•ญ๋ชฉ์€ Java ํ˜ธํ™˜ ๊ฐ€๋Šฅํ•œ API๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ํ˜„์žฌ ๋‹น์‹ ์ด ๋งŒ๋“ค๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ Java์™€์˜ ํ˜ธํ™˜์„ฑ์„ ํ•„์š”๋กœ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ๋ผ์ธ์€ ์ฃผ๋กœ ์šฐ๋ฆฌ๊ฐ€ Spark์˜ Java API๋ฅผ ๋งŒ๋“œ๋Š” ๊ณผ์ •์—์„œ ์šฐ๋ฆฌ๊ฐ€ ๊ฒฝํ—˜ํ•œ ๊ฒƒ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Scala์—์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์—†๋Š” Java ๊ธฐ๋Šฅ

์•„๋ž˜์˜ JavaํŠน์ง•๋“ค์€ Scala์— ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์•„๋ž˜์˜ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด Java์—์„œ ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Scala ๋ฌธ์„œ๋ฅผ ๋ณด์‹œ๋ฉด Java๋กœ ์ •์˜๋œ ํŒŒ์ผ์— ๋Œ€ํ•œ ๋ณด์žฅ์€ ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • Static ๋ณ€์ˆ˜
  • Static ๋‚ด๋ถ€ ํด๋ž˜์Šค
  • Java enum
  • Annotation

Traits ์™€ Abstract ํด๋ž˜์Šค

์™ธ๋ถ€์—์„œ ๊ตฌํ˜„ ๋  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ฒฝ์šฐ ์•„๋ž˜์˜ ํ•ญ๋ชฉ์„ ๋ช…์‹ฌํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค:

  • Trait์— ์žˆ๋Š” ๊ธฐ๋ณธ์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ๋Š” ํ•จ์ˆ˜๋“ค์€ Java์—์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
  • ์ผ๋ฐ˜์ ์œผ๋กœ trait์˜ ์‚ฌ์šฉ์„ ํ”ผํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋‹จ, ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋ฏธ๋ž˜์˜ ์–ด๋–ค ๊ฒฝ์šฐ์—๋„ ์–ด๋– ํ•œ ์ •์˜๋œ ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์‹  ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// The default implementation doesn't work in Java
trait Listener {
  def onTermination(): Unit = { ... }
}

// Works in Java
abstract class Listener {
  def onTermination(): Unit = { ... }
}

Type ๋ณ„์นญ

๋ณ„์นญ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ๋ฐ”์ดํŠธ์ฝ”๋“œ์™€ Java์—์„œ ๋ณด์—ฌ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’

์ธ์ž์— ๊ธฐ๋ณธ๊ฐ’์„ ์ฃผ์–ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋กœ๋“œ ํ•ฉ๋‹ˆ๋‹ค.

// Breaks Java interoperability
def sample(ratio: Double, withReplacement: Boolean = false): RDD[T] = { ... }

// The following two work
def sample(ratio: Double, withReplacement: Boolean): RDD[T] = { ... }
def sample(ratio: Double): RDD[T] = sample(ratio, withReplacement = false)

๋‹ค์ค‘ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ‘œ๊ธฐ

์—ฌ๋Ÿฌ ์ธ์ž๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฌถ์–ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ฐ€๋ณ€์ธ์ž

  • varargs ํ•จ์ˆ˜๊ฐ€ Java์—์„œ ์‚ฌ์šฉ ๋  ์ˆ˜ ์žˆ๋„๋ก @scala.annotation.varargs ์–ด๋…ธํ…Œ์ด์…˜์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. Scala ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ํ•˜๋‚˜๋Š” Scala๋ฅผ ์œ„ํ•ด(๋ฐ”์ดํŠธ์ฝ”๋“œ ์ธ์ž๋Š” Seq ์ž…๋‹ˆ๋‹ค) ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” Java๋ฅผ ์œ„ํ•ด (๋ฐ”์ดํŠธ์ฝ”๋“œ ์ธ์ž๋Š” ๋ฐฐ์—ด ์ž…๋‹ˆ๋‹ค) ์ด ๋‘๊ฐœ์˜ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    @scala.annotation.varargs
    def select(exprs: Expression*): DataFrame = { ... }
  • ์ถ”์ƒ varargs ํ•จ์ˆ˜๋Š” Java์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” Scala์˜ ๋ฒ„๊ทธ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(SI-1459, SI-9013).

  • varargs ํ•จ์ˆ˜๋“ค์„ ์˜ค๋ฒ„๋กœ๋”ฉํ•  ๋•Œ ์กฐ์‹ฌํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. varargs ํ•จ์ˆ˜๋ฅผ ๋‹ค๋ฅธ varargs ํƒ€์ž…๊ณผ ์˜ค๋ฒ„๋กœ๋”ฉ ํ•˜๋Š” ๊ฒƒ์€ ์†Œ์Šค์˜ ํ˜ธํ™˜์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    class Database {
      @scala.annotation.varargs
      def remove(elems: String*): Unit = ...
    
      // Adding this will break source compatibility for no-arg remove() call.
      @scala.annotation.varargs
      def remove(elems: People*): Unit = ...
    }
    
    // This won't compile anymore because it is ambiguous
    new Database().remove()

    ๋Œ€์‹ , ๋ช…์‹œ์  ํƒ€์ž…์„ ๊ฐ–๋Š” ์ธ์ž๋ฅผ ์ฒ˜์Œ ์˜ค๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค:

    class Database {
      @scala.annotation.varargs
      def remove(elems: String*): Unit = ...
    
      // The following is OK.
      @scala.annotation.varargs
      def remove(elem: People, elems: People*): Unit = ...
    }

Implicits

ํด๋ž˜์Šค๋‚˜ ํ•จ์ˆ˜๋ฅผ ์œ„ํ•ด implicit์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ClassTag, TypeTag ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

class JavaFriendlyAPI {
  // This is NOT Java friendly, since the method contains an implicit parameter (ClassTag).
  def convertTo[T: ClassTag](): T
}

๊ด€๋ จ ๊ฐ์ฒด, ์ •์  ํ•จ์ˆ˜ ๋ฐ ๋ณ€์ˆ˜

๋™๋ฐ˜ํ•˜๋Š” ๊ฐ์ฒด๋“ค๊ณผ ์ •์  ํ•จ์ˆ˜/๋ณ€์ˆ˜ ๋“ค์„ ์‚ฌ์šฉ ํ•  ๋•Œ, ๋ช‡ ๊ฐ€์ง€ ์กฐ์‹ฌํ•ด์•ผ ํ•  ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋™๋ฐ˜(companion) ๊ฐ์ฒด๋“ค์€ Java์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ์—๋Š” ์กฐ๊ธˆ ์–ด์ƒ‰ํ•ฉ๋‹ˆ๋‹ค(๋™๋ฐ˜(companion) ๊ฐ์ฒด Foo ๋Š” Foo$ ํด๋ž˜์Šค์˜ Foo$ ํƒ€์ž…์˜ MODULE$ ์ •์  ๋ณ€์ˆ˜ ์ž…๋‹ˆ๋‹ค).

    object Foo
    
    // equivalent to the following Java code
    public class Foo$ {
      Foo$ MODULE$ = // instantiation of the object
    }

    ๋งŒ์•ฝ ๋™๋ฐ˜(companion) ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด, Java ์ •์  ๋ณ€์ˆ˜๋ฅผ ๋‹ค๋ฅธ ํด๋ž˜์Šค์— ๋งŒ๋“ญ๋‹ˆ๋‹ค.

  • ๋ถˆํ–‰ํžˆ๋„, JVM ์ •์  ๋ณ€์ˆ˜๋ฅผ Scala์—์„œ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—†์Šต๋‹ˆ๋‹ค. JavaํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ์ด๋ฅผ ์ •์˜ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

  • ๋™๋ฐ˜(companion) ๊ฐ์ฒด์˜ ํ•จ์ˆ˜๋“ค์€ ์ž๋™์œผ๋กœ ๋™๋ฐ˜(companion) ํด๋ž˜์Šค์˜ ์ •์  ํ•จ์ˆ˜๋กœ ๋ณ€ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋‹จ, ๊ฐ™์€ ํ•จ์ˆ˜๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ •์  ํ•จ์ˆ˜์˜ ์ƒ์„ฑ์ด ๋ณด์žฅ ๋˜๋„๋ก ํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ Javaํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜์—ฌ ์ด ์ •์  ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

    class Foo {
      def method2(): Unit = { ... }
    }
    
    object Foo {
      def method1(): Unit = { ... }  // a static method Foo.method1 is created in bytecode
      def method2(): Unit = { ... }  // a static method Foo.method2 is NOT created in bytecode
    }
    
    // FooJavaTest.java (in test/scala/com/databricks/...)
    public class FooJavaTest {
      public static void compileTest() {
        Foo.method1();  // This one should compile fine
        Foo.method2();  // This one should fail because method2 is not generated.
      }
    }
  • ํ•˜๋‚˜์˜ case ๊ฐ์ฒด (ํ˜น์€ ์‹ฌ์ง€์–ด ๋ณดํ†ต ๋™๋ฐ˜(companion) ๊ฐ์ฒด) MyClass๋Š” ์‚ฌ์‹ค MyClass ํƒ€์ž…์ด ์•„๋‹™๋‹ˆ๋‹ค.

    case object MyClass
    
    // Test.java
    if (MyClass$.MODULE instanceof MyClass) {
      // The above condition is always false
    }

    ์ด๋ฅผ ์ ์ ˆํ•œ ํƒ€์ž… ๊ตฌ์กฐ๋ฅผ ๊ฐ–์„ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋™๋ฐ˜(companion) ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ , ์ด๋ฅผ case ๊ฐ์ฒด์—์„œ ์ƒ์† ๋ฐ›๋„๋ก ํ•ฉ๋‹ˆ๋‹ค:

    class MyClass
    case object MyClass extends MyClass

ํ…Œ์ŠคํŠธ

์˜ˆ์™ธ ๊ฐ€๋กœ ์ฑ„๊ธฐ

ํŠน์ •ํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ ์‹œํ‚ค๋Š” ํ–‰๋™์„ ํ…Œ์ŠคํŠธ ํ•  ๋•Œ๋Š” (์˜ˆ๋ฅผ ๋“ค์–ด, ์ž˜๋ชป๋œ ์ธ์ž๋ฅผ ์ฃผ์–ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ ํ•˜๋Š” ๊ฒƒ), ๊ฐ€๋Šฅํ•œ ํ•œ ์˜ˆ์™ธ์˜ ํƒ€์ž…์„ ๊ตฌ์ฒด์ ์œผ๋กœ ๋ช…์‹œ ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. (ScalaTest๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ) ๋‹จ์ˆœํžˆ intercept[Exception] ์ด๋‚˜ intercept[Throwable] ์„ ํ•ด์„œ๋Š” ์•ˆ๋ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด, ์ด ๊ฒƒ์€ ๋ชจ๋“  ํƒ€์ž…์˜ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ ํ–ˆ๋‹ค๋Š” ๊ฒƒ์„ ์ฒดํฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ๋งŒ๋“ค์–ด์ง„ ํ…Œ์ŠคํŠธ๋“ค์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋Š” ๊ฒƒ๋งŒ ํ™•์ธ ํ•˜๊ณ , ์‹ค์ œ ํ™•์ธํ•ด์•ผ ํ•˜๋Š” ํ–‰๋™์„ ํ™•์ธํ•˜์ง€ ์•Š์€์ฑ„ ์กฐ์šฉํžˆ ํ†ต๊ณผ ํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

// ์ž˜๋ชป๋œ ๊ฒฝ์šฐ
intercept[Exception] {
  thingThatThrowsException()
}

// ์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ์šฐ
intercept[MySpecificTypeOfException] {
  thingThatThrowsException()
}

๋งŒ์•ฝ ์˜ˆ์™ธ์˜ ํƒ€์ž…์ด ๊ตฌ์ฒด์ ์œผ๋กœ ๋ช…์‹œ ๋  ์ˆ˜ ์—†๋‹ค๋ฉด, ์ฝ”๋“œ ์Šค๋ฉœ์˜ ์ง•ํ›„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ฑฐ๋‚˜ ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์˜ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋„๋ก ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ˆ˜์ • ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐํƒ€

currentTimeMillis ๋ณด๋‹ค๋Š” nanoTime

_์ง€์† ์‹œ๊ฐ„_์„ ๊ณ„์‚ฐํ•  ๋•Œ ํ˜น์€ _ํƒ€์ž„์•„์›ƒ_์„ ํ™•์ธ ํ•  ๋•Œ์—๋Š”, ์‹ฌ์ง€์–ด millisecond ์ดํ•˜์˜ ์ˆซ์ž๋“ค์ด ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ์—๋„ System.currentTimeMillis()์˜ ์‚ฌ์šฉ์„ ํ”ผํ•˜์‹œ๊ณ  System.nanoTime()์„ ์‚ฌ์šฉ ํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

System.currentTimeMillis()๋Š” ํ˜„์žฌ ์‹œ๊ฐ„์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ํ˜„์žฌ ์‹œ์Šคํ…œ์˜ ํด๋ก์„ ๋’ค๋”ฐ๋ผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋Ÿฌํ•œ ๋„ค๊ฑฐํ‹ฐ๋ธŒ ํด๋ก ์กฐ์ •์€ ๊ธด ์‹œ๊ฐ„์˜ ํƒ€์ž„์•„์›ƒ์„ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(ํด๋ก ์‹œ๊ฐ„ ์ด์ „ ๊ฐ’์œผ๋กœ ์žก์„ ๋•Œ ๊นŒ์ง€). ์ด ๊ฒƒ์€ ๋„คํŠธ์›Œํฌ๊ฐ€ ์žฅ ์‹œ๊ฐ„ ์ค‘๋‹จ ๋œ ํ›„์—, ntpd๊ฐ€ ๋‹ค์Œ "step"์œผ๋กœ ์ง„ํ–‰ํ•  ๋•Œ ๋ฐœ์ƒ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์ „ํ˜•์ ์ธ ์˜ˆ๋กœ๋Š” ์‹œ์Šคํ…œ ๋ถ€ํŒ… ๋™์•ˆ DHCP ์‹œ๊ฐ„์ด ํ‰์†Œ๋ณด๋‹ค ์˜ค๋ž˜ ์†Œ์š”๋  ๋•Œ ์ž…๋‹ˆ๋‹ค. ์ด๋Š”, ์ดํ•ดํ•˜๊ฑฐ๋‚˜ ์žฌํ˜„ํ•˜๊ธฐ ํž˜๋“  ์—๋Ÿฌ๋ฅผ ์ดˆ๋ž˜ ํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. System.nanoTime()์€ wall-clock์— ์ƒ๊ด€ ์—†์ด, ํ•ญ์ƒ ์ผ์ •ํ•˜๊ฒŒ ์ฆ๊ฐ€ ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜:

  • ์ ˆ๋Œ€ nanoTime()์˜ ์ ˆ๋Œ€๊ฐ’์„ ์ ˆ๋Œ€๋กœ ์ง๋ ฌํ™” ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์œผ๋กœ ๋ณด๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ์ ˆ๋Œ€๊ฐ’์€ ์˜๋ฏธ๊ฐ€ ์—†์œผ๋ฉฐ, ์‹œ์Šคํ…œ ๊ด€๋ จ ๊ฐ’์ด๊ณ , ์‹œ์Šคํ…œ์ด ์žฌ๋ถ€ํŒ… ๋˜๋ฉด ๋ฆฌ์…‹๋ฉ๋‹ˆ๋‹ค.
  • ์ ˆ๋Œ€ nanoTime()์˜ ์ ˆ๋Œ€๊ฐ’์€ ์–‘์ˆ˜๋กœ ๋ณด์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(ํ•˜์ง€๋งŒ t2 - t1 ์€ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’์„ ๊ณ„์‚ฐํ•˜๋„๋ก ๋ณด์žฅ ๋ฉ๋‹ˆ๋‹ค).
  • nanoTime()์€ 292๋…„์„ ์ฃผ๊ธฐ๋กœ ๋‹ค์‹œ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋งŒ์•ฝ Spark ์ž‘์—…(job)์ด ์•„์ฃผ ๊ธด ์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋œ๋‹ค๋ฉด, ๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€๋ฅผ ์ฐพ์•„์•ผ ํ•˜๊ฒ ์ฃ  :)

URL ๋ณด๋‹ค๋Š” URI

์–ด๋–ค ์„œ๋น„์Šค์˜ URL์„ ์ •๋ ฌ ํ•  ๋•Œ, URI ํ‘œํ˜„์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

URL์˜ ๋™์ผ์„ฑ ๊ฒ€์‚ฌ ๋Š” ์‚ฌ์‹ค IP ์ฃผ์†Œ๋ฅผ ์•Œ์•„๋‚ด๊ธฐ ์œ„ํ•ด (๋ธ”๋กœํ‚น) ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์„ ํ•ฉ๋‹ˆ๋‹ค. URI ํด๋ž˜์Šค๋Š” ํ•„๋“œ์˜ ๋™์ผ์„ฑ์„ ํ™•์ธํ•˜๊ณ  URL์˜ ์ƒ์œ„ ์ง‘ํ•ฉ ์ž…๋‹ˆ๋‹ค.

์ด๋ฏธ ์กด์žฌ ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค๋Š” ๊ธฐ์กด์˜ ์ž˜ ํ…Œ์ŠคํŠธ ๋œ ํ•จ์ˆ˜ ์‚ฌ์šฉ

์ด๋ฏธ ์กด์žฌํ•˜๋ฉฐ ์ž˜ ํ…Œ์ŠคํŠธ ๋˜์–ด์žˆ๋Š” ํ•จ์ˆ˜๊ฐ€ ์žˆ๊ณ  ์ด ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ค ์„ฑ๋Šฅ ๋ฌธ์ œ๋„ ๊ฐ–๊ณ  ์žˆ์ง€ ์•Š์„ ๋•Œ์—๋Š”, ์ด๋ฅผ ์‚ฌ์šฉ ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ๊ตฌํ˜„ํ•˜๋ฉด ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค (์–ด์ฉŒ๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ํ…Œ์ŠคํŠธ ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์žŠ์–ด๋ฒ„๋ฆด ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค!).

val beginNs = System.nanoTime()
// ์‹œ๊ฐ„ ์ธก์ •์„ ์œ„ํ•œ ์ผ์„ ํ•ฉ๋‹ˆ๋‹ค.
Thread.sleep(1000)
val elapsedNs = System.nanoTime() - beginNs

// ์•„๋ž˜ ์˜ˆ์™€ ๊ฐ™์ด ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๋งค์ง ๋„˜๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์„œ ์‰ฝ๊ฒŒ ์‹ค์ˆ˜ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
val elapsedMs = elapsedNs / 1000 / 1000

// ์•„๋ž˜ ์˜ˆ์™€ ๊ฐ™์ด Java์˜ TimeUnit API ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
import java.util.concurrent.TimeUnit
val elapsedMs2 = TimeUnit.NANOSECONDS.toMillis(elapsedNs)

// ์•„๋ž˜ ์˜ˆ์™€ ๊ฐ™์ด Scala์˜ Duration API๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
import scala.concurrent.duration._
val elapsedMs3 = elapsedNs.nanos.toMillis

์˜ˆ์™ธ ๊ฒฝ์šฐ:

  • ์ด๋ฏธ ์ž˜ ํ…Œ์ŠคํŠธ ๋˜์–ด์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ์œ„ํ•ด ์ƒˆ๋กœ์šด ์ข…์†์„ฑ(dependency)์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ๋งŒ์•ฝ ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๊ฐ€ ๊ฐ„๋‹จํ•œ ํŽธ์ด๋ผ๋ฉด, ๋‹ค์‹œ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ƒˆ๋กœ์šด ์ข…์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค ๋‚ซ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผ ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋ง์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ์กด์˜ ํ•จ์ˆ˜๊ฐ€ ์‚ฌ์šฉ ์šฉ๋„์— ์ตœ์ ํ™” ๋˜์–ด ์žˆ์ง€ ์•Š๊ณ  ๋Š๋ฆฐ ๊ฒฝ์šฐ. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์—๋Š” ๋ฒค์น˜๋งˆํ‚น์„ ๋จผ์ € ํ•˜๊ณ , ๋„ˆ๋ฌด ์ด๋ฅธ ์ตœ์ ํ™”๋Š” ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published