Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


8장

메소드

  • 객체의 멤버인 함수
import scala.io.Source
object LongLines {
    def processFile(filename: String, width: Int) { // 메소드
        val source = Source.fromFile(filename)
        for (line <- source.getLines())
            processLine(filename, width, line)
    }

    private def processLine(filename: String, width: Int, line: String) { // 도우미 메소드
        if (line.length > width)
            println(filename +": "+ line.trim)
    }
}

지역 함수

  • 함수 안에서 정의한 함수
  • 정의를 감싸고 있는 블록 내에서만 접근 가능
def processFile(filename: String, width: Int) {
    def processLine(filename: String, width: Int, line: String) { // 지역 함수
        if (line.length > width)
            println(filename +": "+ line)
    }

    val source = Source.fromFile(filename)
    for (line <- source.getLines())
        processLine(line)
}
  • 바깥 함수의 파라미터를 사용 가능
def processFile(filename: String, width: Int) {
    def processLine(line: String) {
        if (line.length > width) // 바깥 함수의 파라미터 width를 사용
            println(filename +": "+ line) // 바깥 함수의 파라미터 filename을 사용
    }

    val source = Source.fromFile(filename)
    for (line <- source.getLines())
        processLine(filename, width, line)
}

1급 계층 함수

  • 함수를 정의, 호출, 리터럴로 표기해 값처럼 주고받을 수 있다.
  • 함수 리터럴 : 소스 코드 상의 표기로 존재
  • 함수 값(function value) : 실행 시점에 객체로 존재
(x: Int) => x + 1 // 함수 리터럴 예
scala> var increase = (x: Int) => x + 1
increase: Int => Int = <function1>

scala> increase(10)
res0: Int = 11

scala> increase = (x: Int) => x + 9999 // 재할당 가능
increase: Int => Int = <function1>

scala> increase(10)
res1: Int = 10009
val someNumbers = List(-11, -10, -5, 0, 5, 10)
someNumbers.foreach((x: Int) => println(x)) // 함수 리터럴 사용 예 - foreach
someNumbers.filter((x: Int) => x > 0) // 함수 리터럴 사용 예 - filter

간단한 형태의 함수 리터럴

someNumbers.filter((x: Int) => x > 0)
someNumbers.filter((x) => x > 0) // 타깃 타이핑
someNumbers.fileter(x => x > 0)

위치 표시자

  • 하나 이상의 파라미터에 대한 위치 표시자로 밑줄(_) 사용
  • '채워넣어야 할 빈칸'
someNumbers.fileter(x => x > 0)
someNumbers.filter(_ > 0)
val f = _ + _   // error
val f = (_: Int) + (_: Int) // 타입 명시

부분 적용한 함수

someNumbers.foreach(x => println(x))
someNumbers.foreach(println _)  // 전체 인자 목록에 대한 위치 표시자 사용 = 부분 적용한 함수
scala> def sum(a: Int, b: Int, c: Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int

scala> val a = sum _    // 인자 3개를 받는 함수 값을 인스턴스화
a: (Int, Int, Int) => Int = <function3>

scala> a(1,2,3) // == a.apply(1,2,3)
res0: Int = 6
  1. 변수 a : 함수 값 객체
  2. 함수 값 : 컴파일러가 sum _을 해석해 자동으로 만든 클래스의 인스턴스
  3. 클래스 : 인자 3개를 받는 apply 메소드 존재
  4. apply 메소드 : sum _에서 빠진 인자가 3개이기 때문에 3개의 인자를 받음
  • 밑줄 있는 함수 값으로 감싸면, 메소드나 중첩 함수를 변수에 할당하거나, 인자로 전달 가능
  • 인자 중 일부만 넘기는 것도 가능
val b = sum(1, _:Int, 3)
  • 명확하게 함수가 필요한 경우 밑줄 생략 가능
someNumbers.foreach(println _)
someNumbers.foreach(println)

클로저

  • 클로저(closure)
    • 함수 리터럴로부터 실행 시점에 만들어낸 객체인 함수 값
    • 함수 리터럴 본분에 있는 모든 자유 변수에 대한 바인딩을 포획해서 자유 변수가 없게 닫음
  • 자유 변수(free variable) : 함수에서 의미를 부여하지 않은 변수
  • 바운드 변수(bound variable) : 함수의 문맥 내에서만 의미가 있는 변수
  • 닫힌 코드 조각(closed term) : 자유 변수가 없는 함수 리터럴
  • 열린 코드 조각(open term) : 자유 변수가 있는 함수 리터럴 -> 클로저
(x: Int) => x + more
// x : 바운드 변수
// more : 자유 변수
  • 클로저가 변화를 감지함.
  • 표획한 인자는 힙에 재배치됨

특별한 형태의 함수 호출

반복 파라미터

def echo(args: String*) =   // args: Array[String]
    for (arg <- args) println(arg)
echo("hello", "world")

val arr = Array(...)
echo(arr: _*)   // 배열을 직접 전달하는 경우

이름 붙인 인자

def speed(distance: Float, time: Float): Float =
    distance / time

speed(100, 10)
speed(distance = 100, time = 10)    // 이름 붙인 인자 사용
speed(time = 10, distance = 100)    // 순서 변경 가능

디폴트 인자 값

  • 인자에 기본값 지정시 생략 가능
def printTime(out: java.io.PrintStream = Console.out) =
    out.println("time = "+ System.currentTimeMillis())

printTime() // 디폴트 값인 Console.out 사용
printTime(Console.err)  // 인자 지정
  • 일부 인자만 이름 붙여서 지정 가능
def printTime2(out: java.io.PrintStream = Console.out, divisor: Int = 1) =
    out.println("time = "+ System.currentTimeMillis()/divisor)

printTime2()
printTime2(out = Console.err)
printTime2(divisor = 1000)

꼬리 재귀

def approximate(guess: Double): Double =
    if (isGoodEnough(guess)) guess
    else approximate(improve(guess))    // 재귀 호출
  • 마지막에 자신을 재귀호출 하는 꼬리 재귀의 경우 최적화함
  • 단, 간접적 재귀(번갈아 호출하는 등), 함수 값 호출 등은 최적화되지 않음


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


7장

if 표현식

var filename = "default.txt"
if (!args.isEmpty)
  filename = args(0)
val filename =
  if (!args.isEmpty) args(0)
  else "default.txt"

while 루프

  • 수행 결과는 Unit 타입
  • 할당의 결과는 Unit 값(())
(line == readLine()) != "" // () != "" 이므로 항상 참

while

while (a != 0) {
    val temp = a
    a = b%a
    b = temp
}

do-while

do {
    line = readLine()
    println("Read: " + line)
} while (line != "")

for 표현식

컬렉션 순회

  • 배열 순회
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 는 전체 본문의 앞에 위치

try 표현식

예외 발생

throw new IllegalArgumentException // throw
  • throw는 Nothing이라는 타입을 결과값으로 갖는다.

예외 잡기

try {
  val f = new FileReader("input.txt")
} catch {
  case ex: FileNotFoundException => // ...
  case ex: IOExceptoin => // ...
}

finally 절

val file = new FileReader("input.txt")
try {
  // ...
} finally {
  file.close()
}

결과값

  • try, catch 값이 결과값으로 사용됨
  • finally는 결과값을 바꾸지 않음

match 표현식

  • 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
}

break / continue

  • 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)
  • 인터프리터는 구문마다 새로운 스코프 생성


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


6장

생성자

주 생성자

class Rational(n: Int, d: Int)
  • 클래스 파라미터 : n, d

주 생성자 코드

  • 클래스 내부에 있으면서 필드나 메소드 정의에 들어 있지 않은 코드는 주 생성자에 들어감
class Rational(n: Int, d: Int) {
    println("Created "+ n +"/"+d)
}

toString 메소드 오버라이드

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)


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


5장

기본 타입

  • 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'

이스케이프 시퀀스(escape sequence)

  • \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 메소드를 수행함

연산자 우선순위

  • 연산자 시작하는 문자에 따라서 아래와 같은 순서로 우선 순위 판단
  1. (all other special characters)
  2. */%
  3. +-
  4. :
  5. =! <> &
  6. ˆ
  7. |
  8. (all letters)
  9. (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


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


4장

클래스

  • 클래스 정의
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 }

세미콜론 추론

다음 세가지 경우가 아니면 줄의 끝은 세미콜론으로 취급된다.

  1. 줄이 명령을 끝낼 수 있는 단어로 끝나지 않는다. 마침표(.)나 중위 연산자 등의 줄의 맨 끝에 있는 경우
  2. 다음 줄의 맨 앞이 문장을 시작할 수 없는 단어로 시작한다.
  3. 줄이 () 사이나 [] 사이에서 끝난다.

싱글톤 객체

  • 클래스 정의와 비슷하나 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

Application 트레이트

object FallWinterSpringSummer extends Application {
    // main 메소드 내용
}
  • scala.Application -> scala.App


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


3장

배열

  • 파라미터화(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부터 시작

집합과 맵

변경가능 / 불가능 모두 제공

변경 불가능 Set

var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"

변경 가능 Set

import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"

변경 불가능 Map

val romanNumeral = Map(
    1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
  • -> 메소드를 호출하면 해당 객체를 키로하고 인자를 값으로 하는 원소가 2개인 튜플을 생성

변경 가능 Map

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")


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.

2장

인터프리터 사용법

  • 대화형 셸 $ 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
  • 명령행 인자 접근 : args args(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절)


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


1장

스칼라

  • 확장 가능 언어
  • 객체지향과 함수형 프로그래밍 개념을 정적 타입 언어에 합쳐 놓은 언어

확장성

타입

  • 언어가 기본 지원하는 것처럼 느껴지는 스칼라 표준 라이브러리로 타입을 제공
  • 라이브러리로 새로운 타입을 만들어 내장 타입처럼 편하게 사용할 수 있음

제어구조

  • 액터 모델을 구현하는 라이브러리를 제공하여 스칼라 내장 구성요소처럼 사용함
  • 새로운 분야에 대한 추상화를 설계/구현하고 언어가 원래 지원하는 것처럼 사용 가능

확장성의 이유

객체지향적

  • 스칼라는 순수한 형태의 객체지향 언어 : 모든 값은 객체, 모든 연산은 메소드
  • Trait : 끼워넣기 좋은 구성 요소

함수형

  • 함수는 1급 계층 값 : 인자로 넘길 수 있고, 변수에 저장 가능하고, 함수에서 반환 가능
  • 메소드에는 부수 효과가 없어야 함

스칼라 장점

  • 호환성 : 자바 메소드 호출, 필드 접근, 클래스 상속, 인터페이스 구현 가능
  • 간결함 : 적은 얼개 코드 사용, 타입 추론, 라이브러리 정의 도구
  • 고수준 : 높은 수준 추상화 가능
  • 정적타입
    • 타입 추론으로 장황함 회피, 패턴 매치와 타입을 쓰고 합성하는 방법으로 유연성 확보
    • 프로퍼티 검증, 안전한 리펙토링, 문서화

스칼라의 뿌리

  • 문법 : 자바, C#
  • 일관성 있는 객체 모델 : 스몰토크, 루비
  • 보편적인 내포 : 알골, 시뮬라, Beta, gbeta
  • 메소드 호출, 필드 선택을 통일하게 처리하는 단일 접근 원칙 : Eiffel
  • 함수형 프로그래밍 접근 방식 : SML, OCaml, F#
  • 스칼라 표준 라이브러리의 고차 함수 중 다수 : ML, 하스켈
  • 암시적 파라미터 : 하스켈의 타입 클래스
  • 액터 기반 동시성 라이브러리 : 얼랑


PHP Best Practices를 읽다가 내친 김에 번역을 했는데, 이걸로 모던PUG에서 "UTF-8 in PHP"라는 주제로 발표까지 하게 되었다. 별 내용은 아니라서 발표할 것까지는 없다고 생각했는데, 발표 준비를 한 덕분에 PHP의 UTF-8 지원에 대해서 정리해볼 수 있었다.

PHP7에서 기본 문자열이 유니코드(UTF-8)로 되는 것으로 알고 있었기때문에 곧 필요없어질 내용이라고 생각했는데, 조사하다보니 PHP7에서도 유니코드 지원 상황은 변함이 없는 것으로 확인되었다. RFC에 UString 클래스 제안이 있던 정도였지만, 이마저도 통과되지 않았다.

가장 흥미로웠던 것은 MySQL의 "utf8mb4" 인코딩이었다. UTF-8에서 4바이트를 사용하는 문자가 있다는 것은 어렴풋이 알고 있었지만, 보통 사용하는 "utf8" 인코딩은 4바이트 문자를 저장하지 못한다는 사실은 모르고 있었다. 이것 때문에 UTF-8의 "Plane"이라는 것에 대해서도 알아보게 되었다.

별 내용은 없지만, 아래는 발표때 사용한 슬라이드이다.


월의 몇 주차를 어떻게 계산해야할지 찾아보았지만, 마땅한 표준은 존재하지 않았다. 하지만, 해당 년도의 첫 주 계산은 ISO 8601 표준에 정해져있다는 것을 알게되었다. ISO 8601 표준의 위키피디아 해당 항목을 보면 매 해의 첫주의 정의는 아래와 같이 설명될 수 있다고 한다,

  • the week with the year's first Thursday in it (the formal ISO definition),
  • the week with 4 January in it,
  • the first week with the majority (four or more) of its days in the starting year, and
  • the week starting with the Monday in the period 29 December – 4 January.

가장 간단한 설명은 1월 4일이 포함된 주가 첫주라고 보면 된다는 것이다. 년에 관한 것만 나와있어서 그다지 도움은 안됐지만, 한가지 또 알게 된 사실은 ISO 8601 표준에서 "주"는 월요일에서 시작해서 일요일로 끝나는 것으로 본다.

하지만, 달력에는 주로 일요일이 시작인 것처럼 나와있는 경우가 많아서 한주의 시작요일을 변경할 수 있도록 해서 한번 만들어봤다.


'Developing' 카테고리의 다른 글

PHP에서의 UTF-8 지원  (0) 2015.06.08
PHP 에러(Error)를 예외(Exception)로 변환하기  (0) 2015.01.30
PHP 에서 IE 구분  (0) 2015.01.19

+ Recent posts