https://www.superdesign.dev/

 

Superdesign.dev - Open Source Design Agent

Generate UI, mockups, and wireframes inside your IDE with AI-powered design assistance. Works with Cursor, Windsurf, VS Code, and more.

www.superdesign.dev

오픈소스 AI 디자인 에이전트. VS Code 등에 확장으로 설치되어 사용하는 도구로 Anthropic API Key가 필요하다.

데모를 보면 한번 써보고 싶긴한데, AI는 무료가 아니라서 그냥 써볼 수는 없다.

 

'Developing' 카테고리의 다른 글

만들면서 배우는 클린 아키텍처  (0) 2022.05.23
유연한 소프트웨어를 만드는 설계 원칙  (0) 2022.05.13
Brackets 개발 중단  (0) 2021.03.09

제목에 "만들면서 배운다"는 말이 들어가 있어서 실전에 바로 사용할 수 있는 것들을 다루지 않을까 기대했는데, 그런 책은 아니었다. 다만, 자바 코드로 책의 내용을 설명하고 있기 때문에 좀 더 이해하기 좋은 면이 있었다.

책은 "육각형 아키텍처"를 다루고 있다. 육각형 아키텍처의 구현 방법에 대해서 코드와 함께 설명해서 좀 더 명확하게 이해되는 느낌이었다. 얇은 책의 두께도 내용에 대한 부담을 줄여주는 듯했다.

'Developing' 카테고리의 다른 글

SuperDesign  (0) 2025.08.28
유연한 소프트웨어를 만드는 설계 원칙  (0) 2022.05.13
Brackets 개발 중단  (0) 2021.03.09

책 제목에 있는 "설계"라는 단어에 혹 해서 읽어보았는데, 서문에도 있듯이 "인공지능 응용의 핵심부에 해당하는 프로그램을 작성하는데 유용한 기법과 기술(수학적 기호 조작과 규칙 기반 시스템 등)을 학부 고학년과 대학원생에게 가르치는" 강의 내용을 기반으로 하고 있다. 또한, 한 학기 강좌에서 다를 수 있는 것보다 훨씬 많은 내용이 들어있다고 한 것을 보면... 술술 읽기는 어려운 책임에는 분명하다.

주로 수학적인 예제를 스킴(Scheme) 언어를 이용하여 코드로 설명하고 있고, 읽고 빠르게 써먹을 수 있는 실무적인 책은 아니었다. DSL, 확장성 있는 프로시저, 패턴 매칭, 평가(eval), 계층, 전파 등을 다루고 있고 모두 시간을 충분히 들여서 봐야 할 내용들이었다. 언젠가 연습문제도 살펴보면서 느긋하게 다시 한번 읽어볼 만한 책이었다.

'Developing' 카테고리의 다른 글

만들면서 배우는 클린 아키텍처  (0) 2022.05.23
Brackets 개발 중단  (0) 2021.03.09
어려운 스칼라의 underscore  (0) 2020.01.08

blog.brackets.io/brackets-eol-notice/

adobe에서 개발 중이었던 오픈소스 에디터인 Brackets의 개발 중단한다는 소식이다. 한때 잘 사용했던 괜찮은 에디터라서 그런지 (지금은 쓰지도 않지만) 개발 중단 소식이 안타깝다. 오픈소스이니 누군가 fork해서 유지할 수도 있겠지만, MS에서 개발하는 Visual Studio Code나 Github에서 개발하고 있는 Atom을 생각하면, adobe도 손을 뗀 상황에서 대체할만한 경쟁자로 계속 발전하기는 어렵지 않을까하는 생각이 든다.

2021년 3월 1일부터 개발을 중단하고, 9월 1일 부터는 지원도 완전히 중단한다고 한다.

'Developing' 카테고리의 다른 글

유연한 소프트웨어를 만드는 설계 원칙  (0) 2022.05.13
어려운 스칼라의 underscore  (0) 2020.01.08
Spring Social 개발 중단 소식  (0) 2018.07.07

스칼라 스터디 중, 다음과 같은 코드가 왜 오류가 발생하는지 의문이 생겼다. (코드는 재구성함)

val t = Seq(1.0,2.0,3.0)
t.map(math.pow(_ + 1,2))

// error: missing parameter type for expanded function ((<x$1: error>) => x$1.$plus(1))

타입 추론이 잘 안되는가 싶어서, 아래와 같이 해도 동일한 오류가 발생한다.

t.map((_:Double) => math.pow(_ + 1,2))

// error: missing parameter type for expanded function ((<x$2: error>) => x$2.$plus(1))

아래와 같이 하면 문제가 없다.

t.map(_+1).map(math.pow(_,2))

혹은, 아래와 같이 하면 문제가 없다.

t.map(x => math.pow(x + 1,2))

이런 현상이 발생하는 이유를 생각해보았다.

  1. f(_)a => f(a) 와 동일하다.
  2. _ + 1_.$plus(1) 과 동일하다.
  3. _.$plus(1)a => a.$plus(1) 과 동일하다.
  4. math.pow(_ + 1,2)math.pow(a => a.$plus(1),2) 와 동일??

위의 흐름이라면, Double이든 Int 이든 값이 들어가야할 자리에 함수가 들어가면서 문제가 발생한 것으로 보이는데, 맞는지는 잘 모르겠다.

'Developing' 카테고리의 다른 글

Brackets 개발 중단  (0) 2021.03.09
Spring Social 개발 중단 소식  (0) 2018.07.07
Programming in Scala 스터디 정리 - 22장. 리스트 구현  (0) 2017.10.14

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을 직접 만들어 사용하는 것이다.

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


+ Recent posts