์ด ๊ฐ์ด๋๋ผ์ธ์ ์์ง๋์ด๋ง ํ ๋ฐ ๊ด๋ฒ์ํ ์คํ ์์ค ์ปค๋ฎค๋ํฐ์ ๊ฒฝํ์ ๋ฐํ์ผ๋ก ์์ฑ๋์์ต๋๋ค.
์ฝ๋๋ ์ ์์ ์ํด ํ ๋ฒ ์ฐ์ฌ์ง์ง๋ง, ๋ง์ ๋ค๋ฅธ ์์ง๋์ด๋ค์ ๊ทธ ๊ฐ์ ์ฝ๋๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์์ ํ๊ณ ์ฝ์ต๋๋ค. ๋๋ถ๋ถ์ ๋ฒ๊ทธ๋ค์ ๋ณดํต ์ฝ๋์ ๋ณ๊ฒฝ์ผ๋ก๋ถํฐ ๋์ต๋๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ๋ ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง ๋ณด์์ฑ์ ํฅ์์ํค๊ธฐ ์ํด ์ฐ๋ฆฌ์ ์ฝ๋๋ฅผ ์ต์ ํ ํด์ผํฉ๋๋ค. ์ด๋ฅผ ์ํ ์ต์ ์ ๋ฐฉ๋ฒ์ ๊ฐ๋จํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋๋ค.
Scala๋ ๋งค์ฐ ๊ฐ๋ ฅํ๋ฉฐ ์ฌ๋ฌ๊ฐ์ง ํ๋ฌ๋ค์์ ์ ์ฉ ๊ฐ๋ฅํ ์ธ์ด์ ๋๋ค. ์ฐ๋ฆฌ๋ ์๋์ ๊ฐ์ด๋๋ผ์ธ์ ํตํด ์ฌ๋ฌ๊ฐ์ง ํ๋ก์ ํธ๋ฅผ ๋น ๋ฅธ ์๋๋ก ์งํํ๊ณ ์์ต๋๋ค. ํ์ด๋ ํ์ฌ์ ์๊ตฌ์ฌํญ ๋ฑ์ ๋ฐ๋ผ์ ์ผ๋ถ ๋ค๋ฅด๊ฒ ์ ์ฉ ํด์ผ ํ ์๋ ์์ต๋๋ค.
- ๋ฌธ์ ์ญ์ฌ
- ๊ตฌ๋ฌธ ์คํ์ผ
- Scala ์ธ์ด์ ๊ธฐ๋ฅ
- ์ผ์ด์ค ํด๋์ค์ ๋ถ๋ณ์ฑ
- apply ํจ์
- override ์์ ์
- ํํ ์ถ์ถ
- Call by Name
- ๋ค์ค ๋งค๊ฐ ๋ณ์ ํ๊ธฐ
- ํน์ ๋ฌธ์ ํจ์ (์คํผ๋ ์ดํฐ ์ค๋ฒ๋ก๋ฉ)
- ํ์ ์ถ๋ก
- Return ์์ฝ์ด
- ์ฌ๊ท ์ฉ๋ฒ๊ณผ ๊ผฌ๋ฆฌ ์ฌ๊ท ์ฉ๋ฒ
- Implicits
- ์์ธ ์ฒ๋ฆฌ (Try vs try)
- Options
- ๋ชจ๋๋ ์ฑ์ด๋
- ์ฌ๋ณผ ๋ฆฌํฐ๋ด
- ๋์์ฑ ์ ์ด
- ์ฑ๋ฅ
- Java ํธํ์ฑ
- ํ ์คํธ
- ๊ธฐํ
์ฐ๋ฆฌ๋ ์ฃผ๋ก 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๊ฐ ์ด์์ ํ์ ์๋ฆฌ๋จผํธ๋ฅผ ํฌํจ ํ๊ณ ์๋ค๋ฉด, ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ์์ ๊ฐ๋ฅ์ฑ์ด ๋๋ค." - 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 ์ ์์ ์ ๋ฏธ์ฌ๋ 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๋ฅผ ๋ง๋ค ๋ ์์ ๊ฐ์ ํ์ ์ฌ์ฉ ๋ฉ๋๋ค.
-
์์ผ๋ ์นด๋๋ฅผ ์ด์ฉํ 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 => ... })
์ผ์ด์ค ํด๋์ค๋ ์ผ๋ฐ ํด๋์ค ์ ๋๋ค๋ง, ์ปดํ์ผ๋ฌ๊ฐ ์๋์ผ๋ก ์๋์ ๊ฐ์ ํญ๋ชฉ๋ค์ ์ง์ํฉ๋๋ค.
- ์์ฑ์์ ํ๋ผ๋ฉํฐ๋ค์ ์ํ ํผ๋ธ๋ฆญ 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)
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 ์์ ์๋ ์ถ์ ํจ์๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ๋ ๊ฒฝ์ฐ์ด๊ฑด ์ค์ ํจ์๋ฅผ ์ค๋ฒ๋ผ์ด๋ ํ๋ ๊ฒฝ์ฐ์ด๊ฑด ํญ์ ๋ถ์ฌ์ค์ผ ํฉ๋๋ค. 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 ์ ํผํ๋๋ก ํฉ๋๋ค. () => 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์ ํจ์(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
}
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๋ฅผ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
-
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] }
๋๋ฒ์งธ๋ ํ์คํ ์ด๋ค ์๋ฌ๋ฅผ ํธ๋ค๋ง ํ๋์ง ํธ์ถํ๋ ์ชฝ์์ ์๊ธฐ๊ฐ ์ฝ์ต๋๋ค.
-
๊ฐ์ด ๋น์ด ์์ ์ ์์ ๋
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)
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
์ ์ฌ์ฉ์ ํผํด์ผ ํฉ๋๋ค.
๋์์ฑ์ ์ ์ดํ๊ธฐ ์ํด์ 3๊ฐ์ง์ ์ถ์ฒํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ์์ด์ ์ฌ์ฉํ์ง ์์ต๋๋ค, ์๋ํ๋ฉด ์ด๋ ํ๋ก๊ทธ๋จ์ ๋์ฑ ๋ณต์กํ๊ฒ ํ๊ณ ๋ฐ๋๋ฝ์ ์ผ์ผํฌ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
java.util.concurrent.ConcurrentHashMap
: ๋ชจ๋ ์ํ๊ฐ map์ ์ ์ฅ ๋๊ณ , ๋น๋ฒํ ์ ๊ทผ์ด ์ผ์ด ๋ ๋ ์ฌ์ฉํฉ๋๋ค.
private[this] val map = new java.util.concurrent.ConcurrentHashMap[String, String]
java.util.Collections.synchronizedMap
: ๋ชจ๋ ์ํ๊ฐ map์ ์ ์ฅ๋๊ณ , ๋น๋ฒํ ์ ๊ทผ์ด ์ผ์ด๋์ง ์์ง๋ง ์ฝ๋๋ฅผ ์์ ํ๊ฒ ๋ง๋ค๊ณ ์ถ์ ๋ ์ฌ์ฉํฉ๋๋ค. ๋ง์ฝ ์๋ฌด๋ฐ ๋์์ฑ ์ ๊ทผ์ด ์ผ์ด๋์ง ์๋๋ค๋ฉด, JVM JIT ์ปดํ์ผ๋ฌ๋ ๋๊ธฐํ์ ์ค๋ฒํค๋๋ฅผ ์ง์ธ ์ ์์ต๋๋ค.
private[this] val map = java.util.Collections.synchronizedMap(new java.util.HashMap[String, String])
- ๋ช ์์ ์ผ๋ก ๋๊ธฐํ๋ฅผ ํ๋ ๋ฐฉ๋ฒ: ์ด ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๋ณ์๋ค์ ๋์์ฑ ์ ์ด๋ฅผ ํ ์ ์๋๋ก ํฉ๋๋ค. 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: _*) }
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
๋ณ์๋ค์ด ์ธ๋ถ ๊ฐ์ ํด๋์ค์ ๋ค๋ฅธ ๊ฐ์ฒด๋ค๋ก๋ถํฐ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ์ฃผ์ํ์๊ธฐ ๋ฐ๋๋๋ค. ๋ฐ๋ผ์, ์ด๋ฅผ 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๋ ๋ฒจ์์ ์ ์ ์๊ฒ ๋ ธ์ถ๋ ํจ์๋ ์ฝ๋ฐฑํจ์์ ์ด ๋๊ธฐํ ๋ณ์๋ค์ ๋ ธ์ถ ํ๋ ๊ฒ์ ํผํฉ๋๋ค.
- ๋ณต์กํ ๋ชจ๋์์๋ ์์ ๋ด๋ถ ๋ชจ๋์ ๋ง๋ค์ด ๋์์ฑ์ ์ํ ๋ณ์๋ค์ ๊ฐ๊ณ ์๋๋ก ํฉ๋๋ค.
๋๋ถ๋ถ์ ์ฝ๋๋ ๋ณดํต ์ฑ๋ฅ์ ๋ํ์ฌ ํฌ๊ฒ ๊ณ ๋ ค๋์ง ์์ต๋๋ค. ์ฑ๋ฅ ํฅ์์ ์ํ ์ฝ๋๋ฅผ ์ํด์ ๋ช ๊ฐ์ง ํ์ด ์์ต๋๋ค.
์ข์ microbenchmark๋ฅผ ์์ฑํ๋ ๊ฒ์ ์์ฃผ ์ด๋ ค์ด ์ผ ์ ๋๋ค, ์๋ํ๋ฉด Scala ์ปดํ์ผ๋ฌ์ JVM JIT ์ปดํ์ผ๋ฌ๋ ๋ง์ ๋ง๋ฒ๊ณผ ๊ฐ์ ์ผ์ ์ฝ๋์ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์๋ํ๋ฉด ๋๊ฒ์ ๊ฒฝ์ฐ microbenchmark๋ ์ธก์ ํ๊ณ ์ ํ๋ ๊ฒ์ ์ธก์ ํ์ง ์์ต๋๋ค.
microbenchmark๋ฅผ ์์ฑํ๋ ค๋ฉด jmh๋ฅผ ์ฌ์ฉํ์๊ธฐ ๋ฐ๋๋๋ค. "์ฃฝ์ ์ฝ๋" ์ ๊ฑฐ์ ์์๊ฐ ๋์ฒด ๊ทธ๋ฆฌ๊ณ ๋ฃจํ ํ๊ธฐ๋ฅผ ์ดํดํ๊ธฐ ์ํด ์ง์ ๋ชจ๋ ์ํ์ ์ฝ๋๋ก ํฉ๋๋ค.
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
}
์ฑ๋ฅ์ ๊ณ ๋ คํ ์ฝ๋๋ฅผ ์ํด, ๊ฐ์ ํจ์ ํธ์ถ๊ณผ boxing์ ํผํ๋ Option
๋ณด๋ค๋ null
์ ์ฌ์ฉ์ด ๊ถ์ฅ๋ฉ๋๋ค. Null์ ๊ฐ์ ์ ์๋ ๋ณ์์๋ Nullable ์ด๋ผ๊ณ label์ ํ์คํ ํ๋๋ก ํฉ๋๋ค.
class Foo {
@javax.annotation.Nullable
private[this] var nullableField: Bar = _
}
์ฑ๋ฅ์ ๊ณ ๋ คํ ์ฝ๋๋ฅผ ์ํด, Scala์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ๋ณด๋ค๋ Java์ collection ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์ด ๊ถ์ฅ๋ฉ๋๋ค. ์ด๋ Scala์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ข ์ข Java ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ณด๋ค ๋๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ฑ๋ฅ์ ๊ณ ๋ คํ ์ฝ๋๋ฅผ ์ํด, 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 ํธํ ๊ฐ๋ฅํ API๋ฅผ ๋ง๋ค๊ธฐ ์ํ ๊ฐ์ด๋๋ผ์ธ์ ๋ค๋ฃจ๊ณ ์์ต๋๋ค. ์ด ๊ฒ์ ํ์ฌ ๋น์ ์ด ๋ง๋ค๊ณ ์๋ ์ปดํฌ๋ํธ๊ฐ Java์์ ํธํ์ฑ์ ํ์๋ก ํ์ง ์๋๋ค๋ฉด ์ ์ฉ๋์ง ์์ต๋๋ค. ์ด ๊ฐ์ด๋๋ผ์ธ์ ์ฃผ๋ก ์ฐ๋ฆฌ๊ฐ Spark์ Java API๋ฅผ ๋ง๋๋ ๊ณผ์ ์์ ์ฐ๋ฆฌ๊ฐ ๊ฒฝํํ ๊ฒ์ ๋ฐํ์ผ๋ก ์์ฑ๋์์ต๋๋ค.
์๋์ Javaํน์ง๋ค์ Scala์ ์์ต๋๋ค. ๋ง์ฝ ์๋์ ๊ธฐ๋ฅ์ด ํ์ํ๋ค๋ฉด Java์์ ์ ์ํ์ฌ ์ฌ์ฉํ์๊ธฐ ๋ฐ๋๋๋ค. ๊ทธ๋ฌ๋ Scala ๋ฌธ์๋ฅผ ๋ณด์๋ฉด Java๋ก ์ ์๋ ํ์ผ์ ๋ํ ๋ณด์ฅ์ ํ์ง ์๋๋ค๊ณ ๋ช ์๋์ด ์์ต๋๋ค.
- Static ๋ณ์
- Static ๋ด๋ถ ํด๋์ค
- Java enum
- Annotation
์ธ๋ถ์์ ๊ตฌํ ๋ ์ ์๋ ์ธํฐํ์ด์ค์ ๊ฒฝ์ฐ ์๋์ ํญ๋ชฉ์ ๋ช ์ฌํ์๊ธธ ๋ฐ๋๋๋ค:
- 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 = { ... }
}
๋ณ์นญ์ ์ฌ์ฉํ์ง ์์ต๋๋ค. ์ด๋ค์ ๋ฐ์ดํธ์ฝ๋์ 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 = ... }
ํด๋์ค๋ ํจ์๋ฅผ ์ํด 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()
}
๋ง์ฝ ์์ธ์ ํ์ ์ด ๊ตฌ์ฒด์ ์ผ๋ก ๋ช ์ ๋ ์ ์๋ค๋ฉด, ์ฝ๋ ์ค๋ฉ์ ์งํ์ผ ์ ์์ต๋๋ค. ๋ฎ์ ๋ ๋ฒจ์ ํ ์คํธ๋ฅผ ํ๊ฑฐ๋ ๊ตฌ์ฒด์ ์ธ ํ์ ์ ์์ธ๋ฅผ ๋ฐ์์ํค๋๋ก ํด๋น ์ฝ๋๋ฅผ ์์ ํด์ผ ํฉ๋๋ค.
_์ง์ ์๊ฐ_์ ๊ณ์ฐํ ๋ ํน์ _ํ์์์_์ ํ์ธ ํ ๋์๋, ์ฌ์ง์ด 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
์ ๋์ผ์ฑ ๊ฒ์ฌ ๋ ์ฌ์ค 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)์ ์ถ๊ฐํด์ผ ํ๋ ๊ฒฝ์ฐ, ๋ง์ฝ ์ด๋ฌํ ํจ์๊ฐ ๊ฐ๋จํ ํธ์ด๋ผ๋ฉด, ๋ค์ ๊ตฌํํ๋ ๊ฒ์ด ์๋ก์ด ์ข ์์ฑ์ ์ถ๊ฐํ๋ ๊ฒ ๋ณด๋ค ๋ซ์ต๋๋ค. ํ์ง๋ง, ํ ์คํธ๋ฅผ ํด์ผ ๋๋ค๋ ๊ฒ์ ์์ง ๋ง์์ผ ํฉ๋๋ค.
- ๊ธฐ์กด์ ํจ์๊ฐ ์ฌ์ฉ ์ฉ๋์ ์ต์ ํ ๋์ด ์์ง ์๊ณ ๋๋ฆฐ ๊ฒฝ์ฐ. ์ด๋ฌํ ๊ฒฝ์ฐ์๋ ๋ฒค์น๋งํน์ ๋จผ์ ํ๊ณ , ๋๋ฌด ์ด๋ฅธ ์ต์ ํ๋ ํผํ๋๋ก ํฉ๋๋ค.