# Build (Fat Jar 생성)
## build.sbt에
## addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.6")
## 추가 필요
### 생성된 jar 위치 : target/scala-[version]/*.jar
sbt assembly
Scala Binaries (선택 사항)
REPL(scala), 단독 Compile(scalac)을 이용하기 위해 필요
# 실행
scala
scala> 1
res0: Int = 1
# 종료
scala> :quit
## 혹은
scala> :q
IDE
- Intellij에 기본적으로 SBT 내장. Intellij만 설치하면 된다.
REPL 실행 방법
왼쪽 Project Tree에서 아무 Class에 오른쪽으로 누르고 “Run Scala Console”.
Hello, world!
가변 인자 args를 입력 받아 “Hello, world!”를 출력하는 예제 작성
Script
Compile 하지 않고, Shell script 처럼 사용
// class, main 함수를 선언하지 않고도 가변인자 args 사용 가능!
args.foreach(print)
println()
> scala script.scala Hello , world !
Hello, world!
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
내장 제어 구문
전체적으로 재귀나 내장함수(reduce, map, filter, reduce)로 대체가 가능하기 때문에 사용을 자제하자. 제어 구문은 함수형 표현에 적합하지 않다.
키워드로써 break, continue는 존재하지 않는다. (함수형 언어에 어울리지 않으므로.)
굳이 break를 쓰고 싶다면 scala.util.control.Breaks 에 있는 것을 사용할 수 있다…만…
제어 구문들도 함수이므로 var/val에 할당할 수 있다. (이 때, 기본적으로 바로 계산한다. lazy loading이 필요하면 lazy를 맨 앞에 붙이자.)
while은 대체로 중간 결과를 저장하는 변수 var가 필요하기 때문에 사용을 자제하자.
for
foreach 문만 존재한다. 그러나 대체로 반복하는 경우 내장 함수(foreach, reduce, map, filter 등)이 존재하기 때문에 특별한 경우가 아니면 큰 필요성이 없다.
/* 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")
for-yield
<EXPRESSION>의 값을 Collection로 반환. (map 과 같은 효과)
for (<IDENTIFIER> <- <ITERATOR>) yield <EXPRESSION>
// 인자가 없기 때문에 ()를 생략한 형태.
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)
try-catch
try, finally는 단 하나의 식만 포함한다면 중괄호 생략 가능.
try {
val f = new FileReader(".")
} catch {
case ex: FileNotFoundException => ex.printStackTrace()
case ex: IOException => ex.printStackTrace()
} finally println("done")
다른 제어 구조, 함수와 마찬가지로 결과로 값을 반환한다. 이 때, finally 절의 값은 버려진다.
finally에는 값을 반환하지 말고 Side-effect(결과 완료 알림, 파일 저장등)만 이용하자.
// 결과 : 2
// Java 형식으로 finally 절 안에 return문을 명시적으로 사용하면 try/cath의 결과를 덮어쓰게 된다.
// 잘 생각해보면 Java에서 finally는 어떤 결과로든 항상 실행되는 구문이기 때문.
def test1(): Int = try return 1 finally return 2
// 결과 : 1
// Scala 형식으로 작성하면 의도된대로 가장 처음에 만나는 값을 반환한다.
def test2(): Int = try 1 finally 2
// 결론적으로 finally에는 값을 사용하지 말자.
match
case의 대안. Java와 달리 타입에 상관없이 어떤 상수값이라도 사용 가능. 이 때, match 앞에 오는 비교대상의 타입을 기본적으로 다른다. (그렇게 되면 case로 비교하는 값들도 그 타입을 따른다.) '_'은 default에 대응하며 “완전히 알려지지 않은 값”을 의미.
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
}
Functions
재귀 함수인 경우 반드시 Return Type 명시, 그외에는 생략 가능.
함수 길이 긴 경우 사람이 읽기 쉽게 Return Type을 명시하는 게 좋음.
“return” 구문은 선택. 생략 가능.
본론 문장이 하나면 괄호 생략 가능
Retury Type의 “Unit”은 Java의 void. 즉, Side-effect를 위해서만 실행하는 함수.
parameter는 val이므로 값을 재할당할 수 없다. (예를 들어 Call-by-reference를 이용하려는 경우)
함수 안에 함수를 정의할 수 있다.
인자가 없는 메소드는 소괄호를 생략할 수 있다. 그러나 관례로서 Side-Effect가 있거나 함수 본래 작업 이외의 부가적인 작업이 존재할 경우 소괄호를 붙인다.
def sum(x: Int, y: Int): Int = {
x + y
}
// 위와 같은 표현
def sum(x: Int, y: Int) = x + y
// Unit 함수. Return 값이 없음을 의미.
// Type 추론이 되므로 Return Type에 Unit 생략 가능.
def greeting(): Unit = println("Hello, world!")
Local functions
함수의 중첩이 가능.
def test1(x: Int) = {
def test1(y: Int) = x * y
test1(10)
}
println(test1(5)) // 50
Placeholder syntax
위치 표시자.
/* 고차함수를 더 간략하게 작성하는 방법 */
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
Partitially applied function
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)
Closure
/* Java의 경우 final(immuitable)로 선언해야만 접근 가능한 것에 비해 var(mutable)도 사용 가능. */
/* Javascript와 동일. */
/* 변수 바인딩 */
var sum = 0
(1 to 10).foreach(sum += _) ;; 바깥 범위(scope)에 있는 sum의 값에 누적.
println(sum) // 55
/* 함수 바인딩 */
def scope1(x: Int) = {
/*def scope2(y: Int) = x + y
scope2 _*/
// 위와 같은 표현
x + (_: Int)
}
// 각 scope1에 바인딩된 바깥쪽 x를 다르게 설정
val closure1 = scope1(1)
val closure2 = scope1(2)
println(closure1(10)) // 11
println(closure2(10)) // 12
Paramter & Argument
/* 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))
Currying
함수를 1급 객체로 취급하기 때문에 가능. 다중 인수를 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것.
def curriedSum(x: Int)(y: Int) = x + y
//def curriedSum(x: Int) = (y: Int) => x + y
println(curriedSum(1)(2))
By-name parameters
값, 함수 모두 “값”으로 평가하면서 인자를 넘기고 싶은 경우 사용.
이 역시 함수를 1급 객체로 취급하기 때문에 가능. paramter의 Lazy evalution. 성능상의 이점을 가진다.
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)
eC (Effectively Constant) : 연산에 사실상 상수 시간이 걸리는데, 이는 Vector의 최대 길이나 Hash key 분포와 같은 일부 가정에 따라 달라질 수 있다.
L (Linear) : 연산에 선형 시간이 걸린다. 즉, Collections 크기에 비례하여 시간이 걸린다.
결론적으로 head가 임의 위치 접근이 필요한 경우 Vector 선택.
Mutable
Set
같은 이름의 mutable, immutable 패키지가 있으니 주의.
import scala.collection.mutable
val set1 = Set("a", "b")
// 원소 추가
// Collection이 val인 경우, mutable package의 import 필요!
/// set1.+=("b") 와 같은 표현
set1 += "b"
Map
// 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)
Java는 Field, Method, Type, Package의 4가지 Namespace를 갖지만 Scala는 Field, Method를 2가지 Namespace 갖는다.
Field가 Parameter없는 Method를 Override할 수 있다. (서로 동일시 한다.)
이 같은 성질로 같은 Class에 같은 이름의 Field와 Method를 정의할 수 없다.
Package도 Field 및 Method처럼 Namespace를 공유하는 이유는 Singleton Object에서 Field와 Method를 import하기 위해서다.
Access Modifier
Modifier
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가 작성된 파일 이름이 같을 필요가 없으나 권장.
class Person {
// member 변수와 Getter/Setter 서술
}
Constructor
Primary Constructor
주 생성자만이 Super Class의 생성자 호출 가능!
// 아래와 같이 class를 선언하면 member 변수들은 private val
// 이후 값을 변경할 수 없음.
class Person (name: String, age: Int) {
def getName: String = name
def getAge : Int = age
}
// 아래와 같이 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)
// Scala Compiler는
// Class 내부에 있으면서
// Field나 Method 정의에 들어 있지 않은 코드를
// Primary Constructor에 삽입한다.
/// 객체 생성과 동시에 println 메시지 출력.
class Person (name: String, age: Int) {
println("Created!" + name + " / " + age)
}
Auxiliary Constructor
모든 보조 생성자는 반.드.시. 같은 클래스에 속한 다른 생성자를 호출하는 코드로 시작해야 한다. 결국 주 생성자를 호출하게 만드는 효과가 있다.
주 생성자만이 Super Class의 생성자 호출 가능!
class Person (val name: String, val age: Int) {
def this(age: Int) = this("meteor", age);
override def toString: String = name + " / " + age
}
Checking Preconditions
유효성 검사 때문에 생성되는 try ~ catch, if ~ else 에서 벗어날 수 있다!
조건을 만족시키지 못하면 IllegalArgumentException 발생. 메시지가 없으면 “requirement failed”
class Person (name: String, age: Int) {
require(name != null, "name is not nullable.")
require(age > 0)
override def toString: String = name + " / " + age
}
Operator Defination
Operator Overloading.
class Person (val name: String, val age: Int) {
def +(p: Person) = this.toString + "\n" + p.toString
override def toString: String = name + " / " + age
}
object Client extends App {
val p1 = new Person("hyuk", 30)
val p2 = new Person("meteor", 26)
println(p1 + p2)
}
Method Overloading
Java와 동일하나 Scala는 Type을 유추하기 때문에 적합한 Method의 Parameter Type 일치 결과가 없다면 'ambiguous reference' 오류를 출력. 그리고 Overloading보다 DEFAULT PARAMETER VALUES를 이용을 권장. Method 수를 줄일 수 있다.
'apply' Method
'Default Method' 또는 'Injector Method'라고도 불린다. 이는 Method 이름없이 괄호를 사용하여 호출하는 기능을 갖고 있다.
val l = List(1, 2, 3)
// 같은 의미
println(s"${l.apply(1)} ${l(1)}")
위 예제의 'List.apply(index)' 처럼 상식적으로 이해할 수 있는, 자연스러운 연산에 적용되어야 한다.
object
class 대신 object로 시작. Java의 static class에 대한 대안.
Instance를 생성할 수 없다. 그러므로 당연히 Constructor도 선언 불가.
“Singleton”으로 작동하므로 첫 접근이 되지 않는 한 인스턴스가 만들어지지 않는다.
대개 특정 객체에 상관없는 Side-effect Method들(대개 Service 목적의 Util 성질을 가진 공통 사용 Method)을 담아두고 사용하는 Method 사용.
// Companion class
class Person (val name: String, val age: Int)
// Companion object
object Person {
def print(person: Person) :Unit = println(person.name + " / " + person.age)
}
object Client extends App {
val person: Person = new Person("Amy", 11)
Person.print(person)
}
/* Amy / 11 */
Companion object
어떤 class 이름이 같은 object. 그 피대상인 class는 “Companion class”라고 한다. Companion object와 'apply' Method를 이용하여 Companion class의 Factory Pattern 을 적용할 때 사용. 이 때, object의 field들이 “private”으로 선언되어 있더라도 접근 가능.
Companion class와 Companion object는 반드시 같은 소스 파일에 있어야 한다!
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()
case class
자동으로 유용한 Method들 생성. (Kotlin의 'data class'와 동일한 개념)
Instance의 field들을 Tuple로 추출하여 Pattern Matching에 case class instance를 사용할 수 있도록 함
copy
class
Deep copy!
equals
class
Reference가 아닌 구조적 동등성(모든 필드가 일치)으로 비교. '=='로도 호출 할 수 있다.
hashCode
class
Instance의 field들의 Hash code 반환. hash 기반 collections에서 동등성에 이용.
toString
class
Field들을 String으로 전환
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)
Java의 경우 1.5 기준으로 @Override를 명시할 수 있으나 필수사항이 아닌데 반면, Scala는 강제한다. 이로 인해 “우연한 Override”로 “fragile base class”가 생성되는 문제를 줄여준다.
class Person (val name: String, val age: Int) {
override def toString: String = name + " / " + age
}
Abstract Class
Java와 비교하여
method에 abstract를 붙이면 안된다.
abstract는 class 혹은 abstract class를 mix-in한 trait의 method만 붙일 수 있다.
본체가 없으면 abstract method로 인식한다. (이는 trait도 동일)
본체가 있는 concrete method를 구현할 수 있다.
Field가 파라미터 없는 메소드를 Override 할 수 있다.
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
}
Parameter field 정의
같은 이름의 생성자 Paramter와 Field를 동시 정의하는 단축 표기. 특정 Field에 할당하는 이름 중복을 피하기 위한 Boilerplace 제거.
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
Super class의 Constuctor 호출
Java의 경우 상속을 받으면 반드시 부모 Class의 생성자와 같은 형태의 생성자를 호출하고 super()를 하는 과정을 아래와 같이 간략화할 수 있다.
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)) {
}
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가 필요한 상황에서 사용한다.
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("안녕, 세상아!")
}
Stackable Modifications
object Client extends App {
val queue = new BasicIntQueue with Doubling
queue.put(10)
println(queue.get())
// 20
}
abstract class IntQueue {
def get(): Int
def put(x: Int)
}
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
override def get(): Int = buf.remove(0)
override def put(x: Int) = buf += x
}
trait Doubling extends IntQueue {
// trait에서 super로 접근할 때는 override abstract가 필요.
override abstract def put(x: Int): Unit = super.put(x * 2)
}
여러 Trait를 mix-in 했을 때 우선 순위
가장 오른쪽에 있는 Trait의 효과부터 적용된다.
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
Type parameterization
Bounded Type
Type Parameter를 특정 Class나 거의 Sub type 또는 Base type으로 제한하는 방법.
반환해야 하는(생산하는) 값이 Super type인데, 인자로 받은(제공하는) 값이 Sub type이면 Super type의 Method를 호출할 수 없음.
이런 이유로 T1은 동등한 레벨의 타입이거나 상위 타입(Super type)이어야 한다.
“상위 추상화 타입 → 하위 구체화 타입”
// 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
}
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()
}
}
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”를 앞에 붙인다.