Skip to content

Korean version of the tour #343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 7, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions ko/tutorials/tour/abstract-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
layout: tutorial
title: 추상 타입

disqus: true

tutorial: scala-tour
num: 2
outof: 35
language: ko
---

스칼라에선 값(생성자 파라미터)과 타입(클래스가 [제네릭](generic-classes.html)일 경우)으로 클래스가 매개변수화된다. 규칙성을 지키기 위해, 값이 객체 멤버가 될 수 있을 뿐만 아니라 값의 타입 역시 객체의 멤버가 된다. 또한 이런 두 형태의 멤버 모두 다 콘크리트하거나 추상화될 수 있다.

이 예제에선 [클래스](traits.html) `Buffer`의 멤버로써 완전히 확정되지 않은 값과 추상 타입을 정의하고 있다.

trait Buffer {
type T
val element: T
}

*추상 타입*은 본성이 완전히 식별되지 않은 타입이다. 위의 예제에선 클래스 `Buffer`의 각 객체가 T라는 타입 멤버를 갖고 있다는 점만 알 수 있으며, 클래스 `Buffer`의 정의는 멤버 타입 `T`에 해당하는 콘크리트 타입이 무엇이지 밝히고 있지 않다. 값 정의와 같이 타입 정의도 서브클래스에서 재정의 할 수 있다. 이는 타입 경계(추상 타입의 콘크리트 인스턴스화가 가능한 범위를 나타내는)를 설정함으로써 추상 타입에 관한 정보를 나타낼 수 있도록 해준다

다음 프로그램에선 `T` 타입이 새로운 추상 타입 `U`로 표현된 `Seq[U]`의 서브타입이어야 함을 나타내서, 버퍼에 시퀀스 만을 저장하는 클래스 `SeqBuffer`를 만들었다.

abstract class SeqBuffer extends Buffer {
type U
type T <: Seq[U]
def length = element.length
}

추상 타입 멤버를 포함한 트레잇이나 [클래스](classes.html)는 종종 익명 클래스 인스턴스화와 함께 사용된다. 이를 알아보기 위해 정수의 리스트를 참조하는 시퀀스 버퍼를 다루는 프로그램을 살펴보자.

abstract class IntSeqBuffer extends SeqBuffer {
type U = Int
}

object AbstractTypeTest1 extends App {
def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
new IntSeqBuffer {
type T = List[U]
val element = List(elem1, elem2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
}

메소드 `newIntSeqBuf`의 반환 타입은 트레잇 `Buffer`의 특수화를 따르며, 타입 `U`가 `Int`와 같아진다. 메소드 `newIntSeqBuf` 내부의 익명 클래스 인스턴스화에서도 비슷한 타입 별칭이 있다. 여기선 `T` 타입이 `List[Int]`를 가리키는 `IntSeqBuf`의 새로운 인스턴스를 생성한다.

추상 타입 멤버를 클래스의 타입 파라미터로 하거나 클래스의 타입 파라미터로 추상 타입 멤버로를사용할 수 있음에 주목하자. 다음은 타입 파라미터만을 사용한, 앞서 살펴본 코드의 새로운 버전이다.

abstract class Buffer[+T] {
val element: T
}
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
def length = element.length
}
object AbstractTypeTest2 extends App {
def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
new SeqBuffer[Int, List[Int]] {
val element = List(e1, e2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
}

여기선 [가변성 어노테이션](variances.html)을 사용해야만 한다는 점에 유의하자. 이를 사용하지 않으면 메소드 `newIntSeqBuf`에서 반환되는 객체의 콘크리트 시퀀스 구현 타입을 감출 수 없게 된다. 뿐만 아니라 추상 타입을 타입 파라미터로 대체할 수 없는 경우도 있다.

윤창석, 이한욱 옮김
128 changes: 128 additions & 0 deletions ko/tutorials/tour/annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
layout: tutorial
title: 어노테이션

disqus: true

tutorial: scala-tour
num: 3
language: ko
---

어노테이션은 메타 정보와 정의 내용을 연결해준다.

간단한 어노테이션 절은 `@C`나 `@C(a1, .., an)`와 같은 형태다. 여기서 `C`는 `C` 클래스의 생성자이며, `scala.Annotation`에 맞는 클래스여야만 한다. `a1, .., an`으로 주어지는 모든 생성자의 인수는 반드시 상수 표현식이여야 한다(예, 숫자 리터럴, 문자열, 클래스 리터럴, 자바 열거형, 그리고 이들의 1차원 배열).

어노테이션 절은 첫 번째 정의나, 그 다음에 이어지는 선언에 적용된다. 정의와 선언에는 하나 이상의 어노테이션 절이 붙을 수 있다. 이런 절이 표현되는 순서는 영향을 미치지 않는다.

어노테이션 절의 의미는 _구현 종속적_이다. 자바 플랫폼에선 다음의 스칼라 어노테이션이 표준에 해당하는 의미를 갖고 있다.

| Scala | Java |
| ------ | ------ |
| [`scala.SerialVersionUID`](http://www.scala-lang.org/api/2.9.1/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (필드) |
| [`scala.cloneable`](http://www.scala-lang.org/api/2.9.1/scala/cloneable.html) | [`java.lang.Cloneable`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Cloneable.html) |
| [`scala.deprecated`](http://www.scala-lang.org/api/2.9.1/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) |
| [`scala.inline`](http://www.scala-lang.org/api/2.9.1/scala/inline.html) (2.6.0 부터) | 해당 없음 |
| [`scala.native`](http://www.scala-lang.org/api/2.9.1/scala/native.html) (2.6.0 부터) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) |
| [`scala.remote`](http://www.scala-lang.org/api/2.9.1/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) |
| [`scala.serializable`](http://www.scala-lang.org/api/2.9.1/index.html#scala.annotation.serializable) | [`java.io.Serializable`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html) |
| [`scala.throws`](http://www.scala-lang.org/api/2.9.1/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) |
| [`scala.transient`](http://www.scala-lang.org/api/2.9.1/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) |
| [`scala.unchecked`](http://www.scala-lang.org/api/2.9.1/scala/unchecked.html) (2.4.0 부터) | 해당 없음 |
| [`scala.volatile`](http://www.scala-lang.org/api/2.9.1/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (키워드) |
| [`scala.reflect.BeanProperty`](http://www.scala-lang.org/api/2.9.1/scala/reflect/BeanProperty.html) | [`디자인 패턴`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) |

다음 예제에선 자바의 메인 프로그램에서 던지는 예외를 잡기 위해, `read` 메소드에 `throws` 어노테이션을 추가했다.

> 자바 컴파일러는 메소드나 생성자를 실행할 때 어떤 확인 예외가 발생할 수 있는지 분석해, 프로그램이 [확인이 필요한 예외](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html)를 처리할 핸들러를 포함하고 있는지 검사한다. 메소드나 생성자의 **throws** 절에선 발생할 가능성이 있는 확인 예외마다, 해당 예외의 클래스나 해당 예외 클래스의 상위 클래스를 _반드시 명시해야 한다.
> 스칼라는 확인 예외가 없기 때문에 스칼라 메소드는 스칼라 메소드가 던지는 예외를 자바 코드가 잡을 수 있도록 _반드시_ 하나 이상의 `throws` 어노테이션을 붙여야 한다.

package examples
import java.io._
class Reader(fname: String) {
private val in = new BufferedReader(new FileReader(fname))
@throws(classOf[IOException])
def read() = in.read()
}

다음의 자바 프로그램은 `main` 메소드의 첫 번째 인수로 전달된 이름의 파일을 열어 내용을 출력한다.

package test;
import examples.Reader; // Scala class !!
public class AnnotaTest {
public static void main(String[] args) {
try {
Reader in = new Reader(args[0]);
int c;
while ((c = in.read()) != -1) {
System.out.print((char) c);
}
} catch (java.io.IOException e) {
System.out.println(e.getMessage());
}
}
}

Reader 클래스의 `throws` 어노테이션을 주석으로 처리하면 자바 메인 프로그램을 컴파일 할 때 다음과 같은 오류 메시지가 나타난다.

Main.java:11: exception java.io.IOException is never thrown in body of
corresponding try statement
} catch (java.io.IOException e) {
^
1 error

### 자바 어노테이션 ###

**주의:** 자바 어노테이션과 함께 `-target:jvm-1.5` 옵션을 사용해야 한다.

자바 1.5에선 [어노테이션](http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html)이란 형태로 사용자 지정 메타데이터가 추가됐다. 어노테이션의 핵심 기능은 키와 값의 쌍을 지정해 자신의 항목을 초기화하는 데 기반하고 있다. 예를 들어 클래스의 출처를 추적하고 싶다면 다음과 같이 정의할 수 있다.

@interface Source {
public String URL();
public String mail();
}

그리고 이를 다음과 같이 적용한다.

@Source(URL = "http://coders.com/",
mail = "support@coders.com")
public class MyClass extends HisClass ...

스칼라에선 어노테이션을 적용하는 방식은 생성자 호출과 비슷한 모습을 갖고 있으며 자바 어노테이션을 인스턴스화 하기 위해선 이름을 지정한 인수를 사용해야 한다.

@Source(URL = "http://coders.com/",
mail = "support@coders.com")
class MyScalaClass ...

어노테이션에 단 하나의 항목(기본 값이 없는)만 있다면 이 구문은 상당히 장황하게 느껴지기 때문에, 자바에선 그 이름이 `value`로 지정됐다면 편의를 위해 생성자와 유사한 구문을 사용할 수도 있다.

@interface SourceURL {
public String value();
public String mail() default "";
}

그리고 이를 다음과 같이 적용한다.

@SourceURL("http://coders.com/")
public class MyClass extends HisClass ...

이 경우엔 스칼라도 같은 기능을 제공한다.

@SourceURL("http://coders.com/")
class MyScalaClass ...

`mail` 항목은 기본 값과 함께 설정됐기 때문에 이 항목에 반드시 값을 명시적으로 할당할 필요는 없다. 하지만 만약 해야만 한다면, 자바의 두 스타일을 함께 섞어서 사용할 순 없다.

@SourceURL(value = "http://coders.com/",
mail = "support@coders.com")
public class MyClass extends HisClass ...

스칼라에선 이를 사용하는 더 유연한 방법을 제공한다.

@SourceURL("http://coders.com/",
mail = "support@coders.com")
class MyScalaClass ...

이렇게 확장된 구문은 .NET의 어노테이션과 같은 방식이며, 어노테이션의 기능을 최대한 활용할 수 있도록 해준다.

윤창석, 이한욱 옮김
42 changes: 42 additions & 0 deletions ko/tutorials/tour/anonymous-function-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
layout: tutorial
title: 익명 함수 구문

disqus: true

tutorial: scala-tour
num: 14
language: ko
---

스칼라를 사용하면 비교적 간결한 구문을 통해 익명 함수를 정의할 수 있다. 다음 표현식은 정수의 지정 함수를 만들어준다.

(x: Int) => x + 1

이는 다음의 익명 클래스 정의를 축약한 표현이다.

new Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}

마찬가지로 여러 파라미터의 함수를 정의하거나:

(x: Int, y: Int) => "(" + x + ", " + y + ")"

파라미터가 없는 함수를 정의할 수도 있다:

() => { System.getProperty("user.dir") }

매우 간결하게 함수 타입을 작성하는 방법도 있다. 다음은 위에서 정의한 세 함수의 타입이다.

Int => Int
(Int, Int) => String
() => String

이 구문은 다음 타입을 축약한 표현이다.

Function1[Int, Int]
Function2[Int, Int, String]
Function0[String]

윤창석, 이한욱 옮김
66 changes: 66 additions & 0 deletions ko/tutorials/tour/automatic-closures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
layout: tutorial
title: 타입 의존 클로저의 자동 구성

disqus: true

tutorial: scala-tour
num: 16
language: ko
---

스칼라에선 파라미터가 없는 함수의 이름을 메소드의 파라미터로 사용할 수 있다. 이런 메소드가 호출되면 파라미터가 없는 함수의 이름에 해당하는 실제 파라미터를 찾지 않고, 대신 해당 파라미터의 계산을 캡슐화한 무항 함수를 전달하게 된다(소위 말하는 *이름에 의한 호출* 연산).

다음 코드는 이 방식을 사용하는 방법을 보여준다.

object TargetTest1 extends App {
def whileLoop(cond: => Boolean)(body: => Unit): Unit =
if (cond) {
body
whileLoop(cond)(body)
}
var i = 10
whileLoop (i > 0) {
println(i)
i -= 1
}
}

`whileLoop` 함수는 `cond`와 `body`라는 두 파라미터를 받는다. 이 함수가 적용될 때 실제 파라미터는 계산되지 않는다. 대신 `whileLoop`의 내부에서 이 정형 파라미터를 사용할 때마다 암시적으로 생성된 무항 함수로 처리한다. 따라서 `whileLoop` 메소드는 재귀 구현의 방식에 맞춰 자바와 같은 while 반복문을 구현한다.

[중위/후위 연산자](operators.html)와 이 기법을 함께 사용해 좀 더 복잡한 명령문(보기 좋게 작성된)을 생성할 수 있다.

다음은 반복문을 제거한 명령문 구현이다.

object TargetTest2 extends App {
def loop(body: => Unit): LoopUnlessCond =
new LoopUnlessCond(body)
protected class LoopUnlessCond(body: => Unit) {
def unless(cond: => Boolean) {
body
if (!cond) unless(cond)
}
}
var i = 10
loop {
println("i = " + i)
i -= 1
} unless (i == 0)
}

`loop` 함수는 단순히 반복문의 내용을 받아서 `LoopUnlessCond` 클래스의 인스턴스(반복문 내용에 해당하는 객체를 캡슐화한)를 반환한다. 해당 내용이 아직 계산되지 않았음을 유념하자. `LoopUnlessCond` 클래스는 *중위 연산자*로 사용할 수 있는 `unless`라는 메소드를 포함하고 있다. 이런 접근을 통해 상당히 자연스럽게 표현된 새로운 반복문을 완성하게 된다: `loop { < stats > } unless ( < cond > )`.

다음은 `TargetTest2`를 실행한 출력 결과다.

i = 10
i = 9
i = 8
i = 7
i = 6
i = 5
i = 4
i = 3
i = 2
i = 1

윤창석, 이한욱 옮김
77 changes: 77 additions & 0 deletions ko/tutorials/tour/case-classes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
layout: tutorial
title: 케이스 클래스

disqus: true

tutorial: scala-tour
num: 5
language: ko
---

스칼라는 _케이스 클래스_ 개념을 지원한다. 케이스 클래스는 생성자 파라미터를 노출하고 [패턴 매칭](pattern-matching.html)을 통해 재귀적 디컴포지션 메커니즘을 제공하는 일반 클래스이다.

아래 예제는 추상 슈퍼클래스 `Term`과 세 개의 콘크리트 클래스 `Var`, `Fun`, `App`으로 이루어진 클래스 계층구조를 나타낸다.

abstract class Term
case class Var(name: String) extends Term
case class Fun(arg: String, body: Term) extends Term
case class App(f: Term, v: Term) extends Term

이 클래스 계층구조는 [타입 없는 람다 칼큘러스](http://www.ezresult.com/article/Lambda_calculus)의 표현식으로 사용될 수 있다. 케이스 클래스의 인스턴스 생성을 편리하게 하기 위해서 스칼라에서는 `new` 프리미티브를 쓰지 않아도 된다. 단지 클래스명을 함수로 쓸 수 있다.

예를 들면 다음과 같다 :

Fun("x", Fun("y", App(Var("x"), Var("y"))))

케이스 클래스의 생성자 파라미터는 퍼블릭 값으로 취급되어 직접 접근할 수 있다.

val x = Var("x")
println(x.name)

모든 케이스 클래스에 대해 스칼라 컴파일러는 구조적 동일성을 구현하는 `equals` 메서드와 `toString` 메서드를 생성한다. 예를 들면,

val x1 = Var("x")
val x2 = Var("x")
val y1 = Var("y")
println("" + x1 + " == " + x2 + " => " + (x1 == x2))
println("" + x1 + " == " + y1 + " => " + (x1 == y1))

와 같은 코드는 다음과 같이 출력된다.

Var(x) == Var(x) => true
Var(x) == Var(y) => false

케이스 클래스의 정의는 패턴매칭이 데이터 구조를 쪼개는 데에 사용될 때에만 유효하다. 아래와 같은 객체는 위의 람다 칼큘러스 표현을 위한 간단한 프린터를 정의한다.

object TermTest extends scala.App {
def printTerm(term: Term) {
term match {
case Var(n) =>
print(n)
case Fun(x, b) =>
print("^" + x + ".")
printTerm(b)
case App(f, v) =>
print("(")
printTerm(f)
print(" ")
printTerm(v)
print(")")
}
}
def isIdentityFun(term: Term): Boolean = term match {
case Fun(x, Var(y)) if x == y => true
case _ => false
}
val id = Fun("x", Var("x"))
val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
printTerm(t)
println
println(isIdentityFun(id))
println(isIdentityFun(t))
}

이 예제에서 함수 `printTerm`은 `match` 키워드로 시작되고 `case Patterm => Body` 구문으로 구성되는 패턴 매칭 구문으로 표현되어 있다. 이 프로그램은 또한 주어진 표현식이 단순한 항등함수인지 확인하는 함수 `isIdentityFun`을 정의한다. 이 예제는 딥 패턴과 가드를 사용한다. 주어진 값으로 패턴 매칭을 한 뒤에 (`if` 키워드 이후에 정의된) 가드가 계산된다. 만약 `true`를 리턴하면, 매칭이 성공한 것이고 그렇지 않은 경우 매칭이 실패하고 다음 패턴을 시도한다.

윤창석, 이한욱 옮김
Loading