A Tour of Go

슬라이드 로딩 중...
    Welcome

    Hello, 세상아

    Go 프로그래밍 언어 둘러보기에 오신 것을 환영합니다.

    이 둘러보기는 세 개의 섹션으로 나뉘어져 있습니다. 각 섹션의 끝에는 여러분의 완벽한 이해를 돕기 위한 연습문제들이 있습니다.

    이 둘러보기는 인터랙티브한 방법을 사용합니다. 원격 서버여러분의 컴퓨터에서 이 프로그램을 컴파일하고 실행해보기 위해 지금 바로 RUN 버튼을 클릭해보세요. (또는 Shift-Enter를 눌러보세요) 결과가 소스 코드 아래에 표시됩니다.

    이 둘러보기에 있는 예제 프로그램들은 Go의 다양한 측면을 보여줍니다. 여기에 있는 프로그램들은 여러분들이 Go를 실험해보기 위한 시작점이 될 것입니다.

    이 프로그램을 수정해서 다시 실행해보세요.

    다음으로 이동할 준비가 되었으면, NEXT 버튼을 클릭하거나 PageDown 키를 누르세요.

    package main import "fmt" func main() { fmt.Println("Hello, 세상아") }

    Go 오프라인

    이 둘러보기는 또한 여러분이 인터넷 접속없이 스탠드얼론(stand-alone) 프로그램으로도 사용할 수 있도록 되어 있습니다.

    스탠드얼론 둘러보기는 여러분의 컴퓨터에서 샘플 코드를 빌드하고 실행하므로 좀 더 빠릅니다. 또, 온라인 버전에서는 이용할 수 없는 추가적인 연습문제들을 포함하고 있습니다.

    둘러보기를 로컬에서 실행해보려면 먼저 Go를 설치 (마지막 stable 릴리즈는 release.r60.1)하고, gotour를 설치하기 위해 goinstall을 사용합니다:

        goinstall go-tour.googlecode.com/hg/gotour

    그리고 그 결과로 나온 gotour 파일을 실행합니다. (역주: 위 goinstall을 실행하고 나면 $GOROOT/bin/gotour 실행파일이 생김)

    위처럼 하지 않고 계속 진행하려면, NEXT 버튼을 클릭하거나 PageDown 키를 누르세요.

    (INDEX 버튼을 클릭해서 언제든 다시 이 gotour 설치 순서를 보러 되돌아올 수 있습니다.)

    Introduction

    Packages

    모든 Go 프로그램은 패키지들로 구성되어 있습니다.

    프로그램들은 main 패키지 안에서 실행을 시작합니다.

    이 프로그램은 "fmt""math" 패키지들을 임포트해서 사용합니다.

    관례적으로, 패키지 이름은 임포트 경로의 마지막 요소와 같습니다.
    (역주: 패키지 경로는 $GOROOT/pkg/{$GOOS_$GOARCH} 가 됩니다. 즉, linux에 386 머신이라면 $GOROOT/pkg/linux_386/ 아래에 패키지 파일들이 있습니다.)

    package main import ( "fmt" "math" ) func main() { fmt.Println("Happy", math.Pi, "Day") }

    Imports

    이 코드에서는 임포트할 패키지들을 괄호안에 그룹화했습니다. 이런 것을 "인자화된"(factored) 임포트 문이라고 합니다. 여러분은 물론 각 임포트 문장을 따로 써도 됩니다. 아래처럼요:

    	import "fmt"
    	import "math"
            

    하지만 어수선함을 제거하기 위해서 임포트할 패키지를 묶어서 사용하는 것이 일반적입니다.

    package main import ( "fmt" "math" ) func main() { fmt.Printf("Now you have %g problems.", math.Nextafter(2, 3)) }

    Exported names

    패키지를 임포트한 후에, 그 패키지가 외부로 드러낸 이름들을 참조할 수 있습니다.

    Go에서는 변수나 함수의 첫 글자가 대문자로 시작하면 그 이름이 외부에서 참조할 수 있는 이름이 됩니다.

    Pi는 외부로 드러난 이름이지만, pi는 그렇지 않습니다.

    이 코드를 실행해보세요. 그리고 math.pi를 math.Pi로 고치고 다시 실행해보세요.

    package main import ( "fmt" "math" ) func main() { fmt.Println(math.pi) }

    Functions

    함수는 0개 또는 그 이상의 인자들을 받을 수 있습니다.

    이 예제에서는, add 함수는 두 개의 int형 파라미터를 받습니다.

    타입이 변수명 다음에 온다는 것에 주의하세요.

    (왜 타입들을 이렇게 선언하는지 더 자세한 내용을 보려면, 이 블로그 포스트를 참고하세요.)

    package main import "fmt" func add(x int, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }

    Functions

    두 개 이상의 함수 파라미터가 같은 타입을 가지고 있을 때, 마지막 타입은 놔두고 나머지 타입들은 생략할 수 있습니다.

    이 예제에서, 아래처럼 되어 있는 것을

    x int, y int

    아래처럼 줄일 수 있습니다.

    x, y int
    package main import "fmt" func add(x, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }

    Functions

    한 함수는 여러 개의 결과를 리턴할 수 있습니다.

    swap 함수는 두 개의 string을 리턴합니다.

    package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hello", "world") fmt.Println(a, b) }

    Functions

    함수는 파라미터를 가질 수 있습니다; Go에서는 함수가 리턴할 결과에 이름을 미리 지어줄 수가 있고, 그 걸 변수처럼 사용할 수 있습니다; 이런 것을 리절트 파라미터(result parameters)라고 부릅니다.

    만일 이 리절트 파라미터가 이름지어져 있다면, 인자없는 return 문은 그 결과의 현재 값들을 리턴합니다.

    역주: x, y라는 이름을 가진 int 타입 두 개를 리턴할 것이라고 하면 그 함수내에서 x, y를 변수처럼 사용하고 리턴할 때는 그냥 return만 해주면 x, y가 리턴됩니다. 물론 그 함수의 결과값을 받을 때는 꼭 x, y로 받지 않아도 됩니다.

    package main import "fmt" func split(sum int) (x, y int) { x = sum * 4/9 y = sum - x return } func main() { fmt.Println(split(17)) }

    Variables

    var 문은 변수들의 리스트를 선언합니다. 함수 인자 리스트에서처럼 타입은 마지막에 적습니다.

    package main import "fmt" var x, y, z int var c, python, java bool func main() { fmt.Println(x, y, z, c, python, java) }

    Variables

    var 선언은 변수 당 하나의 값을 초기값으로 줄 수 있습니다.

    만일 초기값이 지정되어 있으면 var의 변수 타입은 생략이 가능합니다; 이 때, 그 변수는 초기값의 타입을 (추론에 의해) 가지게 됩니다.

    package main import "fmt" var x, y, z int = 1, 2, 3 var c, python, java = true, false, "no!" func main() { fmt.Println(x, y, z, c, python, java) }

    Variables

    함수 내에서, var 선언 대신에 축약된 할당문인 :=를 사용할 수 있습니다.

    (함수 밖에서, 모든 구조는 키워드로 시작되고 := 구조는 사용할 수 없습니다.)

    package main import "fmt" func main() { var x, y, z int = 1, 2, 3 c, python, java := true, false, "no!" fmt.Println(x, y, z, c, python, java) }

    Constants

    상수는 변수처럼 선언되지만, const 키워드를 사용해서 선언합니다.

    상수는 string, boolean, 숫자 값(numeric values)만 될 수 있습니다.

    package main import "fmt" const Pi = 3.14 func main() { const World = "世界" fmt.Println("Hello", World) fmt.Println("Happy", Pi, "Day") const Truth = true fmt.Println("Go rules?", Truth) }

    Numeric Constants

    숫자 상수는 고정밀도(high-precision)의 입니다.

    타입이 없는 상수(untyped constant)는 문맥에 의해 필요한 타입을 가지게 됩니다.

    needInt(Big)도 출력해보세요.

    package main import "fmt" const ( Big = 1<<100 Small = Big>>99 ) func needInt(x int) int { return x*10 + 1 } func needFloat(x float64) float64 { return x*0.1 } func main() { fmt.Println(needInt(Small)) fmt.Println(needFloat(Small)) fmt.Println(needFloat(Big)) }

    For

    Go는 단지 하나의 루프문을 가지고 있는데, 바로 for 루프입니다.

    기본적인 for 루프는 C나 Java와 같지만, 괄호 ( )가 없고(이건 옵션도 아닙니다) 중괄호 { }는 필수입니다.

    package main import "fmt" func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }

    For

    C나 Java에서처럼, 조건의 앞과 뒤를 비워둘 수 있습니다.

    package main import "fmt" func main() { sum := 1 for ; sum < 1000; { sum += sum } fmt.Println(sum) }

    For

    세미콜론을 뺄 수도 있습니다: 이 경우 C의 while처럼 Go에서 for를 쓸 수 있습니다.

    package main import "fmt" func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }

    For

    만일 루프 조건을 생략한다면, 무한루프가 됩니다.

    package main func main() { for ; ; { } }

    For

    세미콜론들을 완전히 생략하면, 역시 무한루프 표현이 됩니다.

    package main func main() { for { } }

    If

    if 문은 C나 Java와 같지만, 괄호 ( )가 없고(이건 옵션도 아닙니다) 중괄호 { }는 필수입니다.

    (어디서 많이 들어본 소리 같죠?)

    package main import ( "fmt" "math" ) func sqrt(x float64) string { if x < 0 { return sqrt(-x) + "i" } return fmt.Sprint(math.Sqrt(x)) } func main() { fmt.Println(sqrt(2), sqrt(-4)) }

    If

    for에서처럼, if 문은 조건 전에 실행할 짧은 문장과 함께 시작할 수 있습니다.

    이 문장에서 선언된 변수는 단지 if의 안에서만 사용할 수 있는 범위를 가지게 됩니다.

    (마지막 return 문 전에 v를 한 번 사용해보세요.)

    package main import ( "fmt" "math" ) func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) }

    If

    if의 짧은 문장 안에서 선언된 변수들은 또한 else 블록 안에서도 사용 가능합니다.

    package main import ( "fmt" "math" ) func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } else { fmt.Printf("%g >= %g\n", v, lim) } // can't use v here, though return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) }

    Basic types

    Go의 기본 타입들입니다.

    bool
    
    string
    
    int  int8  int16  int32  int64
    uint uint8 uint16 uint32 uint64 uintptr
    
    float32 float64
    
    complex64 complex128
    	
    package main import ( "cmath" "fmt" ) var ( ToBe bool = false MaxInt uint64 = 1<<64 - 1 z complex128 = cmath.Sqrt(-5+12i) ) func main() { const f = "%T(%v)\n" fmt.Printf(f, ToBe, ToBe) fmt.Printf(f, MaxInt, MaxInt) fmt.Printf(f, z, z) }

    Structs

    struct는 필드들의 모음입니다.

    (그리고 type 선언은 여러분이 예상하고 있는 것입니다.)

    package main import "fmt" type Vertex struct { X int Y int } func main() { fmt.Println(Vertex{1, 2}) }

    Struct Fields

    struct의 필드들은 점(.)을 사용해서 접근합니다.

    package main import "fmt" type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v.X) }

    Pointers

    Go는 포인터는 있지만, 포인터 연산은 없습니다.

    struct 필드들은 struct 포인터를 통해 접근할 수 있습니다. 이 포인터를 통한 간접지정은 명백합니다.

    package main import "fmt" type Vertex struct { X int Y int } func main() { p := Vertex{1, 2} q := &p q.X = 1e9 fmt.Println(p) }

    Struct Literals

    struct 리터럴은 struct 내 필드들의 값을 기재해서 새롭게 할당된 struct를 의미합니다.

    Name: 문법을 사용해서 필드들 중 일부만 기재할 수 있습니다. (이렇게 이름 지어진 필드들의 순서는 상관이 없습니다.)

    특별한 접두사 & 는 struct 리터럴에 대한 포인터를 만듭니다.

    package main import "fmt" type Vertex struct { X, Y int } var ( p = Vertex{1, 2} // has type Vertex q = &Vertex{1, 2} // has type *Vertex r = Vertex{X: 1} // Y:0 is implicit s = Vertex{} // X:0 and Y:0 ) func main() { fmt.Println(p, q, r, s) }

    The new function

    new(T) 라는 표현은 제로화된(zeroed) T 값을 메모리에 할당하고 그것에 대한 포인터를 리턴합니다.

    var t *T = new(T)

    또는

    t := new(T)
    package main import "fmt" type Vertex struct { X, Y int } func main() { v := new(Vertex) fmt.Println(v) v.X, v.Y = 11, 9 fmt.Println(v) }

    Maps

    map은 키와 값을 연관시킵니다. (역주: Python의 Dictionary와 같은)

    map은 사용하기 전에 make로 만들어져야 합니다. (new가 아니라); nil map은 비어있는 것이고 할당할 수 없습니다.

    package main import "fmt" type Vertex struct { Lat, Long float64 } var m map[string]Vertex func main() { m = make(map[string]Vertex) m["Bell Labs"] = Vertex{ 40.68433, 74.39967, } fmt.Println(m["Bell Labs"]) }

    Maps

    map 리터럴은 struct 리터럴과 같습니다만, 키가 필요합니다.

    package main import "fmt" type Vertex struct { Lat, Long float64 } var m = map[string]Vertex{ "Bell Labs": Vertex{ 40.68433, -74.39967, }, "Google": Vertex{ 37.42202, -122.08408, }, } func main() { fmt.Println(m) }

    Maps

    만일 최상위 레벨 타입이 타입명이라면, 리터럴의 요소에서 그 부분을 생략할 수 있습니다.

    package main import "fmt" type Vertex struct { Lat, Long float64 } var m = map[string]Vertex{ "Bell Labs": {40.68433, -74.39967}, "Google": {37.42202, -122.08408}, } func main() { fmt.Println(m) }

    Slices

    slice는 값들의 배열을 가리키고 또한 길이를 포함하고 있습니다.

    []TT 타입의 원소들을 가지는 slice 입니다.

    package main import "fmt" func main() { p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) for i := 0; i < len(p); i++ { fmt.Printf("p[%d] == %d\n", i, p[i]) } }

    Slices

    slice는 같은 array를 가리키는 새로운 slice를 만듬으로써 다시 slice화 될 수 있습니다.

    아래 표현은

    s[lo:hi]

    lo에서 hi-1까지의 요소들 중 일부분으로 slice를 만듭니다. 따라서

    s[lo:lo]

    이건 비어있는 slice고

    s[lo:lo+1]

    이건 하나의 요소를 가진 slice가 됩니다.

    package main import "fmt" func main() { p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) fmt.Println("p[1:4] ==", p[1:4]) // missing low index implies 0 fmt.Println("p[:3] ==", p[:3]) // missing high index implies len(s) fmt.Println("p[4:] ==", p[4:]) }

    Slices

    slice는 make 함수로 만듭니다. 이는 제로화된(zeroed) array를 메모리에 할당하고 그 array를 참조하는 slice를 리턴합니다:

    a := make([]int, 5)  // len(a)=5
            
    slice는 length와 capacity를 가지고 있습니다. capacity는 slice가 늘어날 수 있는 최대 length 입니다.

    capacity를 지정하기 위해서는 make 시 3 번째 인자를 넘겨주면 됩니다:

    b := make([]int, 0, 5)
    // len(b)=0, cap(b)=5
            
    slice는 re-slicing 될 수 있습니다 (그 slice의 capacity 한도까지)

    b = b[:cap(b)] // len(b)=5, cap(b)=5
    b = b[1:]      // len(b)=4, cap(b)=4
    	
    package main import "fmt" func main() { a := make([]int, 5) printSlice("a", a) b := make([]int, 0, 5) printSlice("b", b) c := b[:2] printSlice("c", c) d := c[2:5] printSlice("d", d) } func printSlice(s string, x []int) { fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x) }

    Slices

    slice의 zero value는 nil 입니다.

    nil slice는 0의 length, capacity를 가지게 됩니다.

    좀 더 자세한 내용은 "Go Slices: usage and internals" 이 포스트를 보세요.

    package main import "fmt" func main() { var z []int fmt.Println(z, len(z), cap(z)) if z == nil { fmt.Println("nil!") } }

    Functions

    함수 역시 값입니다.

    package main import ( "fmt" "math" ) func main() { hypot := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(3, 4)) }

    Functions

    함수는 완전한 클로저(closures) 입니다.

    adder 함수는 클로저를 리턴합니다. 각각의 클로저는 자신의 sum 변수에 바인딩 됩니다.

    package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } }

    Range

    for 루프에서 range 는 slice나 map에서 값을 하나씩 꺼내오면서 반복적으로 루프돌 수 있게 합니다.

    package main import "fmt" var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} func main() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } }

    Range

    _ 에 사용하지 않을 key나 value를 대입할 수 있습니다.

    만약에 이 예제에서 value가 아닌 slice의 index만 얻고자 한다면, “, value” 부분을 제거하면 됩니다.

    package main import "fmt" func main() { pow := make([]int, 10) for i := range pow { pow[i] = 1<<uint(i) } for _, value := range pow { fmt.Printf("%d\n", value) } }

    Switch

    여러분은 아마 switch가 어떤 것인지는 알 것입니다. 좀 다른 점은 case 에서 fallthrough 문을 끝에 써주지 않는다면 그 case는 자동으로 break 됩니다.

    package main import ( "fmt" "runtime" ) func main() { fmt.Print("Go runs on ") switch os := runtime.GOOS; os { case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: // freebsd, openbsd, // plan9, windows... fmt.Printf("%s.", os) } }

    Switch

    switch case는 위에서부터 아래로 case를 확인하는데, case 조건이 맞으면 멈추게 됩니다.

    (예로,

    switch i {
    case 0:
    case f():
    }

    만일 i == 0 이면, f 함수를 호출하지 않습니다.)

    package main import ( "fmt" "time" ) func main() { fmt.Println("When's Saturday?") today := time.LocalTime().Weekday switch time.Saturday { case today+0: fmt.Println("Today.") case today+1: fmt.Println("Tomorrow.") case today+2: fmt.Println("In two days.") default: fmt.Println("Too far away.") } }

    Switch

    조건이 없는 switch는 switch true 와 같습니다.

    package main import ( "fmt" "time" ) func main() { t := time.LocalTime() switch { case t.Hour < 12: fmt.Println("Good morning!") case t.Hour < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") } }

    Exercise: Loops and Functions

    함수와 루프를 가지고 놀아볼 간단한 방법으로, 뉴턴의 메서드를 이용한 제곱근을 구현해 보세요.

    이 경우에, 뉴턴의 메서드는 시작점 z를 고른 다음 그것을 반복함으로써 Sqrt(x) 근사값을 구하는 것입니다.

    처음에는 단지 저 공식을 10번 반복하도록 루프를 만드세요. 그리고 x를 1, 2, 3 등 다양한 값을 줬을 때 나오는 z 값이 얼마나 정답에 가까운지 보세요.

    다음으로, 루프를 돌 때 바로 직전에 구한 z 값이 더이상 변하지 않고 고정될 때 루프를 멈추도록 수정해보세요. 루프를 더 많이 혹은 적게 돌았는지 보세요. (또는 아주 작은 값만 변하게 될 때) math.Sqrt와 얼마나 가깝게 답이 나왔나요?

    힌트: 부동소수점을 선언하고 값을 초기화하기 위해서는 부동소수점 문법을 쓰든지 아니면 형변환을 사용하세요.

    	z := float64(0)
    	z := 0.0
    	
    package main import ( "fmt" ) func Sqrt(x float64) float64 { } func main() { fmt.Println(Sqrt(2)) }

    Exercise: Maps

    WordCount를 구현해봅시다. 이 함수는 string s안에 각 단어(“word”)의 수를 map으로 리턴해야 합니다. wc.Test 함수는 인자로 제공된 함수에 대해 테스트 수트를 실행하고 성공인지 실패인지를 출력합니다.

    strings.Fields 여기에서 도움이 될만한 것을 찾을 수 있을 것입니다.

    package main import ( "tourgo-tour.googlecode.com/hg/wc" ) func WordCount(s string) map[string]int { return map[string]int{"x": 1} } func main() { wc.Test(WordCount) }

    Exercise: Slices

    Pic을 구현해봅시다. Pic 함수는 dy 크기의 slice를 리턴해야 합니다. dy의 각 원소는 8비트 부호없는 정수의 dx slice 입니다. 이 프로그램을 실행했을 때, 흑백의 값들로 정수를 해석한 여러분의 그림을 표시하게 될 겁니다.

    이미지는 여러분이 선택하는 것입니다. 흥미로운 함수들은 x^y, (x+y)/2, x*y 를 포함합니다.

    ([][]uint8 내에서 각 []uint8를 할당하기 위해 루프를 사용해야 합니다.)

    package main import "tourgo-tour.googlecode.com/hg/pic" func Pic(dx, dy int) [][]uint8 { } func main() { pic.Show(Pic) }

    Exercise: Fibonacci closure

    함수로 재미있는 것을 좀 해봅시다.

    함수(클로저)를 리턴하는 피보나치(fibonacci) 함수를 구현해보세요. 리턴된 그 함수는 연속된 피보나치 수를 리턴하는 함수입니다.

    package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }

    Advanced Exercise: Complex cube roots

    complex64complex128 타입을 통한 Go의 복소수에 대한 지원을 살펴봅시다. 세제곱을 위해 뉴턴의 메서드는 반복하는 것이 됩니다.

    2의 세제곱근을 찾아서, 알고리즘이 제대로 동작하는지 확인해보세요. cmath.Pow 함수가 있습니다.

    package main import "fmt" func Cbrt(x complex128) complex128 { } func main() { fmt.Println(Cbrt(2)) }
    Methods and Interfaces

    Methods and Interfaces

    Methods

    Go에는 클래스가 없습니다. 하지만, struct 타입에 메서드를 정의할 수 있습니다.

    메서드 리시버func 키워드와 메서드명 사이에 적습니다.

    package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (p *Vertex) Abs() float64 { return math.Sqrt(p.X*p.X + p.Y*p.Y) } func main() { p := &Vertex{3, 4} fmt.Println(p.Abs()) }

    Methods

    사실, 단지 struct 뿐만 아니라 여러분이 만든 패키지 안에 직접 정의한 어떤 타입에라도 메서드를 정의할 수 있습니다.

    하지만, 다른 패키지로부터의 타입이나 기본 타입에는 메서드를 정의할 수 없습니다.

    package main import ( "fmt" "math" ) type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }

    Methods with pointer receivers

    메서드들은 명명된 타입(named type)이나 명명된 타입에 대한 포인터에 연관되어질 수 있습니다.

    우리는 방금 두 개의 Abs 메서드들을 보았습니다. 하나는 *Vertex 포인터 타입에 있는 것, 또 하나는 MyFloat 타입에 있는 것이죠.

    포인터 리시버를 사용하는 이유는 두 가지가 있습니다. 첫째는, 각 메서드 호출때마다 리시버의 값이 복사되는 것을 피하기 위해서 입니다. (만일 그 값의 타입이 아주 큰 struct라면 더 효과적) 두번째는, 그럼으로써 메서드는 그 리시버가 가리키고 있는 값을 변경할 수 있게 됩니다.

    리시버로 *Vertex 대신에 Vertex를 사용하도록 AbsScale 메서드 선언을 고쳐보세요.

    pVertex 일 때, Scale 메서드는 아무런 영향을 안미칩니다. Scalep를 변형시킵니다. p가 값(포인터가 아닌)일 때, 이 메서드는 Vertex의 복사된 값을 가지게 되고 원래의 값을 변형시킬 수 없습니다.

    Abs 역시 마찬가지입니다. 단지 p를 읽어들일 뿐입니다. 원래의 값(포인터를 통해)을 읽어들이든지 복사본을 읽어들이든지는 문제가 되지 않습니다.

    package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (p *Vertex) Scale(f float64) { p.X = p.X * f p.Y = p.Y * f } func (p *Vertex) Abs() float64 { return math.Sqrt(p.X*p.X + p.Y*p.Y) } func main() { p := &Vertex{3, 4} p.Scale(5) fmt.Println(p, p.Abs()) }

    Interfaces

    interface 타입은 메서드들의 집합에 의해 정의됩니다.

    interface 타입의 값은 그 interface의 메서드들을 구현한 어떠한 값이라도 담을 수 있습니다.

    package main import ( "fmt" "math" ) type Abser interface { Abs() float64 } func main() { var a Abser f := MyFloat(-math.Sqrt2) p := Vertex{3, 4} a = f // a MyFloat implements Abser a = &p // a *Vertex implements Abser a = p // a Vertex, does NOT // implement Abser fmt.Println(a.Abs()) } type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } func (p *Vertex) Abs() float64 { return math.Sqrt(p.X*p.X + p.Y*p.Y) }

    Interfaces

    한 타입은 메서드를 구현하기만 함으로써 어떤 인터페이스를 구현했다고 할 수 있습니다.

    명시적으로 인터페이스를 구현한다는 선언은 없습니다.

    이런 암묵적인 인터페이스는 인터페이스가 정의되어 있는 패키지와 구현된 패키지를 분리할 수 있게 해줍니다: 아무 곳에도 다른 곳에 의존된 곳은 없습니다.

    이런 방식은 개발자가 꼼꼼한 인터페이스 정의를 하도록 합니다. 왜냐하면 개발자가 모든 인터페이스 구현된 것을 찾을 필요도 없고, 인터페이스를 구현할 때 인터페이스 이름을 표시할 필요도 없기 때문입니다.

    Package ioReaderWriter 를 정하고 있습니다; 여러분이 할 필요가 없는.

    package main import ( "fmt" "os" ) type Reader interface { Read(b []byte) (n int, err os.Error) } type Writer interface { Write(b []byte) (n int, err os.Error) } type ReadWriter interface { Reader Writer } func main() { var w Writer // os.Stdout implements Writer w = os.Stdout fmt.Fprintf(w, "hello, writer\n") }

    Errors

    에러는 그 자신을 설명할 수 있는 그 무엇 입니다:

    package os
    
    type Error interface {
    	String() string
    }
    	
    package main import ( "fmt" "os" "time" ) type MyError struct { When *time.Time What string } func (e *MyError) String() string { return fmt.Sprintf("at %v, %s", e.When, e.What) } func run() os.Error { return &MyError{ time.LocalTime(), "it didn't work", } } func main() { if err := run(); err != nil { fmt.Println(err) } }

    Web servers

    Package httphttp.Handler를 구현한 어떤 타입을 사용해서 HTTP 요청 처리하는 방법을 제공합니다.

    package http
    
    type Handler interface {
    	ServeHTTP(w ResponseWriter,
    	          r *Request)
    }
    	

    이 예제에서, MyHandler 타입은 http.Handler를 구현합니다.

    인사말을 보려면 http://localhost:4000/ 여기를 방문해보세요. 주의: 이 예제는 웹 기반의 둘러보기에서는 실행되지 않습니다. 웹 서버 작성을 직접 해보기 위해 Go 설치 이 문서가 필요할 겁니다.

    package main import ( "fmt" "http" ) type Hello struct{} func (h Hello) ServeHTTP( w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello!") } func main() { var h Hello http.ListenAndServe("localhost:4000",h) }

    Images

    Package imageImage 인터페이스를 정의하고 있습니다:

    package image
    
    type Image interface {
    	ColorModel() ColorModel
    	Bounds() Rectangle
    	At(x, y int) Color
    }

    (자세한 것은 이 문서를 보세요.)

    ColorColorModel 역시 인터페이스입니다. 하지만, 우리는 미리 정의된 구현 image.RGBAColorimage.RGBAColorModel를 사용해서 이 부분을 무시할 것입니다.

    package main import ( "fmt" "image" ) func main() { m := image.NewRGBA(100, 100) fmt.Println(m.Bounds()) fmt.Println(m.At(0, 0).RGBA()) }

    Exercise: Errors

    앞선 연습문제에서 여러분이 작성했던 Sqrt 함수를 복사해와서 이 함수가 os.Error 값을 리턴하도록 수정해보세요.

    Sqrt는 음수가 주어졌을 때, 제곱근 구하는 것은 복소수를 지원하지 않으므로 nil이 아닌 값을 리턴해야 합니다.

    새로운 타입을 만드세요.

    type ErrNegativeSqrt float64

    그리고 이 것을 하나의 아래 메서드를 줘서 os.Error로 만드세요.

    func (e ErrNegativeSqrt) String() string

    ErrNegativeSqrt(-2).String() 이렇게 하면 "cannot Sqrt negative number: -2" 이런 메시지가 리턴되도록 String() 메서드를 만드세요.

    주의: String 메서드 안에서 fmt.Print(e)를 호출하는 것은 프로그램을 무한루프에 빠지게 할 겁니다. 이 것을 피하기 위해 여러분은 첫번째로 e를 형변환 할 수 있습니다. fmt.Print(float64(e)). 왜 그럴까요?

    Sqrt 함수에 음수가 주어졌을 때, ErrNegativeSqrt 값을 리턴하도록 수정하세요.

    package main import ( "fmt" "os" ) func Sqrt(f float64) (float64, os.Error) { return 0, nil } func main() { fmt.Println(Sqrt(2)) fmt.Println(Sqrt(-2)) }

    Exercise: HTTP Handlers

    아래 타입들을 구현해보세요. 그리고 그 타입에 ServeHTTP 메서드를 정의해보세요. 여러분의 웹 서버로 들어오는 특정 경로를 처리할 수 있도록 등록해보세요.

    type String string
    	
    type Struct struct {
    	Greeting string
    	Punct    string
    	Who      string
    }

    예로, 아래와 같은 핸들러를 등록할 수 있도록 해야 합니다.

    http.Handle("/string", String("I'm a frayed knot."))
    http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
    package main import ( "http" ) func main() { // your http.Handle calls here http.ListenAndServe("localhost:4000", nil) }

    Exercise: Images

    앞서 여러분이 만든 그림 제너레이터를 기억하시나요? 새로운 걸 하나 만들어 봅시다. 하지만 이번에는 데이터의 slice 대신에 image.Image의 구현을 리턴하도록 할 것입니다.

    여러분만의 Image 타입을 정의하고, the necessary methods 를 구현한 다음, pic.ShowImage을 호출하세요.

    Boundsimage.Rect(0, 0, w, h) 처럼 image.Rectangle를 리턴해야 합니다.

    ColorModelimage.RGBAColorModel을 리턴해야 합니다.

    At 하나의 컬러를 리턴해야 합니다. 마지막 그림 제너레이터의 v 값은 image.RGBAColor{v, v, 255, 255} 의 것과 일치합니다.

    package main import ( "image" "tourgo-tour.googlecode.com/hg/pic" ) type Image struct{} func main() { m := Image{} pic.ShowImage(m) }

    Exercise: Rot13 Reader

    흔한 패턴 중 하나는 io.Reader 입니다. 이건 어떤 경우에는 스트림을 바꾸기도 하는 또다른 io.Reader를 감싸고 있습니다.

    예로, gzip.NewReaderio.Reader(gzip된 데이터의 스트림)를 인자로 받고, *gzip.Decompressor를 리턴합니다. *gzip.Decompressor은 또한 io.Reader(압축 해제된 데이터의 스트림)을 구현하고 있습니다.

    io.Reader를 구현한 rot13Reader를 만들어보세요. rot13ReaderROT13가 적용되어 스트림을 변경하는 io.Reader로부터 읽어들여야 합니다. ROT13은 모든 알파벳 문자를 13자리씩 치환해서 암호화하는 방법입니다.

    rot13Reader 타입은 소스에 나와 있고, 이걸 Read 메서드를 구현한 io.Reader로 만들어보세요.

    package main import ( "io" "os" "strings" ) type rot13Reader struct { r io.Reader } func main() { s := strings.NewReader( "Lbh penpxrq gur pbqr!") r := rot13Reader{s} io.Copy(os.Stdout, &r) }
    Concurrency

    Concurrency

    Goroutines

    goroutine은 Go 런타임에서 관리되는 경량의 쓰레드입니다.

    go f(x, y, z)

    위 문장은 함수 f를 새로운 goroutine으로 실행합니다.

    f(x, y, z)

    f, x, y, z의 evaluation은 현재의 goroutine 안에서 일어나고, f의 실행은 새로운 goroutine 안에서 일어납니다.

    goroutine들은 같은 어드레스 공간에서 실행되기 때문에 공유 메모리는 동기화 되어야 합니다. sync 패키지는 다른 원시 타입들보다는 많이 쓰이지 않겠지만 유용한 방법을 제공합니다. (다음 슬라이드를 보세요.)

    package main import ( "fmt" "runtimetime" ) func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched()time.Sleep(100e6) fmt.Println(s) } } func main() { go say("world") say("hello") }

    Channels

    채널(channel)은 채널 오퍼레이터인 <- 를 통해 값을 보내고 받을 수 있는 타입화된 도관과 같은 것입니다.

    ch <- v    // v를 channel ch로 보냄.
    v := <-ch  // ch로부터 받아서 그 값을 v로 할당.
    

    (데이터는 "화살표"의 방향으로 흐릅니다.)

    map과 slice처럼 channel도 사용하기 전에 생성되어 있어야 합니다.

    ch := make(chan int)
    

    기본적으로 보내고 받은 것은 다른 한 쪽이 준비가 되기 전까지는 블럭됩니다. 그래서 명시적인 lock이나 조건 변수 없이도 goroutine이 동기화되도록 해줍니다.

    package main import "fmt" func sum(a []int, c chan int) { sum := 0 for _, v := range a { sum += v } c <- sum // send sum to c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x + y) }

    Buffered Channels

    channel은 buffered 될 수 있습니다. buffered channel을 초기화를 위해 make의 두 번째 인자로 버퍼 크기를 제공하면 됩니다.

    ch := make(chan int, 100)
    

    buffered channel로 데이터를 보낼 때는 단지 그 버퍼가 꽉 찼을 때만 블럭됩니다. 받을 때는 버퍼가 비어있을 때 블럭됩니다.

    버퍼가 넘치도록 예제를 수정해보고 무슨 일이 일어나는지 보세요.

    package main import "fmt" func main() { c := make(chan int, 2) c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) }

    Range and Close

    보내는 쪽에서는 더 이상 보낼 값이 없다는 걸 가리키기 위해 channel을 close 할 수 있습니다. 받는 쪽에서는 channel로부터 값을 받는 표현에 두 번째 파라미터를 줌으로써 그 channel이 닫혔는지 확인해볼 수 있습니다.

    v, ok := <-ch

    만약 더 이상 받을 값이 없고 channel이 닫혀있다면 okfalse가 됩니다.

    for i := range c 이렇게 하면 channel이 닫힐 때까지 루프를 돌며 반복적으로 그 channel로부터 값을 받아오게 됩니다.

    주의: 단지 보내는 쪽(sender)에서만 channel을 close 할 수 있습니다. 절대 받는 쪽(receiver)에서는 close 할 수 없습니다. 닫혀 있는 channel로 데이터를 보내는 것은 panic을 일으킬 것입니다.

    주의 하나 더: channel은 파일과 같지 않습니다; 보통은 channel을 파일 close 처럼 close 할 필요가 없습니다. channel을 close 하는 것은 receiver에서 더 이상 받을 값이 없다고 알게 할 때만 필요합니다.

    package main import ( "fmt" ) func fibonacci(n int, c chan int) { x, y := 1, 1 for i := 0; i < n; i++ { c <- x x, y = y, x + y } close(c) } func main() { c := make(chan int, 10) go fibonacci(cap(c), c) for i := range c { fmt.Println(i) } }

    Select

    select 문은 복수 개의 통신 작업에서 goroutine이 기다도록 합니다.

    select의 case들 중 하나가 실행할 수 있을 때까지 select는 블럭합니다. 그리고 case에 조건이 맞았을 때 case 안을 실행합니다. case가 복수 개가 만족되어 있다면 무작위로 하나를 선택해서 실행합니다.

    package main import "fmt" func fibonacci(c, quit chan int) { x, y := 1, 1 for { select { case c <- x: x, y = y, x + y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }

    Default Selection

    select 안에서 만족하는 case가 없다면 default가 실행됩니다.

    블럭되는 일 없이 보내기, 받기를 하려면 default를 사용하세요.

    select {
    case i := <-c:
    	// use i
    default:
    	// receiving from c would block
    }

    주의: 이 예제는 웹 기반의 둘러보기에서는 실행되지 않습니다. 왜냐하면 샌드박스 환경에서는 time의 개념이 없기 때문입니다. 이 예제를 실행해보기 위해 Go 설치 문서가 필요할 것입니다.

    package main import ( "fmt" "time" ) func main() { tick := time.Tick(1e8) boom := time.After(5e8) for { select { case <-tick: fmt.Println("tick.") case <-boom: fmt.Println("BOOM!") return default: fmt.Println(" .") time.Sleep(5e7) } } }

    Exercise: Equivalent Binary Trees

    이진 트리에서는 같은 순서를 가진 값들을 가지고도 잎(노드, leaves)에 저장하는 방식에 따라 많은 이진 트리 종류들이 있습니다. 예로, 아래에 1, 1, 2, 3, 5, 8, 13 순서를 저장하는 두 종류의 이진 트리가 있습니다.

    두 개의 이진 트리가 같은 순서로 저장을 했는지 체크하는 함수는 대부분의 프로그래밍 언어에서는 아주 복잡한 구현입니다. 우리는 간단한 해결책을 만들어보기 위해 Go의 병행성(concurrency)과 channel을 사용할 것입니다.

    이 예제는 tree 패키지를 사용하는데, 여기에는 아래와 같은 타입을 정의하고 있습니다.

    type Tree struct {
    	Left  *Tree
    	Value int
    	Right *Tree
    }
    

    Exercise: Equivalent Binary Trees

    1. Walk 함수를 구현해보세요.

    2. Walk 함수를 테스트해보세요.

    tree.New(k) 함수는 값 k, 2k, 3k, ..., 10k를 무작위의 구조화된 이진 트리로 만듭니다.

    새 채널 ch를 만들고 Walk를 시작해보세요:

    go Walk(tree.New(1), ch)
    

    이 채널로부터 값을 읽고 10 개의 값을 출력해보세요. 결과는 숫자 1, 2, 3, ..., 10 이 되어야 합니다.

    3. t1t2가 같은 값들을 저장하고 있는지 판단하기 위해 Walk를 사용해서 Same 함수를 구현해보세요.

    4. Same 함수를 테스트해보세요.

    Same(tree.New(1), tree.New(1)) 은 true를 리턴해야 합니다. Same(tree.New(1), tree.New(2)) 은 false를 리턴해야 합니다.

    package main import "tourgo-tour.googlecode.com/hg/tree" // Walk walks the tree t sending all values // from the tree to the channel ch. func Walk(t *tree.Tree, ch chan int) // Same determines whether the trees // t1 and t2 contain the same values. func Same(t1, t2 *tree.Tree) bool func main() { }

    Exercise: Web Crawler

    이번 예제에서는 웹 크롤러를 병렬화하기 위한 Go의 concurrency 기능을 사용할 것입니다.

    같은 URL을 두 번 가져오지 않도록 병렬적으로 URL 들을 가져오는 Crawl 함수를 수정해보세요.

    package main import ( "os" "fmt" ) type Fetcher interface { // Fetch returns the body of URL and // a slice of URLs found on that page. Fetch(url string) (body string, urls []string, err os.Error) } // Crawl uses fetcher to recursively crawl // pages starting with url, to a maximum of depth. func Crawl(url string, depth int, fetcher Fetcher) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: if depth <= 0 { return } body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { Crawl(u, depth-1, fetcher) } return } func main() { Crawl("http://golang.org/", 4, fetcher) } // fakeFetcher is Fetcher that returns canned results. type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f *fakeFetcher) Fetch(url string) (string, []string, os.Error) { if res, ok := (*f)[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } // fetcher is a populated fakeFetcher. var fetcher = &fakeFetcher{ "http://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "http://golang.org/pkg/", "http://golang.org/cmd/", }, }, "http://golang.org/pkg/": &fakeResult{ "Packages", []string{ "http://golang.org/", "http://golang.org/cmd/", "http://golang.org/pkg/fmt/", "http://golang.org/pkg/os/", }, }, "http://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, "http://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, }

    Where to Go from here...

    여러분은 Go 설치Go App Engine SDK를 다운로드 받는 것으로부터 시작할 수 있습니다.

    일단 Go를 여러분의 컴퓨터에 설치하거나 Go App Engine SDK를 다운로드 받았다면, Go Documentation은 좋은 시작점입니다. 여기에는 레퍼런스들, 튜토리얼, 비디오, 그리고 많은 것들이 포함되어 있습니다.

    만일 표준 라이브러리에 대한 도움이 필요하면, package reference를 보세요. Go 언어 자체에 대한 도움을 위해서는, Language Spec이 꽤 읽을만 할 겁니다.

    웹 애플리케이션 작성하는 것에 관심이 있다면, Wiki Codelab을 보세요.

    Go의 concurrency 모델을 좀 더 살펴보고 싶다면, Share Memory by Communicating를 보세요.

    First Class Functions in Go는 Go의 함수 타입에 대한 흥미로운 관점을 줄 것입니다.

    Go Blog에는 Go에 대한 유용한 글이 많이 있습니다.

    좀 더 알고 싶으시면 golang.org를 방문해보세요.

    Go 언어 한국 커뮤니티도 방문해보세요.

    번역: 김종민 (Google Plus | Twitter): 잘못된 부분이 있으면 언제든 편하게 알려주세요.