11월 6일에 있었던 AWS Dev Day 2018에서 들었던 내용 중, 기억할만한 것 몇가지를 정리해보려고 한다. 발표 자료가 공유되면 그때 정리하려고 했는데, 생각보다 늦게 공개되는 모양이다. 그래서, 별 내용은 없더라도 머릿속에 조금 더 남아있을 때에 남겨본다.

딥러닝 서비스에 쓰이는 GPU 인스턴스 비용 효율을 위한 스팟(Spot) 활용기

  • "콴다" 서비스를 운영한 경험 공유
  • 컨테이너 환경을 사용하기 위해서 nvidia-docker를 사용
  • 사실 GPU 비용은 비싸서 온프레미스 환경이 더 저렴할 수도 있음.
    • 온프레미스에 배포하고 피크 타임 시에만 클라우드 사용하는 것도 방법.
    • 컨테이너 이미지를 보유하고 있으면 환경이 문제되지 않음.
  • 인스턴스 체크만으로는 확장에 필요한 지표로 삼기 어려워서 서비스 처리 상태 지표를 추가로 사용함.
  • 스팟 인스턴스 활용
    • 스팟 인스턴스가 죽는 경우에는 온디맨드 인스턴스 배포
    • 스팟 인스턴스 종료 2분전 상태를 확인해서 온디맨드 인스턴스 시작하는 도구 필요함.

Apache MXNet/Gluon으로 쉽고 빠르게 구현하는 딥러닝

  • Gluon은 Apache MXNet 인터페이스로 NumPy와 유사한 API를 제공하고, 데이터 변환이 가능
  • gradient를 자동으로 계산하는 Autograd 기능을 제공
  • GluonNLP, GluonCV 등의 프로젝트 있음

AWS 기반 블록체인 (1부) 블록체인 환경 구성하기 / (2부) 블록체인 서비스 개발하기

쿠버네티스 환경에 geth 등을 배포하여 PoA 프라이빗 네트워크를 구성하는 것을 보여주고, 2부에서 DApp 개발하는 시연을 보여주는 시간이었다.

코드는 모두 아래의 깃헙에 있다.

나중에 알게된 것이지만, 쿠버네티스에 이더리움 배포는 helm을 사용하면 간편하게 되는 듯 하다. 그리고, 개발 시연 중에 사용/언급한 도구들은 아래와 같다.

Service Mesh를 Amazon EKS로 구현하기

LinkerdIstio라는 도구를 사용하는 것을 보여주었는데, AWS에서 이와 관련한 서비스를 내놓을 듯?

'Etc' 카테고리의 다른 글

Pencil  (0) 2017.11.29
자바 퍼즐러  (0) 2015.06.25
Ship it 성공적인 소프트웨어 개발 프로젝트를 위한 실용 가이드  (0) 2015.06.24

Spring Social이 개발 중단된고한다. Spring Security 5에서 직접 OAuth2를 지원하는 것이 그 이유라고 하는데, Spring Social의 API Binding을 만든 목적이 Spring Social의 Connection Framework 사용방법을 보여주기 위한 것이라는 걸 이제야 알았다. 어쩐지 예전에 spring-social-facebook의 Facebook Graph API 지원이 느린 것에 의문을 가지고 있었는데, 그 정도의 위상을 가진 프로젝트였기 때문에 그렇게 관리되었던 모양이다.

이제 대신 Spring Security 5를 사용하여 소셜사이트 연동하는 방법으로 구현해야 한다. 즉, Spring Social에서 원래 의도했던 바와 같이 Spring Security의 OAuth2 지원을 사용해서 API binding을 직접 만들어 사용하는 것이다.

UI 프로토타이핑 도구를 찾다가 Pencil 이라는 프로젝트를 발견했다. 꽤 오래전에 한번 보고 기능이 너무 단순하고 예쁘지 않아서 관심을 가지지 않았던 기억이 있는데, 최근에 다시 봤더니 꽤 쓸만해졌다. "The Next Version"이라는 말이 있는 걸 보니 Electron 기반으로 새로 만든 것 같다.

사실 비용 지출에 부담이 없는 상황이면 Balsamiq을 사용해도 되겠지만, 이런 도구를 무료로 사용할 수 있다니 훌륭한 것 같다.



'Etc' 카테고리의 다른 글

AWS Dev Day 2018 Seoul  (0) 2018.11.12
자바 퍼즐러  (0) 2015.06.25
Ship it 성공적인 소프트웨어 개발 프로젝트를 위한 실용 가이드  (0) 2015.06.24

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

22장

리스트

package scala
abstract class List[+T]
  • 리스트는 추상 클래스 : new List 불가능
  • 리스트는 공변적(+T) : List[Int] 타입 값을 List[Any] 타입 변수에 할당 가능
  • 리스트 연산은 세가지 기본 메소드(List 클래스의 추상 메소드)로 만들 수 있음
    • def isEmpty: Boolean
    • def head: T
    • def tail: List[T]

Nil 객체

case object Nil extends List[Nothing] {
  override def isEmpty = true
  override def head: Nothing = throw new NoSuchElementException("head of empty list")
  override def tail: List[Nothing] = throw new NoSuchElementException("tail of empty list")
}

:: 클래스

final case class ::[T](hd: T, tl: List[T]) excents List[T] {
  def head = hd
  def tail = tl
  override def isEmpty: Boolean = false
}

// 좀더 짧은 :: 클래스
final case class ::[T](val head: T, val tail: List[T]) excents List[T] {
  override def isEmpty: Boolean = false
}

추가 메소드

def length: Int = if (isEmpty) 0 else 1 + tail.length

def drop(n: Int): List[T] =
  if (isEmpty) Nil
  else if (n <= 0) this
  else tail.drop(n - 1)

def map[U](f: T => U): List[U] =
  if (isEmpty) Nil
  else f(head) :: tail.map(f)

리스트 구성 메소드

def ::[U >: T](x: U): List[U] = new scala.::(x, this)
  • 원소 타입 T의 슈퍼 타입인 U가 타입 파라미터
  • List 정의가 공변적이기 때문
def :::[U >: T](prefix: List[U]): List[U] =
  if (prefix.isEmpty) this
  else prefix.head :: prefix.tail ::: this

실제 List 클래스

// 효율을 위해서 루프 사용
final override def map[U](f: T => U): List[U] = {
  val b = new ListBuffer[U]
  var these = this
  while (!these.isEmpty) {
    b += f(these.head)
    these = these.tail
  }
  b.toList
}

final case class ::[T](hd: T, private[scala] var tl: List[T]) excents List[T] {
  def head = hd
  def tail = tl
  override def isEmpty: Boolean = false
}
package scala.collection.immutable
final class ListBuffer[T] extends Buffer[T] {
  private var start: List[T] = Nil
  private var last0: ::[T] = _
  private var exported: Boolean = false
  ...

  override def toList[T] = {
    exported = !start.isEmpty
    start
  }

  override def += (x: T) {
    if (exported) copy()
    if (start.isEmpty) {
      last0 = new scala.::(x, Nil)
      start = last0
    } else {
      val last1 = last0
      last0 = new scala.::(x, Nil)
      last1.tl = last0  // 콘즈의 tl이 var 인 이유
    }
  }
}


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

21장

암시적 변환

암시적 변환 예

  • 서로를 고려하지 않은 두 독립 소프트웨어를 한데 묶는데 유용
// (1) 자바 코드를 스칼라 코드로 그대로 포팅
val button = new JButton
button.addActionListener(
  new ActionListener {
    def actionPerformed(event: ActionEvent) {
      println("pressed!")
    }
  }
)

// 암시적 변환 코드
implicit deff function2ActionListener(f: ActionEvent => Unit) =
  new ActionListener {
    def actionPerformed(event: ActionEvent) = f(event)
  }

// (2) 변환 코드 사용
button.addActionListener(
  function2ActionListener(
    (_: ActionEvent) => println("pressed!")
  )
)

// (3) 암시적 변환 사용
button.addActionListener(
  (_: ActionEvent) => println("pressed!")
)

암시적 변환 규칙

  • 표시 규칙 : implicit로 표시한 정의만 검토 대상이다
  • 스코프 규칙 : 삽입할 implicit 변환은 스코프 내에 단일 식별자로만 존재하거나, 변환의 결과나 원래 타입과 연관이 있어야 한다(단, 원 타입이나 변환 결과 타입의 동반 객체에 있는 암시적 정의도 검토 대상임)
  • 한번에 하나만 규칙 : 오직 하나의 암시적 선언만 사용한다
  • 명시성 유선 규칙 : 코드가 그 상태 그대로 타입 검사를 통과한다면 암시를 통한 변환을 시도하지 않는다

암시적 변환 이름

  • 명시적으로 변환을 사용할 때 사용
  • 특정 지점에 사용 가능한 암시적 변환을 확인해야할 경우(사용하는 변환만 임포트)

암시가 사용되는 부분

  • 예상 타입 암시적 변환 : implicit def int2double(x: Int): Double = x.toDouble
  • 호출 대상 객체 변환 : "abc".exists === stringWrapper("abc").exists
// 호출 대상 객체 변환 - 새 타입과 함께 통합
implicit def intToRational(x: Int) = new Rational(x, 1)

val oneHalf = new Rational(1, 2)
1 + oneHalf
// Rational = 3/2
// 호출 대상 객체 변환 - 새로운 문법 흉내 내기
package scala
object Predef {
  class ArrowAssoc[A](x: A) {
    def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
  }
  implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
}

Map(1 -> "one", 2 -> "two", 3 -> "three")
  • 암시적 파라미터

암시적 파라미터

// 암시적 파라미터 예
class PreferredPrompt(val preference: String)

object Greeter {
  def greet(name: String)(implicit prompt: PreferredPrompt) {
    println("Welcome, "+ name +". The system is ready.")
    println(prompt.preference)
  }
}

// 명시적 사용 예
val bobsPrompt = new PreferredPrompt("relax> ")
Greeter.greet("Joe")(bobsPrompt)

// 암시적 파라미터 사용 예
object JoesPrefs {
  implicit val prompt = new Preferredprompt("Yes, master> ")
}

import JoesPrefs._
Greeter.greet("Joe")
// 암시적 파라미터 예 - 두개 파라미터
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)

object Greeter {
  def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) {
    println("Welcome, "+ name +". The system is ready.")
    print("But while you work, ")
    println("why not enjoy a cup of "+ drink.preference +"?")
    println(prompt.preference)
  }
}

object JoesPrefs {
  implicit val prompt = new PreferredPrompt("Yes, master> ")
  implicit val drink = new PreferredDrink("tea")
}

import JoesPrefs._

// 명시적 사용 예
Greeter.greet("joe")(prompt, drink)

// 두 암시적 파라미터 사용 예
import JoesPrefs._
Greeter.greet("joe")
  • 암시적 파라미터 타입은 충분히 드물거나 특별해야한다. -> 암시적 파라미터의 타입 안에는 역할을 알려주는 이름을 최소한 하나 이상 사용하는 것이 좋다.
  • 암시적 파라미터는 앞쪽 파라미터 목록에서 명시적으로 사용한 인자 타입에 대한 정보를 제공하는 경우에 많이 사용된다.
// 원소 타입이 Ordered의 서브 타입이 아닌 리스트에서 사용할 수 없음!
def maxListUpBound[T <: Ordered[T]](elements: List[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListUpBound(rest)
      if (x > maxRest) x
      else maxRest
  }

// 암시적 파라미터 사용
def maxListImpParm[T](elements: List[T])(implicit orderer: T => Ordered[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListImpParm(rest)(orderer)
      if (orderer(x) > maxRest) x
      else maxRest
  }

뷰 바운드

  • 파라미터에 대해 implicit을 사용하는 경우, 메소드 본문 안에서 implicit 값으로 취급됨
// 메소드 본문 안에서 implicit 사용
def maxList[T](elements: List[T])(implicit orderer: T => Ordered[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListImpParm(rest)    // orderer
      if (x > maxRest) x                    // orderer(x)
      else maxRest
  }

// 뷰 바운드 사용
def maxList[T <% Ordered[T]](elements: List[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListImpParm(rest)    // orderer
      if (x > maxRest) x                    // orderer(x)
      else maxRest
  }
  • T <% Ordered[T] : T를 Ordered[T]로 다룰 수 있는 모든 T 타입을 사용할 수 있다
  • 타입 T가 이미 Ordered[T]인 경우에는 Predef의 identity 함수 사용
implicit def identity[A](x: A): A = x // 받은 객체를 그대로 반환함

적용 가능한 암시적 변환이 여러개인 경우

  • ~ Scala 2.7 : 컴파일 오류
  • Scala 2.8 : 더 구체적인 변환 적용
    • 인자 타입이 다른 것의 서브 타입이다
    • 변환이 모두 메소드인데, 하나를 둘러싼 클래스가 다른 것을 둘러싼 클래스를 확장한다.
val cba = "abc".reverse
// 2.7 : cba는 컬렉션 - String -> 스칼라 컬렉션 변환
// 2.8 : cba는 String - 더 구체적인 String -> SringOps 변환 추가(reverse 메소드가 String 반환)

암시 디버깅

  • 컴파일러가 암시를 찾지 못하는 경우, 명시적으로 변환을 써보고 오류가 발생하는지 확인한다.
  • 컴파일러에 -Xprint:typer 옵션 사용하여, 타입 검사기가 추가한 모든 암시적 변환이 있는 코드를 확인한다.


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

20장

  • 추상 멤버 : 클래스/트레이트 안에서 완전한 정의를 갖고 있지 않은 멤버
  • 메소드, 필드, 타입
// 추상 멤버 예
trait Abstract {
  type T
  def transform(x: T): T
  val initial: T
  var current: T
}

추상 타입

  • 실제 이름이 너무 길거나 의미가 불명확할 때
  • 서브 클래스에서 꼭 정의해야하는 추상 타입 선언

추상 val

  • 클래스 안에서 변수에 대해 값은 알 수 없지만, 인스턴스에서 변하지 않는 경우
// 추상 val 선언
val initial: String

// 구현
val initial = "hi"
  • 추상 메소드는 val 정의로 구현할 수도 있음

추상 var

trait AbstractTime {
  var hour: Int
  var minute: Int
}

// 위와 동일한 실제 확장 모습
trait AbstractTime {
  def hour: Int
  def hour_=(x: Int)
  def minute: Int
  def minute_=(x: Int)
}

추상 val 초기화

trait RationalTrait {
  val numerArg: Int
  val denomArg: Int
}

// 추상 val 정의 구현
// 익명 클래스 인스턴스 생성
new RationalTrait {
  val numerArg = 1
  val denomArg = 2
}

필드 미리 초기화

  • 필드 정의를 중괄호에 넣어서 슈퍼클래스 생성자 호출 앞에 위치
// 익명 클래스
new {
  val numerArg = 1 * x
  val denomArg = 2 * x
} with RationalTrait

// 정의 중인 객체/클래스의 extends 키워드 다음에 정의
object twoThirds extends {
  val numerArg = 2
  val denomArg = 3
} with RationalTrait
  • 초기화시 생성 중인 객체를 언급할 수 없음

지연 계산

object Demo {
  val x = { println("initializing x"); "done" }
}

// 지연 계산
object Demo {
  lazy val x = { println("initializing x"); "done" }
}

// Demo
// Demo.x
// 미리 초기화할 필요 없는 RationalTrait
trait LazyRationalTrait {
  val numerArg: Int
  val denomArg: Int
  lazy val numer = numerArg / g
  lazy val denom = denomArg / g
  override def toString = numer +"/"+ denom
  private lazy val g = {
    require(denomArg != 0)
    gcd(numerArg, denomArg)
  }
  private def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)
}

추상 타입 예

// 잘못된 예
class Food
abstract class Animal {
  def eat(food: Food)
}

class Grass extends Food
class Cow extends Animal {
  override def eat(food: Grass) {}  // 컴파일 안됨
}
// 올바른 예
class Food
abstract class Animal {
  type SuitableFood <: Food
  def eat(food: SuitableFood)
}

class Grass extends Food
class Cow extends Animal {
  type SuitableFood = Grass
  override def eat(food: SuitableFood) {}
}

경로에 의존하는 타입

  • 외부 객체에 이름을 붙임
  • 실제 타입이 같으면 동일한 타입
class DogFood extends Food
class Dog extends Animal {
  type SuitableFood = DogFood
  override def eat(food: DogFood) {}
}

val bessy = new Cow
val lassie = new Dog
lassie eat (new bessy.SuitableFood) // error
val bootsie = new Dog
lassie eat (new bootsie.SuitableFood)   // ok

구조적 서브타이핑

  • 이름에 의한 서브타입 : 클래서 A가 클래스 B를 상속하는 경우.
  • 구조적 서브타입 : 두 타입의 멤버가 같음
// 세분화한 타입 사용 예
Animal { type SuitableFood = Grass }

class Pasture {
  var animals: List[Animal { type SuitableFood = Grass }] = Nil
  // ...
}

// 여러 클래스를 그룹으로 다루는 예
def using[T <: { def close(): Unit }, S](obj: T)(operation: T => S) = {
  val result = operation(obj)
  obj.close()
  result
}

열거형

  • scala.Enumeration 클래스
  • 각 값의 타입은 Value
object Color extends Enumeration {
  val Red = Value
  val Green = Value
  val Blue = Value
}

// 위와 동일한 코드
object Color extends Enumeration {
  val Red, Green, Blue = Value
}

// Color.Value != Direction.Value
object Direction extends Enumeration {
  val North, East, South, West = Value
}
  • 열거형 값과 이름을 연관시킬수 있음
object Direction extends Enumeration {
  val North = Value("North")
  val East = Value("East")
  val South = Value("South")
  val West = Value("West")
}

for (d <- Direction.values) print(d + " ")
// North East South West

Direction.East.id
// 1

Direction(1)
// East

예 - 통화 변환

  • 첫번째 구현
abstract class Currency {
  val amount: Long
  def designation: String
  override def toString = amount +" "+ designation
  def + (that: Currency): Currency = ...
  def * (x: Double): Currency = ...
}

abstract class Dollar extends Currency {
  def designation = "USD"
}
  • 두번째 구현
abstract class AbstractCurrency {
  type Currency <: AbstractCurrency

  val amount: Long
  def designation: String
  override def toString = amount +" "+ designation
  def + (that: Currency): Currency = ...
  def * (x: Double): Currency = ...
}

abstract class Dollar extends AbstractCurrency {
  type Currency = Dollar
  def designation = "USD"
}
  • 최종 구현
abstract class CurrencyZone {
  type Currency <: AbstractCurrency
  def make(x: Long): Currency

  abstract class AbstractCurrency {
    val amount: Long
    def designation: String

    def + (that: Currency): Currency = make(this.amount + that.amount)
    def * (that: Double): Currency = make((this.amount * x).toLong)
    def - (that: Currency): Currency = make(this.amount - that.amount)
    def / (that: Double): Currency = make((this.amount / that).toLong)
    def / (that: Currency): Currency = this.amount.toDouble / that.amount
    def from(other: CurrencyZone#AbstractCurrency): Currency =
      make(math.round(
        other.amount.toDouble * Converter.exchangeRate(other.designation)(this.designation)))
    private def decimals(n: Long): Int =
      if (n < 10) 0 else 1 + decimals(n / 10)
    override def toString =
      ((amount.toDouble / CurrencyUnit.amount.toDouble) formatted ("%."+ decimals(CurrencyUnit.amount) +"f") +" "+ designation)
  }
  val CurrencyUnit: Currency
}

object Converter {
  var exchangeRate = Map(
    "USD" -> Map("USD" -> 1.0, "EUR" -> 0.7596, "JPY" -> 1.211, "CHF" -> 1.223),
    "EUR" -> Map("USD" -> 1.316, "EUR" -> 1.0, "JPY" -> 1.594, "CHF" -> 1.623),
    "JPY" -> Map("USD" -> 0.8257, "EUR" -> 0.6272, "JPY" -> 1.0, "CHF" -> 1.018),
    "CHF" -> Map("USD" -> 0.8108, "EUR" -> 0.6160, "JPY" -> 0.982, "CHF" -> 1.0)
  )
}

object US extends CurrencyZone {
  abstract class Dollar extends AbstractCurrency {
    def designation = "USD"
  }
  type Currency = Dollar
  def make(cents: Long) = new Dollar {
    val amount = cents
  }
  val Cent = make(1)
  val Dollar = make(100)
  val CurrencyUnit = Dollar
}

object Europe extends CurrencyZone {
  abstract class Euro extends AbstractCurrency {
    def designation = "EUR"
  }
  type Currency = Euro
  def make(cents: Long) = new Euro {
    val amount = cents
  }
  val Cent = make(1)
  val Euro = make(100)
  val CurrencyUnit = Euro
}

object Japan extends CurrencyZone {
  abstract class Yen extends AbstractCurrency {
    def designation = "USD"
  }
  type Currency = Yen
  def make(cents: Long) = new Yen {
    val amount = cents
  }
  val Yen = make(1)
  val CurrencyUnit = Yen
}

// 환전
Japan.Yen from US.Dollar * 100


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

19장

타입 파라미터화(type parameterization)

  • 제너릭 사용

함수형 큐

  • head
  • tail
  • enqueue
// enqueue 연산이 n 시간 걸림
class SlowAppendQueue[T] (elems: List[T]) {
  def head = elems.head
  def tail = new SlowAppendQueue(elems.tail)
  def enqueue(x: T) = new SlowAppendQueue(elems ::: List(x))
}
// head, tail 연산이 n 시간 걸림
class SlowHeadQueue[T] (smele: List[T]) {
  def head = smele.last
  def tail = new SlowHeadQueue(smele.init)
  def enqueue(x: T) = new SlowHeadQueue(x :: smele)
}
// head, tail, enqueue를 비슷한 빈도로 호출하는 경우, 상수 복잡도
class Queue[T] {
  private val leading: List[T]
  private val trailing: List[T]
} {
  private def mirror =
    if (leading.isEmpty) new Queue(trailing.reverse, Nil)
    else this
  def head = mirror.leading.head
  def tail = {
    val q = mirror
    new Queue(q.leading.tail, q.trailing)
  }
  def enqueue(x: T) = new Queue(leading, x :: trailing)
}

정보 은닉

비공개 생성자

  • 파라미터 목록 바로 앞에 private 수식자를 붙임
class Queue[T] private (
  private val leading: List[T],
  private val trailing: List[T]
)
  • 보조 생성자 생성
def this() = this(Nil, Nil)
def this(elems: T*) = this(elems.toList, Nil)   // T*는 파라미터를 반복 (8.8절)
  • 팩토리 메소드
object Queue {
  def apply[T](xs: T*) = new Queue[T](xs.toList, Nil)
}

비공개 클래스

trait Queue[T] {
  def head: T
  def tail: Queue[T]
  def enqueue(x: T): Queue[T]
}

object Queue {
  def apply[T](xs: T*): Queue[T] =
    new QueueImpl[T](xs.toList, Nil)
  
  private class QueueImpl[T] (
    private val leading: List[T],
    private val trailing: List[T]
  ) extends Queue[T] {
    def mirror =
      if (leading.isEmpty) new QueueImpl(trailing.reverse, Nil)
      else this
    def head: T = mirror.leading.head
    def tail: QueueImpl[T] = {
      val q = mirror
      new QueueImpl(q.leading.tail, q.trailing)
    }
    def enqueue(x: T) = new QueueImpl(leading, x :: trailing)
  }
}

변성(variance) 표기

  • 타입 생성자 : 타입 파라미터를 지정하면 타입을 만들 수 있음. (ex. Queue -> Queue[Int], Queue[String])
  • 제네릭 클래스/트레이트 : 여러 구체적인 타입 정의할 수 있음. (ex. 제네릭 큐 -> 구체적 큐)
  • 제네릭 타입은 기본적으로 무공변(nonvariant)

공변 서브타입 관계

  • 파라미터 앞에 +
  • T가 S의 서브타입 -> Queue[T]가 Queue[S]의 서브타입
trait Queue[+T] { ... }

반공변 서브타입 관계

  • 파라미터 앞에 -
  • T가 S의 서브타입 -> Queue[S]가 Queue[T]의 서브타입
trait Queue[-T] { ... }

변성과 배열

  • 배열은 무공변(자바는 공변)
  • 명시적인 변환이 필요함
val a1 = Array("abc")   // Array[String]
val a2: Array[Object] = a1.asInstanceOf[Array[Object]]

변성 표기 검사

  • 변경 가능 필드(세터 메소드)
  • 제네릭 타입의 파라미터가 메소드 파라미터의 타입인 경우
  • 본문의 모든 위치를 긍정적, 부정적, 중립적으로 구분하여 검사
  • + 표기는 긍정적 위치, - 표기는 부정적 위치에만 가능하며, 변성 표기 없는 것은 어디나 가능

위치 구분

  • 선언 중인 클래스의 최고 수준의 위치 = 긍정적
  • 하위 내포 수준은 그 수준을 둘러싼 상위 수준과 같음
  • 메소드 값 파라미터 위치는 메소드 바깥의 위치의 구분을 뒤집는다.
  • 메소드 타입 파라미터 위치는 구분을 뒤집는다.
  • - 표기의 경우 현재 구분을 뒤집는다.
abstract class Cat[-T, +U] {
  def meow[W](volume: T, listener: Cat[U, T]): Cat[Cat[U,T], U]
}
// meow 의 위치는 긍정적
// meow[W]( ... ) : W와 volume, listener의 위치는 모두 부정적
// listener: Cat[U,T] : U의 위치는 긍정적, T의 위치는 부정적
// 결과 타입 Cat[Cat[U,T], U] 의 위치는 긍정적
// Cat[Cat[U,T], U] : Cat[U,T]의 위치는 부정적, U의 위치는 긍정적
// 결과 타입의 Cat[U,T] : U는 긍정적, T는 부정적
// T는 부정적인 위치에서만 사용, U는 긍정적 위치에서만 사용

하위 바운드

  • 타입 파라미터에 하위 바운드를 사용하면, 부정적인 위치의 타입을 공변적으로 만들 수 있음
class Queue[+T] (private val leading: List[T], private val trailing: List[T]) {
  def enqueue[U >: T](x: U) = new Queue[U](leading, x :: trailing)
  // U는 T의 수퍼 타입
  // ...
}

반공변성

trait OutputChannel[-T] {
  def write(x: T)
}

// OutputChannel[String]이 필요한 곳이라면 OutputChannel[AnyRef]를 바꿔놓아도 문제 없음
  • 리스코프 치환 원칙 : U타입의 값이 필요한 모든 경우를 T타입으로 대치할 수 있다면, T타입을 U타입의 서브타입으로 가정해도 안전함.
  • T가 U의 모든 연산을 지원하고, 모든 T의 연산이 그에 대응하는 U의 연산에 비해 더 적게 요구하고 더 많이 제공하는 경우 성립.
class Publication(val title: String)
class Book(title: String) extends Publication(title)
object Library {
  val books: Set[Book] =
    Set(
      new Book("Programming in Scala"),
      new Book("Walden")
    )
  def printBookList(info: Book => AnyRef) {
    for (book <- books) println(info(book)) // println은 인자에 toString을 호출해 출력
  }
}

object Customer extends Application {
  def getTitle(p: Publication): String = p.title
  Library.printBookList(getTitle)
}

객체의 비공개 데이터

  • 어떤 필드가 그 객체에서만 접근 가능하다면, 변성에 아무 문제를 일으키지 않음
class Queue[+T] private (
  private[this] var leading: List[T],
  private[this] var trailing: List[T]
) {
  private def mirror() =
    if (leading.isEmpty) {
      wihle (!trailing.isEmpty) {
        leading = trailing.head :: leading
        trailing = trailing.tail
      }
    }
  def head: T = {
    mirror()
    leading.head
  }
  def tail: Queue[T] = {
    mirror()
    new Queue(leading.tail, trailing)
  }
  def enqueue[U >: T](x: T) = new Queue[U](leading, x :: trailing)
}

상위 바운드

class Person(val firstName: String, val lastName: String) extends Ordered[Person] {
  def compare(that: Person) = {
    val lastNameComparison = lastName.compareToIgnoreCase(that.lastName)
    if (lastNameComparison != 0) lastNameComparison
    else firstName.compareToIgnoreCase(that.firstName)
  }

  override def toString = firstName +" "+ lastName
}

// T는 Ordered의 서브타입이어야 함
def orderedMergeSort[T <: Ordered[T]](xs: List[T]): List[T] = {
  def merge(xs: List[T], ys: List[T]): List[T] =
    (xs, ys) match {
      case (Nil, _) => ys
      case (_, Nil) => xs
      case (x :: xs1, y :: ys1) =>
        if (x < y) x :: merge(xs1, ys)
        else y :: merge(xs, ys1)
    }
  val n = xs.length / 2
  if (n == 0) xs
  else {
    val (ys, zs) = xs splitAt n
    merge(orderedMergeSort(ys), orderedMergesort(zs))
  }
}


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

18장

변경 가능한 상태

  • 순수 함수형은 변경 가능한 상태가 없음.
  • var 이 있더라도 캐시 등의 용도라면 순수 함수형일 수 있음.
  • var 이 없더라도 변경 가능한 상태를 가진 다른 객체에게 호출을 위임하면서 변경 가능한 상태를 가질 수 있음.

재할당 가능한 변수

  • 비공개가 아닌 모든 var 멤버에 게터/세터 메소드를 자동으로 정의함.
  • var x의 게터는 x, 세터는 x_=
  • 게터와 세터는 원래 var과 같은 가시성을 가지고, 필드는 private[this]가 붙음
class Time {
  var hour = 12
  var minute = 0
}

// 실제 자동 확장
class Time {
  private[this] hour = 12
  private[this] minute = 0

  def hour: Int = h
  def hour_= (x: Int) { h = x }

  def minute: Int = m
  def minute_= (x: Int) { m = x }
}

// 직접 정의
class Time {
  private[this] hour = 12
  private[this] minute = 0

  def hour: Int = h
  def hour_= (x: Int) {
    require(0 <= x && x < 24)
  }

  def minute: Int = m
  def minute_= (x: Int) {
    require(0 <= x && x < 60)
    m = x
  }
}
  • 연관된 필드 없이 게터/세터 정의
class Thermometer {
  var celsius: Float = _    // 초기화 값.

  def fahrenheit = celsius * 9 / 5 + 32
  def fahrenheit_= (f: Float) {
    celsius = (f - 32) * 5 / 9
  }

  override def toString = fahrenheit +"F/"+ celsius +"C"
}
  • 필드 초기화 값
    • 수 타입 : 0
    • 불리언 타입 : false
    • 레퍼런스 타입 : null
  • = _ 초기화를 생략하면 추상 변수(20장)를 선언함

예 : 이산 이벤트 시뮬레이션

package org.stairwaybook.simulation

abstract class Simulation {
  type Action = () => Unit
  case class WorkItem(time: Int, action: Action)
  
  private var curtime = 0
  def currentTime: Int = curtime
  private var agenda: List[WorkItem] = List()
  
  private def insert(ag: List[WorkItem], item: WorkItem): List[WorkItem] = {
    if (ag.isEmpty || item.time < ag.head.time) item :: ag
    else ag.head :: insert(ag.tail, item)
  }

  def afterDelay(delay: Int)(block: => Unit) {
    val item = WorkItem(currentTime + delay, () => block)
    agenda = insert(agenda, item)
  }

  private def next() {
    (agenda: @unchecked) match {
      case item :: rest =>
        agenda = rest
        curtime = item.time
        item.action()
    }
  }

  def run() {
    afterDelay(0) {
      println("*** simulation started, time = "+ currentTime +" ***")
    }
    while (!agenda.isEmpty) next()
  }
}

abstract class BasicCircuitSimulation extends Simulation {
  def InverterDelay: Int
  def AndGateDelay: Int
  def OrGateDelay: Int

  class Wire {
    private var sigVal = false
    private var actions: List[Action] = List()
    
    def getSignal = sigVal
    
    def setSignal(s: Boolean) {
      if (s != sigVal) {
        sigVal = s
        actions foreach (_ ())
      }
    }
    
    def addAction(a: Action) {
      actions = a :: actions
      a()
    }
  }

  def inverter(input: Wire, output: Wire) {
    def invertAction() {
      val inputSig = input.getSignal
      afterDelay(InverterDelay) {
        output setSignal !inputSig
      }
    }
    input addAction invertAction
  }

  def andGate(a1: Wire, a2: Wire, output: Wire) {
    def andAction() = {
      val a1Sig = a1.getSignal
      val a2Sig = a2.getSignal
      afterDelay(AndGateDelay) {
        output setSignal (a1Sig & a2Sig)
      }
    }
    a1 addAction andAction
    a2 addAction andAction
  }

  def orGate(o1: Wire, o2: Wire, output: Wire) {
    def orAction() {
      val o1Sig = o1.getSignal
      val o2Sig = o2.getSignal
      afterDelay(OrGateDelay) {
        output setSignal (o1Sig | o2Sig)
      }
    }
    o1 addAction orAction
    o2 addAction orAction
  }

  def probe(name: String, wire: Wire) {
    def probeAction() {
      println(name +" "+ currentTime +" new-value = "+ wire.getSignal)
    }
    wire addAction probeAction
  }
}

abstract class CircuitSimulation extends BasicCircuitSimulation {
  def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire) {
    val d, e = new Wire
    orGate(a, b, d)
    andGate(a, b, c)
    inverter(c, e)
    andGate(d, e, s)
  }

  def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire) {
    val s, c1, c2 = new Wire
    halfAdder(a, cin, s, c1)
    halfAdder(b, s, sum, c2)
    orGate(c1, c2, cout)
  }
}
import org.stairwaybook.simulation._

object MySimulation extends CircuitSimulation {
  def InverterDelay = 1
  def AndGateDelay = 3
  def OrGateDelay = 5
}

import MySimulation._

val input1, input2, sum, carry = new Wire
probe("sum", sum)
probe("carry", carry)
halfAdder(input1, input2, sum, carry)
input1 setSignal true
run()
input2 setSignal true
run()


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

17장

시퀀스

리스트 List

  • 앞부분에 빠르게 원소를 삭제하거나 추가할 수 있다. -> 패턴 매치 이점
  • 불변 객체
  • 임의 위치 접근 시에는 빠르지 않다.
val colors = List("red","blue","green")
colors.head     // red
colors.tail     // List("blue","green")

배열 Array

  • 임의 위치 효율적으로 접근
val fiveInts = new Array[Int](5)
val fiveToOne = Array(5, 4, 3, 2, 1)

fiveInts(0) = fiveToOne(4)

리스트 버퍼 ListBuffer

  • 가변 객체
  • 앞뒤로 원소를 추가하는 데 상수시간 소요
  • += : 원소를 리스트 뒤에 추가
  • +=: : 원소를 리스트 앞에 추가
import scala.collection.mutable.ListBuffer
val buf = new ListBuffer[Int]
buf += 1        // ListBuffer(1)
buf += 2        // ListBuffer(1,2)
3 +=: buf       // ListBuffer(3,1,2)
buf.toList      // List(3,1,2)

배열 버퍼 ArrayBuffer

  • 앞뒤로 원소를 추가/삭제 가능
  • 모든 배열 연산 사용 가능
  • 추가/삭제에 평균적으로 상수시간 걸리나 종종 선형 시간 필요
import scala.collection.mutable.ArrayBuffer
val buf = new ArrayBuffer[Int]()
buf += 12       // ArrayBuffer(12)
buf += 15       // ArrayBuffer(12,15)
buf.length      // 2
buf(0)          // 12

문자열 StringOps

  • StringStringOps로 암시적 변환하여 시퀀스처럼 문자열을 다룰 수 있다.
def hasUpperCase(s: String) = s.exists(_.isUpper)

집합 Set

  • 한 파일 안에서 가변/불변 집합을 다 사용하는 방법
import scala.collection.mutable
val mutaSet = mutable.Set(1,2,3)
  • 특정 객체는 최대 하나만 들어감. 동일한지는 ==로 비교
// 다른 단어 갯수 세기
val text = "See Spot run. Run, Spot. Run!"
val wordsArray = text.split("[ !,.]+")
val words = mutable.Set.empty[String]

for (word <- wordsArray) words += word.toLowerCase
words       // Set(spot, run, see)

집합 연산

  • val nums = Set(1,2,3) : 불변 집합 생성
  • nums + 5 : 원소를 추가
  • nums - 3 : 원소를 제거
  • nums ++ List(5,6) : 여러 원소 추가
  • nums -- List(1,2) : 여러 원소 제거
  • nums & Set(1,3,5,7) : 두 집합의 교집합
  • nums.size : 집합의 크기
  • nums.contains(3) : 집합에 원소가 있는지 확인
  • import scala.collection.mutable : 가변 컬렉션 접근
  • val words = mutable.Set.empty[String] : 빈 문자열 집합 생성
  • words += "the" : 원소 추가
  • words -= "the" : 원소 제거
  • words ++= List("do","re","mi") : 여러 원소 추가
  • `words --= List("do","re") : 여러 원소 제거
  • words.clear : 모든 원소 제거

맵 Map

def countWords(text: String) = {
  val counts = mutable.Map.empty[String, Int]
  for (rawWord <- text.split("[ ,!.]+")) {
    val word = rawWord.toLowerCase
    val oldCount =
      if (counts.contains(word))
        counts(word)
      else
        0
    counts += (word -> (oldCount + 1))
  }
  counts
}

맵 연산

  • val nums = Map("i" -> 1, "ii" -> 2) : 불변 맵 생성
  • nums + ("v" -> 6) : 원소 추가
  • nums - "ii" : 원소 제거
  • nums ++ List("iii"->3, "v"->5) : 여러 원소 추가
  • nums -- List("i", "ii") : 여러 원소 제거
  • nums.size : 맵 크기
  • nums.contains("ii") : 키 포함 여부
  • nums("ii") : 키에 해당하는 값
  • nums.keys : 모든 키의 iterable
  • nums.keySet : 모든 키의 Set
  • nums.values : 모든 값의 iterable
  • nums.isEmpty : 맵이 비었는지 검사
  • import scala.collection.mutable : 가변 컬렉션 접근
  • val words = mutable.Map.empty[String,Int] : 가변 맵 생성
  • words += ("one" -> 1) : 원소 추가
  • words -= "one" : 원소 제거
  • words ++= List("one" -> "two" -> 2, "three" -> 3) : 여러 원소 추가
  • words --= List("one", "two") : 여러 원소 제거

디폴트 집합/맵

가변 집합/맵

  • scala.collection.mutable.Set() -> scala.collection.mutable.HashSet
  • scala.collection.mutable.Map() -> scala.collection.mutable.HashMap

불변 집합/맵

  • 5개 이상 : 해시 사용하는 구현
  • 5개 이하 : 특별한 클래스
    • 0 : scala.collection.immutable.EmptySet / ... .EmptySet
    • 1 ~ 4 : scala.collection.immutable.Set1 / ... .Map1 ~ scala.collection.immutable.Set4 / / ... .Map4

정렬된 집합/맵

  • SortedSet / SortedMap 트레이트
  • TreeSet / TreeMap 클래스 : red-black tree 사용. Ordered 트레이트를 따라 순서 결정
// TreeSet
import scala.collection.immutable.TreeSet
val ts = TreeSet(9,3,1,8,0,2,7,4,6,5)   // TreeSet(0,1,2,3,4,5,6,7,8,9)
val cs = TreeSet('f','u','n')           // TreeSet(f,n,u)

// TreeMap
import scala.collection.immutable.TreeMap
val tm = TreeMap(3 -> 'x', 1 -> 'x', 4 -> 'x')  // Map(1 -> x, 3 -> x, 4 -> x)
tm += (2 -> 'x')
tm                                              // Map(1 -> x, 2 -> x, 3 -> x, 4 -> x)

가변/불변 컬렉션

  • 크키가 작은 맵/집합의 경우 불변인 경우 공간을 적게 차지함.
  • 불변 컬렉션의 지원하지 않는 a += b 연산자는 a = a + b로 표현식을 해석함.(=로 끝나는 모든 연산)
var people = Set("Nancy", "Jane")
people += "Bob"

컬렉션 초기화

  • 동반 객체 이름 뒤의 괄호 안에 모든 원소. -> apply 메소드 호출로 변환하여 처리함.
List(1,2,3)
Set('a','b','c')
  • 타입 추론이 정확하지 않을때에는 타입을 지정.
val stuff = mutable.Set[Any](42)
  • 리스트로 집합/맵 만들때는 ++ 연산자 활용
val colors = List("blue","yellow","red","green")
val treeSet = TreeSet[String]() ++ colors
  • 리스트/배열로 변환
    • iterator 호출했을때의 원소 순서
    • 모든 원소를 복사하기 때문에 느릴 수 있음
// 리스트
treeSet.toList

// 배열
treeSet.toArray
  • 가변/불변 변환 : ++ 연산자 활용
import scala.collection.mutable

val treeSet = TreeSet("blue","yellow","red","green")
val mutaSet = mutable.Set.empty ++= treeSet
val immutaSet = Set.empty ++ mutaSet

튜플

  • 원소의 타입이 서로 다를 수 있음
  • 메소드에서 여러값 반환이 일반적 용도
def longestWord(words: Array[String]) = {
  var word = words(0)
  var idx = 0
  for (i <- 1 until words.length)
    if (words(i).length > word.length) {
      word = words(i)
      idx = i
    }
  (word, idx)
}
  • 결합에 어떤 의미가 있는 경우, 결합에 메소드를 추가하기 원한다면 클래스 사용
  • _1, _2, ... 으로 원소 접근


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

16장

리스트 리터럴

  • 리스트는 변경 불가능
  • 리스트 구조는 재귀적(연결 리스트)
val num = List(1,2,3,4)
val diag3 = List(
  List(1,0,0),
  List(0,1,0),
  List(0,0,1)
)
val empty = List()

리스트 타입

  • 리스트는 균일 : 리스트에 속한 모든 원소의 타입은 동일
  • 리스트 타입은 공변적 : S가 T의 서브타입이면, List[S]도 List[T]의 서브타입
  • 빈 리스트의 타입 : List[Nothing]
// Nothing은 모든 타입의 서브타입이므로, List[Nothing] 타입은 List[String]의 서브타입
val xs: List[String] = List()

리스트 생성

  • Nil:: 사용
val nums = 1 :: (2 :: (3 :: (4 :: Nil)))
val diag3 = (1 :: (0 :: (0 :: Nil))) ::
            (0 :: (1 :: (0 :: Nil))) ::
            (0 :: (0 :: (1 :: Nil))) :: Nil
val empty = Nil
  • 콜론으로 끝나기 때문에 :: 연산자는 오른쪽 결합 법칙 적용
val num = 1 :: 2 :: 3 :: 4 :: Nil

리스트 기본 연산

  • head : 첫번째 원소 반환
  • tail : 첫번째 원소 제외한 나머지 원소로 이루어진 리스트 반환
  • isEmpty : 리스트가 비었으면 true
  • 빈 리스트에 head, tail 호출 시 NoSuchElementException 발생

리스트 패턴

  • List(...) 패턴
val fruit = List("apples", "oranges", "pears")
val List(a, b, c) = fruit
// a: String = apples
// b: String = oranges
// c: String = pears
  • ::Nil 패턴
val fruit = List("apples", "oranges", "pears")
val a :: b :: rest = fruit
// a: String = apples
// b: String = oranges
// rest: List[String] = List(pears)

List 클래스 1차 메소드(함수를 인자로 받지 않는 메소드)

두 리스트 연결 :::

  • xs ::: ys : xs의 모든 원소 뒤에 ys의 모든 원소가 있는 새로운 리스트
  • 오른쪽으로 결합 : xs ::: ys ::: zsxs ::: (ys ::: zs) 와 같음

분할 정복 원칙

def append[T](xs: List[T], ys: List[T]): List[T] =
  xs match {  // 분할 부분 : 2개로 분할(첫번째 원소, 나머지 원소들)
    case List() => ys
    case x :: xs1 => x :: append(xs1, ys)   // 정복 부분 : 나머지 원소들에 대한 연산
  }

리스트 길이 length

List(1,2,3).length // 3

리스트 양 끝 init, last

  • init : 마지막 원소를 제외한 모든 원소
  • last : 마지막 원소
  • 빈 리스트에서 호출하면 NoSuchElementException 발생
  • 전체 리스트를 순회해야해서 비효율적임

리스트 뒤집기 reverse

  • 뒤집힌 새로운 리스트를 반환
  • reverse는 자기 자신의 역 : xs.reverse.reverse == xs

접두사와 접미사 drop, take, splitAt

  • xs take n : xs 리스트의 처음부터 n번째까지 원소를 반환
  • xs drop n : 첫번째에서 n번째까지 원소를 제외한 xs 리스트의 모든 원소를 반환
  • xs splitAt n : (xs take n, xs drop n) 리스트를 두번 순회하지 않음

원소 선택 apply, indices

  • apply : 인덱스로 원소 선택
  • xs apply n == xs(n) == (xs drop n).head. 인덱스 n에 비례한 시간이 걸려 잘 사용하지 않음
  • indices : 유효한 모든 인덱스의 리스트 반환(Range())

리스트의 리스트를 한 리스트로 flatten

List(List(1,2), List(3), List(), List(4, 5)).flatten
// List(1,2,3,4,5)

두 리스트를 순서쌍으로 묶기 zip, unzip

  • xs zip ys : xs 리스트와 ys리스트의 원소들의 순서쌍 리스트를 만들고, 길이가 긴쪽의 남는 원소를 버린다.
  • xs.zipWithIndex : xs 리스트의 각 원소를 그 인덱스와 함께 순서쌍으로 묶은 리스트를 만든다.
  • xs.unzip : 튜플의 리스트를 리스트의 튜플로 나눈다

리스트 출력 toString, mkString

  • toString : 리스트에 대한 표준 문자열 표현 List( ... )
  • xs mkString (pre, sep, post)
    • pre : 제일 앞에 출력할 접두사
    • sep : 원소 사이에 출력할 분리 문자
    • post : 제일 뒤에 출력할 접미사
  • xs mkString sep == xs mkString ("", sep, "")
  • xs mkString == xs mkString ""
  • addString : 생성한 문자열을 결과로 반환하지 않고 StringBuilder 객체에 추가
val buf = new StringBuilder
abcde addString (buf, "(", ";", ")")
  • mkString과 addString은 다른 모든 컬렉션에서도 사용 가능(Traversable로부터 상속한 메소드)

리스트 변환 iterator, toArray, copyToArray

  • 리스트 <-> 배열 변환 : toArray, toList
val arr = abcde.toArray // 배열로 변환
arr.toList              // 리스트로 변환
  • copyToArray : 리스트 원소를 어떤 배열의 특정 지점부터 연속으로 복사
xs copyToArray (arr, start) // xs 의 모든 원소를 arr의 start 지점에 복사
  • iterator
val it = abcde.iterator
it.next

예제

// 병합 정렬
def msort[T](less: (T, T) => Boolean)(xs: List[T]): List[T] = {
  def merge(xs: List[T], ys: List[T]): List[T] = (xs, ys) match {
    case (Nil, _) => ys
    case (_, Nil) => xs
    case (x :: xs1, y :: ys1) =>
      if (less(x, y)) x :: merge(xs1, ys)
      else y :: merge(xs, ys1)
  }
  val n = xs.length / 2
  if (n == 0) xs
  else {
    val (ys, zs) = xs splitAt n
    merge(msort(less)(ys), msort(less)(zs))
  }
}

List 클래스의 고차 메소드

리스트 매핑

  • xs map f : xs 리스트의 모든 원소에 함수 f를 적용해서 나온 결과 값의 리스트를 반환
List(1, 2, 3) map (_ + 1) // List(2, 3, 4)
  • flatMap : 원소의 리스트를 반환하는 함수를 받아, 모든 원소에 적용해서 나온 모든 리스트를 연결한 단일 리스트를 반환한다.
words map (_.toList)      // List(List(...), List(...))
words flatMap (_.toList)  // List( ... )
  • foreach : 프로시저를 받아 모든 원소에 적용한다.
var sum = 0
List(1,2,3,4,5) foreach (sum += _)
sum // 15

리스트 걸러내기

  • xs filter p : T => Boolean 타입의 술어 함수 p를 받아 원소 x중에 p(x) == true 인 원소의 리스트를 만든다.
List(1,2,3,4,5) filter (_ % 2 == 0) // List(2, 4)
  • partition : filter와 같지만, true인 원소와 false인 원소들의 리스트를 한 쌍으로 만든다.
List(1,2,3,4,5) partition (_ % 2 == 0) // (List(2, 4),List(1,3,5))
  • find : 술어 함수를 만족하는 첫번째 원소를 반환
List(1,2,3,4,5) find (_ % 2 == 0) // Some(2)
List(1,2,3,4,5) find (_ <= 0)     // None
  • takeWhile : 술어를 만족하는 가장 긴 접두사를 반환
List(1,2,3,-4,5) span (_ > 0)  // List(1,2,3)
  • dropWhile : 술어를 만족하는 가장 긴 접두사를 제거
List(1,2,3,-4,5) span (_ > 0)  // List(-4,5)
  • span : takeWhiledropWhile결과를 순서쌍으로 반환. 한번만 순회한다.
List(1,2,3,-4,5) span (_ > 0)  // (List(1,2,3),List(-4,5))

리스트 전체에 대한 술어

  • xs forall p : 리스트의 모든 원소가 p를 만족하면 true
  • xs exists p : 리스트에서 p를 만족하는 원소가 하나라도 있으면 true

리스트 폴드

  • (z /: xs) (op) : 왼쪽으로 편향된 연산 트리
  • (xs :\ z) (op) : 오른쪽으로 편향된 연산 트리
    • z : 시작값
    • xs : 대상 리스트
    • op : 이항 연산자
def reverseLeft[T](xs: List[T]) =
  (List[T]() /: xs) { (ys, y) => y :: ys}

리스트 정렬

  • xs sortWith before : 두 원소를 비교할 수 있는 함수 before를 사용하여 xs를 정렬
    • before : x before y가 true 이면 x가 y 앞에 위치
    • 병합 정렬 수행

List 객체의 메소드

원소로 리스트 만들기

  • List.apply(...) == List(...)
List.apply(1,2,3) // List(1,2,3)

수의 범위를 리스트로 만들기

  • List.range(from, until) : until을 제외한 범위 리스트
List.range(1,5) // List(1,2,3,4)
  • List.range(from, until, increment) : increment 만큼 증가하는 리스트

균일 리스트 만들기

  • List.fill(n)(v) : v를 n번 반복한 리스트
List.fill(5)('a')     // List(a,a,a,a,a)
// 인자 갯수를 추가하면 다차원 리스트 생성
List.fill(2,3)('b')   // List(List(b,b,b),List(b,b,b))

함수 도표화

  • List.tabulate : 함수로 계산한 원소의 리스트 생성
List.tabulate(5)(n => n * n)  // List(0, 1, 4, 9, 16)

리스트 연결

  • List.concat
List.concat(List('a','b'), List('c'))   // List(a,b,c)

여러 리스트 처리

  • 튜플의 zipped 메소드는 여러 리스트에서 동작하는 공통 연산을 일반화
(List(10, 20), List(3, 4, 5)).zipped.map(_ * _)       // List(30, 80)

(List("abc","de"), List(3,2)).zipped.forall(_.length == _)    // true

(List("abc","de"), List(3,2)).zipped.exists(_.length != _)    // false

스칼라의 타입 추론 알고리즘

  • 지역적 흐름 기반 타입 추론 방식
// abcde 의 타입으로 부터 메소드에 적용할 인자의 타입 추론
abcde sortWith (_ > _)    // (Char, Char) => Boolean
msort(_ > _)(abcde)         // 비교 함수를 인자로 넘길때 타입을 알 수 없음
msort[Char](_ > _)(abcde)   // 타입인자를 전달하여 추론 가능 (Char, Char) => Boolean
msortSwapped(abcde)(_ > _)  // 이미 알려진 첫번째 파라미터 목록 값의 타입을 참고하여 추론 가능
(xss :\ List[T]()) (_ ::: _)  // 연산 타입 추론 : (List[T], List[T]) => List[T]
(xss :\ List()) (_ ::: _)     // 연산 타입 추론 : (List[T], List[Nothing]) => List[Nothing]


+ Recent posts