Home 4. function(adv)
Post
Cancel

4. function(adv)

Go/Golang - Function

Function 심화

vartadic function(가변인수 함수) 만들기

1
2
3
4
5
6
7
8
  // ex)
  func sum(nums ...int) int {
    sum := 0
    fmt.Printf("nums 타입: %T\n", nums)
    for _, v := range nums {sum += v}
    return sum
    }하지 않는다.
  }

vartadic function(가변인수 함수)와 interface{}

그렇다면 모든 타입을 받을 수 있는 가변인수는 어떻게 만들 수 있을까? 바로 빈 인터페이스(interface {}) 를 사용한다. 모든 타입은 interface{}를 포함하고 있으므로 interface{}를 가변인수로 받으면 모든 타입을 받을 수 있다.

1
2
3
4
5
6
7
8
  // ex)
  func alltypes(args ...interface{})) string {
    sum := 0
    fmt.Printf("nums 타입: %T\n", nums)
    for _, v := range nums {sum += v}
    return sum
    }하지 않는다.
  }

Defer 란 무엇인가?

함수 종료 전에 실행되는 Defer

defer는 함수의 종료 전 반드시 실행되어야 하는 구문을 표현할 때 사용된다. 예를들어 운영체제에서 사용한 자원을 반환해야 할 때 사용하면 유용하다. 특이한 점은, 작성한 defer의 역순으로 실행된다는 점이다.

1
2
3
	defer fmt.Println("반드시 호출됩니다.")  // 1
	defer f.Close()                     // 2
	defer fmt.Println("파일을 닫았습니다.") // 3

함수 타입 변수

함수 타입 변수

상황에 따라 다른 함수를 호출해야 하는 경우, 함수 타입 변수를 사용한다. 리턴타입이 함수인 것. 함수 또한 프로그램 카운터(PC)에 의해 표시되는 주소이다. 사용예시는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  func add(a, b int) int {
    return a + b
  }

  func mul(a, b int) int {
    return a * b
  }

  // calculation의 리턴타입은 `func (int, int) int` 인 것!
  func calculation(op string) func (int, int) int {
    if op == "+"{
      return add
    }
    .
    .
    .
  }
  // 매개변수로 요약한다면
  // type calReturnType func (int, int) int
  // func calculation(op string) calReturnType {
  //    ...
  // }

Function Literal이란 무엇인가?

익명함수, Lamda 라는 용어로 더 익숙하다. 함수명이 없는 형태로, 함수명으로 호출하지 않고 함수 타입 변수로만 호출된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  func calculation(op string) func (int, int) int {
    if op == "+"{
      return func (a, b int) int {
        return a + b
      }
    }
    .
    .
    .
  }

  // 사용 형태 1)
  function := func (a, b int) int {
        return a + b
      }
  res := function(1,2)

  // 사용 형태 2)
  function := func (a, b int) int {
        return a + b
      }(1,2)

Function Literal 내부 상태

함수 리터럴은 필요한 외부 변수를 내부 변수로 가질 수 있다. 함수 리터럴 내부에서 사용되는 외부변수는 자동으로 함수 내부 상태로 저장된다. 이렇게 함수 리터럴 외부 변수를 함수 내부로 가져오는 것을 캡쳐(capture) 라고 한다. 캡쳐는 값 복사가 아닌 참조 형태로 가져오게 된다. 따라서 아래 코드에서 주석이 아닌, 본 코드 그대로를 실행하면 3,3,3이 리턴되게 된다. i에 대한 참조복사가 일어나기 때문이다. 이를 방지하기 위해, 주석처럼 값을 임의변수에 대입해 해당 변수를 함수 리터럴 내부에서 사용해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  func CaptureOne() {
    f := make([]func(), 3)
    fmt.Println("this is refer capt")
    for i := 0; i < 3; i++ {
      // 캡쳐에서 값 복사를 원하는 경우
      // v := i  -> 이 경우, for문이 반복될 때마다 새로운 v가 할당된다.
      f[i] = func() {
        fmt.Println(i)
        // 캡쳐에서 값 복사를 원하는 경우
        // fmt.Println(v)
      }
    }

    for i:= 0; i < 3; i++ {
      f[i]()
    }
  }

Function Literal 예시2 파일 핸들

아래 코드에서는 writeHello안에 들어가는 파라미터를 함수 리터럴(Function Literal) 형태로 구현했다. 이때, writeHello 입장에서는 넘어오는 writer가 어떤 방식으로 동작할 지 모르게 된다. 이런 방식으로 외부에서 로직을 주입하는 형태의존성 주입(dependency injection) 이라고 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  type Writer func(string)

  func writeHello(writer Writer) {
    writer("Hello World")
  }

  // if change in interface form?
  // type Writer interface{
  //   writeHello( func(string) ) 
  // }

  // func writeHello(writer func(string) ) {
  //   writer("Hello World")
  // }

  func main(){
    f, err := os.Create("test.txt")
    if err != nil {
      fmt.Println("Failed to create a file")
      return 
    }

    defer f.Close()

    writeHello(func(msg string){
      fmt.Fprintln(f, msg)
    })
  }

Questions

Q1. 함수의 파라미터로 가변인수를 가져오려면 어떤 Keyword(혹은 기호) 를 사용하는가?

sub1) 해당 기호로 int형 가변인수들을 가져오면 리턴 타입은 무엇인가?

sub2) 그 리턴타입으로 함수 파라미터를 변경 후 디버깅 하면 어떻게 되는가?

sub3) 모든 타입을 인수로 받기 위한 방법을 설명하라

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. ...
// sub1. []int (슬라이스)
// sub2. 에러가 발생한다. 함수 입력을 슬라이스로 입력하면 정상 디버깅된다.
// sub3. interface{} 를 사용한다.
func sum(nums ...int) int {
	sum := 0
	fmt.Printf("nums 타입: %T\n", nums)
	for _, v := range nums {sum += v}
	return sum
}

func sum2(nums []int) int {
	sum := 0
	fmt.Printf("nums 타입: %T\n", nums)
	for _, v := range nums {sum += v}
	return sum
}

func main() {
	fmt.Println(sum(1, 2, 3, 4, 5)) 
	fmt.Println(sum(10, 20)) 
	fmt.Println(sum())

	fmt.Println(sum2([]int{1, 2, 3, 4, 5}))
}

Q2. 함수 리터럴(Function Literal)은 무엇인가? 또 함수 리터럴에서 외부변수를 내부로 가져오는 것을 무엇이라 부르는가?

1
2
  1) 함수명이 없는 형태로, 함수명으로 호출하지 않고 함수 타입 변수로만 호출하는 형태 : lamda
  2) capture

Q3. 아래 코드를 보고 의존성 주입을 설명하라. 또한 아래 의존성 주입을 함수 리터럴이 아닌 인터페이스로 구현하라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  type Writer func(string)

  func writeHello(writer Writer) {
    writer("Hello World")
  }

  // if change in interface form?
  // type Writer interface{
  //   writeHello( func(string) ) 
  // }

  // func writeHello(f *os.File, msg string) {
  //   fmt.Fprintln(f, msg)
  // }

  func main(){
    f, err := os.Create("test.txt")
    if err != nil {
      fmt.Println("Failed to create a file")
      return 
    }

    defer f.Close()

    writeHello(func(msg string){
      fmt.Fprintln(f, msg)
    })
    // writeHello(f, "hi hello")
  }
This post is licensed under CC BY 4.0 by the author.