-
Notifications
You must be signed in to change notification settings - Fork 0
Thomas
meye edited this page May 11, 2017
·
2 revisions
Ref : https://gist.github.com/meye/154d09a95c16e67e2b633953c810c71a
- 확장 가능 언어
- 객체지향과 함수형 프로그래밍 개념을 정적 타입 언어에 합쳐 놓은 언어
- 언어가 기본 지원하는 것처럼 느껴지는 스칼라 표준 라이브러리로 타입을 제공
- 라이브러리로 새로운 타입을 만들어 내장 타입처럼 편하게 사용할 수 있음
- 액터 모델을 구현하는 라이브러리를 제공하여 스칼라 내장 구성요소처럼 사용함
- 새로운 분야에 대한 추상화를 설계/구현하고 언어가 원래 지원하는 것처럼 사용 가능
- 스칼라는 순수한 형태의 객체지향 언어 : 모든 값은 객체, 모든 연산은 메소드
- Trait : 끼워넣기 좋은 구성 요소
- 함수는 1급 계층 값 : 인자로 넘길 수 있고, 변수에 저장 가능하고, 함수에서 반환 가능
- 메소드에는 부수 효과가 없어야 함
- 호환성 : 자바 메소드 호출, 필드 접근, 클래스 상속, 인터페이스 구현 가능
- 간결함 : 적은 얼개 코드 사용, 타입 추론, 라이브러리 정의 도구
- 고수준 : 높은 수준 추상화 가능
- 정적타입
- 타입 추론으로 장황함 회피, 패턴 매치와 타입을 쓰고 합성하는 방법으로 유연성 확보
- 프로퍼티 검증, 안전한 리펙토링, 문서화
- 문법 : 자바, C#
- 일관성 있는 객체 모델 : 스몰토크, 루비
- 보편적인 내포 : 알골, 시뮬라, Beta, gbeta
- 메소드 호출, 필드 선택을 통일하게 처리하는 단일 접근 원칙 : Eiffel
- 함수형 프로그래밍 접근 방식 : SML, OCaml, F#
- 스칼라 표준 라이브러리의 고차 함수 중 다수 : ML, 하스켈
- 암시적 파라미터 : 하스켈의 타입 클래스
- 액터 기반 동시성 라이브러리 : 얼랑
- 대화형 셸
$ scala -
res번호식별자는 변수 처럼 사용 가능 ex>res0 * 3 println("Hello, world!")
-
val: 재할당 불가능 -
var: 재할당 가능 - 타입 추론으로 타입을 지정하지 않아도 됨
val msg = "Hello"
val msg2: java.lang.String = "Hello"
val msg3: String = "Hello"
var msg4 = "Hello"
msg4 = "World"
def max(x: Int, y: Int): Int = {
if (x < y) x
else y
}
-
def: 함수 정의 시작 -
max: 함수 이름 -
(x: Int, y: Int): 인자 항목 -
Int: 함수 결과 타입 -
=: 등호 -
{ }: 함수 본문
Unit 결과 타입은 함수가 의미있는 결과를 반환하지 않는다는 의미
- 실행 방법 예
$ scala hello.scala - 명령행 인자 접근 :
argsargs(0), args(1), ...
- while
while (i < args.length) - if
if (i != 0) print(" ") - foreach
args.foreach(arg => println(arg))args.foreach(println) - for
for (arg <- args) println(arg)
(x: Int, y: Int) => x + y
-
(x: Int, y: Int): 인자 항목 -
=>: 오른쪽 화살표 -
x + y: 함수 본문
함수 리터럴이 인자를 하나만 받는 문장인 경우에는 해당 인자에 이름을 붙일 필요가 없다 (8.6절)
- 파라미터화(parameterization) : new를 사용해 객체를 인스턴스화 할때에 값과 타입을 파라미터로 전달하는 것
val greetStrings: Array[String] = new Array[String](3)
- 인스턴스 타입은 타입 파라미터 까지만 포함됨 :
(3)은 제외
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "World!\n"
for (i <- 0 to 2)
print(greetStrings(i))
-
메소드가 파라미터를 하나만 요구하는 경우, 그 메소드를 점(.)과 괄호 없이 호출할 수 있다. 단, 호출 대상 객체가 명시적으로 지정되어 있는 경우에 가능
0 to 2 === (0).to(2) - 스칼라는 전통적인 의미의 연산자가 없으므로 연산자 오버로드를 제공하지 않으며, 대신 +, -, *, / 등의 문자를 메소드 이름으로 사용 가능
1 + 2 === (1).+(2) - 배열도 평범한 인스턴스 : 변수 뒤에 하나 이상의 값을 괄호로 둘러싸서 호출하면, 스칼라는 변수에 대해 apply 메소드 호출
greetStrings(i) === greetStrings.apply(i) -
변수 뒤에 괄호로 둘러싼 인자들이 있는 표현식에 할당을 하면, 스칼라는 update 메소드를 호출
greeStrings(0) = "Hello" === greetStrings.update(0, "Hello")
val numNames = Array("zero", "one", "two")
- Array의 동반 객체에 정의된 apply라는 팩토리 메소드 호출
같은 타입의 변경 불가능한 시퀀스
val oneTwo = List(1,2)
val threeFour = List(3,4)
val oneTwoThreeFour = oneTwo ::: threeFour
- 리스트 자체를 변환하지 않고 새 값을 갖는 리스트를 만든다.
val twoThree = List(2,3)
val oneTwoThree = 1 :: twoThree
:로 이름이 끝나는 메소드는 오른쪽 피연산자에 대해 호출한다.
val oneTwoThree = 1 :: 2 :: 3 :: Nil
List() === Nil
-
List()/Nill: 빈 리스트 -
List(1,2,3)/1 :: 2 :: 3 :: Nill: 원소들로 구성된 리스트 -
List(1, 2) ::: List(3, 4): 두 리스트를 연결한 리스트 -
thrill(2): thrill 리스트의 세번째 원소 -
thrill.count(s => s.length == 4): 길이가 4인 것의 갯수 -
thrill.drop(2): 처음 두 원소를 뺀 리스트 -
thrill.dropRight(2): 마지막 두 원소를 뺀 리스트 -
thrill.exists(s => s == "until"): "until"이 리스트에 있는지 여부 -
thrill.filter(s => s.length == 4): 길이가 4인 것들의 리스트 -
thrill.forall(s => s.endsWith("l")): 모든 원소가 끝이 "l"로 끝나는지 여부 -
thrill.foreach(s => print(s)): 리스트의 모든 원소에 대해서print실행 -
thrill.foreach(print): 상동 -
thrill.head: 첫번째 원소 -
thrill.init: 마지막 원소 제외한 나머지 -
thrill.isEmpty: 리스트가 비어있는지 여부 -
thrill.last: 마지막 원소 -
thrill.length: 리스트의 길이 -
thrill.map(s => s + "y"): 각 원소 뒤에 "y"를 추가한 리스트 -
thrill.mkString(", "): 원소들을 ", "로 연결한 문자열 -
thrill.remove(s => s.length == 4): 길이가 4인 것을 제외한 리스트 -
thrill.reverse: 원소를 역순으로 한 리스트 -
thrill.sort((s, t) => s.charAt(0).toLower < t.charAt(0).toLower): 알파벳 순서 정렬한 리스트 -
thrill.tail: 첫번째 원소를 제외한 리스트
다른 타입의 원소들을 갖는 변경 불가능한 시퀀스
val pair = (99, "Luftballons")
println(pair._1) // 99
println(pair._2) // Luftballons
-
apply메소드는 항상 동일한 타입 객체를 반환하기 때문에pair(0)과 같이 할 수 없음. - 하스켈, ML 등의 영향으로 인덱스가 1부터 시작
변경가능 / 불가능 모두 제공
var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"
val romanNumeral = Map(
1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
->메소드를 호출하면 해당 객체를 키로하고 인자를 값으로 하는 원소가 2개인 튜플을 생성
import scala.collection.mutable.Map
val treasureMap = Map[Int, String]()
treasureMap += (1 -> "Go to Island.")
- 코드에
var가 있으면 명령형 스타일, 아니면 함수형 스타일 -
val, 불변객체, 부수 효과 없는 메소드를 먼저 사용해보고, 꼭 필요한 경우에만 var, 가변객체, 부수 효과 있는 메소드를 사용하라
import scala.io.Source
if (args.length > 0) {
for (line <- Source.fromFile(args(0)).getLines())
println(line.length + " " + line)
}
else
Console.err.println("Please enter filename")
- 클래스 정의
class ChecksumAccumulator {
// 클래스 정의
}
- 객체 생성 : new
new ChecksumAccumulator
- 필드, 메소드는 멤버
- 필드 정의 : val, var
- 인스턴스 변수라고도 함
- 기본 접근 수준은 전체 공개
- 비공개 필드 : private
class ChecksumAccumulator {
private var sum = 0
}
- 메소드 정의 : def
- 메소드 파라미터는 val
- 권장 스타일
- return을 명시적으로 사용하지 않는 것 -> 메소드는 한 값을 계산하는 표현식
- 하나의 표현식만 있으면 중괄호 생략
- 결과식이 짧으면 def 문 줄에 함께 쓰기
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = ~(sum & 0xFF) + 1
}
- 결과 타입이 Unit 인 경우
- 부수 효과를 위해 사용
- = 생략하고 {}로 감싸는 것으로 표현 가능
def add(b:Byte) { sum += b }
다음 세가지 경우가 아니면 줄의 끝은 세미콜론으로 취급된다.
- 줄이 명령을 끝낼 수 있는 단어로 끝나지 않는다. 마침표(.)나 중위 연산자 등의 줄의 맨 끝에 있는 경우
- 다음 줄의 맨 앞이 문장을 시작할 수 없는 단어로 시작한다.
- 줄이 () 사이나 [] 사이에서 끝난다.
- 클래스 정의와 비슷하나 object 키워드 사용
object ChecksumAccumulator {
// ...
}
- 싱글톤 객체는 1급 계층
- 파라미터를 전달할 방법 없음
- 클래스를 확장(extend)하거나 트레이트를 믹스인(mix in) 가능
- 자체 타입을 정의하지는 않음
- 동반 객체(companion object) : 어떤 클래스 이름과 동일한 이름의 싱글톤 객체
- 동반 클래스(companion class) : 어떤 싱글톤 객체와 동일한 이름의 클래스
- 독립 객체(standalone object) : 동반 클래스가 없는 싱글톤 객체
- 클래스와 동반 객체는 서로의 비공개 멤버에 접근할 수 있다.
- 다음과 같은 main 메소드를 가진 독립 싱글톤 객체가 애플리케이션의 시작점
- 인자 : Array[String]
- 반환값 : Unit
object Summer {
def main(args: Array[String]) {
// ...
}
}
- 클래스 이름과 파일이름을 동일하게 만드는 것이 강제 사항은 아님.
- 컴파일 : scalac, fsc
object FallWinterSpringSummer extends Application {
// main 메소드 내용
}
- scala.Application -> scala.App
- Byte / Short / Int / Long : 8 / 16 / 32 / 64 bits 부호 있는 정수(2의 보수)
- Char : 16 bits 유니코드 문자
- String : Char 시퀀스
- Float / Double : 32 / 64 bits IEEE 754 단정도/배정도 부동소수점 수
- Boolean : true / false
- 10진 : 5, 7, ..
- 16진 : 0x5, 0x00FF
- Long : 35L, 31l
- 1.2345
- 1,2345e1
- 123E45
- Float : 1.2345F, 3e5f
- Double(기본) : 3e5D
- 'A'
- '\101'
- '\u0041'
- \n : 줄바꿈(\u000A)
- \b : 백스페이스(\u0008)
- \t : 탭(\u0009)
- \f : 페이지 넘김(\u000C)
- \r : 줄 맨 앞으로(\u000D)
- " : 큰따옴표(\u0022)
- ' : 작은따옴표(\u0027)
- \ : 역슬래스(\u005C)
- "hello"
- """Welcome ... """
- stripMargin
println("""|Welcome to Ultamix 3000.
|Type "HELP" for help.""".stripMargin)
- 작은따옴표 뒤에 오는 식별자(알파벳,숫자) : 'ident
- 'ident -> 내부에서 Symbol("ident") 호출됨
- intern. 같은 심볼 리터럴은 동일한 객체를 참조.
- true / false
- 어떤 메소드든지 연산자가 될 수 있음
s.indexOf('o', 5)
s indexOf ('o', 5) // 동일함
- 연산자는 메소드
- 중위 연산자 : 1 + 2 == (1).+(2)
- 전위 연산자 : -2.0 == (2.0).unary_-
- 후위 연산자 : s toLowerCase == s.toLowerCase()
- +, -, *, /, %
-
, <, >=, <=, !
- &&, ||
- &, |, ^
- ==, != 사용하여 두 객체가 동일한지 여부를 확인
- null을 포함한 어떤 객체라도 사용 가능
- 내부적으로 null인지 확인하고, null 이 아닌 경우 equals 메소드를 수행함
- 연산자 시작하는 문자에 따라서 아래와 같은 순서로 우선 순위 판단
- (all other special characters)
- */%
- +-
- :
- =! <> &
- ˆ
- |
- (all letters)
- (all assignment operators)
- 메소드가 ':'로 끝나면 오른쪽에서 왼쪽으로 연산되고, 다른 경우는 왼쪽에서 오른쪽으로 연산
- 묵시적 변환(implicit conversion)으로 기본 타입에 더 많은 메소드를 실행 가능
0 max 5 // 5
0 min 5 // 0
-2.7 abs // 2.7
-2.7 round // -3L
1.5 isInfinity // false
(1.0/0) isInfinity // true
4 to 6 // Range(4,5,6)
"bob" capitalize // "Bob"
"robert" drop 2 // "bert"
- 위와 같은 메소드는 다음과 같은 레퍼 타입에 정의되어 있음
- Byte - scala.runtime.RichByte
- Short - scala.runtime.RichShort
- Int - scala.runtime.RichInt
- Char - scala.runtime.RichChar
- Float - scala.runtime.RichFloat
- Double - scala.runtime.RichDouble
class Rational(n: Int, d: Int)
- 클래스 파라미터 : n, d
- 클래스 내부에 있으면서 필드나 메소드 정의에 들어 있지 않은 코드는 주 생성자에 들어감
class Rational(n: Int, d: Int) {
println("Created "+ n +"/"+d)
}
class Rational(n: Int, d: Int) {
override def toString = n +"/"+ d
}
- require 문 사용
- 인자가 false 이면 IllegalArgumentException 예외 발생
class Rational(n: Int, d: Int) {
require(d != 0)
override def toString = n +"/"+ d
}
class Rational(n: Int, d: Int) {
require(d != 0)
val numer: Int = n
val denom: Int = d
override def toString = n +"/"+ d
def add(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
}
- 파라미터 필드(10.6절)를 사용하면 간단하게 작성 가능
- this
def lessThan(that: Rational) =
this.numer * that.denom < that.numer * this.denom // this 생략 가능
def max(that: Rational) =
if (this.lessThan(that)) that else this // lessThan 앞의 this는 생략 가능
-
def this(...)으로 시작
class Rational(n: Int, d: Int) {
require(d != 0)
val numer: Int = n
val denom: Int = d
def this(n: Int) = this(n, 1) // 보조 생성자
override def toString = n +"/"+ d
def add(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
}
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs) // 비공개 필드
val numer: Int = n / g // 초기화 코드
val denom: Int = d / g // 초기화 코드
def this(n: Int) = this(n, 1)
override def toString = n +"/"+ d
def add(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
private def gcd(a: Int, b: Int): Int = // 비공개 메소드
if (b == 0) a else gcd(b, a % b)
}
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer: Int = n / g
val denom: Int = d / g
def this(n: Int) = this(n, 1)
override def toString = n +"/"+ d
def + (that: Rational): Rational = // 덧셈 연산자 정의
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
def * (that: Rational): Rational = // 곱셈 연산자 정의
new Rational(numer * that.numer, denom * that.denom)
private def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
}
- 영숫자 식별자 : 문자나 밑줄(_)로 시작. 문자, 숫자, 밑줄 가능
- 필드, 메소드 인자, 지역 변수, 함수 등 : camelCase 사용이 관례
- 클래스, 트레이트, 상수 등 : CamelCase 사용이 관례
- 연산자 식별자 : 하나 이상의 연산자 문자로 이루어져 있음.
+ ++ ::: <?> :-> - 혼합 식별자 :
unary_+ myvar_= - 리터럴 식별자 : 역따옴표로 둘러싼 문자열
class Rational(n: Int, d: Int) {
require(d != 0)
private val g = gcd(n.abs, d.abs)
val numer: Int = n / g
val denom: Int = d / g
def this(n: Int) = this(n, 1)
override def toString = n +"/"+ d
def + (that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
def + (i: Int): Rational = // 덧셈 연산자 메소드 오버로드
new Rational(numer + i * denom, denom)
def - (that: Rational): Rational =
new Rational(
numer * that.denom - that.numer * denom,
denom * that.denom
)
def - (i: Int): Rational = // 뺄셈 연산자 메소드 오버로드
new Rational(numer - i * denom, denom)
def * (that: Rational): Rational =
new Rational(numer * that.numer, denom * that.denom)
def * (i: Int): Rational = // 곱셈 연산자 메소드 오버로드
new Rational(numer * i, denom)
def / (that: Rational): Rational =
new Rational(numer * that.denom, denom * that.numer)
def / (i: Int): Rational = // 나눗셈 연산자 메소드 오버로드
new Rational(numer, denom * i)
private def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
}
- 암시적 타입 변환 메소드 지정 가능
- 해당 스코프 내에 메소드가 존재해야 동작함
implicit def intToRational(x: Int) = new Rational(x)
var filename = "default.txt"
if (!args.isEmpty)
filename = args(0)
val filename =
if (!args.isEmpty) args(0)
else "default.txt"
- 수행 결과는 Unit 타입
- 할당의 결과는 Unit 값(
())
(line == readLine()) != "" // () != "" 이므로 항상 참
while (a != 0) {
val temp = a
a = b%a
b = temp
}
do {
line = readLine()
println("Read: " + line)
} while (line != "")
- 배열 순회
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere)
println(file)
- Range 순회
for (i <- 1 to 4) // 1,2,3,4
println("Iteration "+i)
for (i <- 1 until 4) // 1,2,3
println("Iteration "+i)
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere if file.getName.endsWith(".scala")) // '.scala'로 끝나는 파일만
println(file)
for {
file <- filesHere
if file.isFile // 필터 추가
if file.getName.endsWith(".scala")
} println(file)
def fileLines(file: java.io.File) =
scala.io.Source.fromFile(file).getLines().toList
def grep(pattern: String) =
for {
file <- fileHere
if file.getName.endsWith(".scala") // 바깥 루프
line <- fileLines(file)
if line.trim.matches(pattern) // 안쪽 루프
} println(file +": "+ line.trim)
grep(".*gcd.*")
def fileLines(file: java.io.File) =
scala.io.Source.fromFile(file).getLines().toList
def grep(pattern: String) =
for {
file <- fileHere
if file.getName.endsWith(".scala")
line <- fileLines(file)
trimmed = line.trim // val 변수 처럼 선언하고 사용
if trimmed.matches(pattern)
} println(file +": "+ trimmed)
grep(".*gcd.*")
def scalaFiles =
for {
file <- filesHere
if file.getName.endsWith(".scala")
} yield file
-
yield는 전체 본문의 앞에 위치
throw new IllegalArgumentException // throw
- throw는 Nothing이라는 타입을 결과값으로 갖는다.
try {
val f = new FileReader("input.txt")
} catch {
case ex: FileNotFoundException => // ...
case ex: IOExceptoin => // ...
}
val file = new FileReader("input.txt")
try {
// ...
} finally {
file.close()
}
- try, catch 값이 결과값으로 사용됨
- finally는 결과값을 바꾸지 않음
- case 내에는 어떤 종류의 상수라도 사용 가능
- 모든 case마다 break문이 암묵적으로 존재
val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
case "salt" => println("pepper")
case "chips" => println("salsa")
case "eggs" => println("bacon")
case _ => println("huh?") // default case
}
- continue -> if
- break -> 불리언 변수
- 재귀 함수 사용(꼬리 재귀 호출)
- scala.util.control.Breaks 클래스
import scala.util.control.Breaks._
import java.io._
val in = new BufferedReader(new InputStreamReader(System.in))
breakable {
while (true) {
println("? ")
if (in.readLine() == "") break
}
}
- 중괄호 사용시 새로운 스코프 생성(예외 존재 - for 등)
- 안쪽 스코프에서 바깥 스코프의 동일 이름 변수를 선언하면 바깥 변수는 가려짐(shadow)
- 인터프리터는 구문마다 새로운 스코프 생성