이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
이전 튜토리얼 살펴보기
서론
안녕하세요 K-IN 입니다.
"나도 모르게 고급 개발자 시리즈" 세 번째 시간입니다.
이전 튜토리얼에서 Go 언어의 패키지 개념과 import 구문의 특성을 알아보았습니다.
이번 시간에는 함수(Function)의 특징을 살펴보도록 하겠습니다.
본론
이 튜토리얼은 아래의 내용을 담고 있습니다.
- Go 언어에서 함수란?
- 함수의 특징 및 핵심원리 5가지
- 한글과 비슷한 특징을 가지는 Go 언어의 과학적인 문법
내용이 좀 많아 보이지만 매우 중요한 내용이기에 압축해서 핵.심.원.리 만 간추려서 설명하겠습니다.
Go 언어에서 함수(function)이란?
OOP를 절대 원칙으로 하는 언어들(Java 등등)은 모든 것은 클래스로 이루어져 있습니다.
하지만 Go 언어에서는 타입과 함수가 전부입니다. 무언가 부족한것 같다구요?
그렇지 않습니다. 오히려 더 심플하고 응용이 무궁무진합니다.
함수(function) 핵심정리
Go 언어의 함수의 핵심은 아래와 같습니다.
- 다른 언어와 동일하게 함수의 파라미터(혹은 인자)를 0개 이상 가질 수 있습니다.
- 파라미터의 이름 뒤에 타입(type)이 오는 독특한 구성입니다.
- 동일한 타입이 연속적으로 파라미터에 나열되면 생략할 수 있습니다.
- 여러개의 벨류를 리턴할 수 있습니다.
- 미리 리턴되는 변수의 이름을 함수의 시그니처 선언부에 정의할 수 있습니다.
자 그렇다면 하나씩 살펴볼까요?
1. 다른 언어와 동일하게 함수의 파라미터(혹은 인자)를 0개 이상 가질 수 있습니다.
위 명제는 아주 기본적인 함수의 정의이자 다른 언어에도 통용되는 요소입니다.
그리고 Go 언어에서도 이는 통용됩니다. 다만, 선언의 형식이 조금더 구체적입니다.
func add(x int, y int) int { // 2
return x + y
}
func main() { // 1
fmt.Println(add(42, 13))
}
그렇다면 C 혹은 Java의 함수의 형태를 살펴볼까요?
Go 언어가 가지는 특징을 좀더 분명하게 알 수 있습니다. 차이점을 나열해보겠습니다.
- 함수의 리턴이 함수 이름보다 앞선다.
- 타입이 변수의 이름 보다 앞선다.
// Java
public <T, R> Map<T, R> convertListToMap(List<T> list, Function<T, R> func) {
// blah
}
// C 또는 C++
int myFunction(int x, int y) {
return x + y;
}
그렇다면 Go 언어는 왜 다른 언어와 다른 선택을 했을까요?
이것은 우리가 코드를 읽는 순서를 생각해보면 명료해집니다.
아래의 함수를 사람의 말로 읽어보겠습니다.
- 이것은 함수(fun)이고 이름은 add 변수명은 x, y 이고 모두 정수(int) 그리고 리턴도 int
네 맞습니다. 왼쪽에서 오른쪽으로(left-to-right) 단순히 읽어만 가도 함수의 정의가 머리 속에 들어옵니다.
읽는 과정에서 앞뒤로 오갈 필요가 없죠. 그렇다면 C 혹은 Java 를 읽어봅시다.
// Go
func add(x int, y int) int { // 2
return x + y
}
- 다른 클래스에서도 참조가 가능한 public 접근제어자 ▶ 리턴 타입은 Generic 타입의 Map ▶ 함수 이름은 convert... ▶ List 타입의 ... list 라는 변수명을 사용 ▶ Function 타입의 func ▶ 리턴 타입이 무엇이었지? 다시 돌아가서 보자.
네, 한마디로 정리하면 사람의 생각의 방향과 정반대입니다.
만약 이러한 함수들이 수천줄이 된다면 여러분의 머리속은 함수를 읽는 과정에서 이미 복잡해진 상태일 것입니다.
실제로 개발자가 되면 하루에도 이런 코드들을 수천줄씩 읽어내야 한다는 것입니다.
// Java
public <T, R> Map<T, R> convertListToMap(List<T> list, Function<T, R> func) {
// blah
}
실제로 이것은 Go 언어의 설계 철학입니다.
아래의 문서를 한번 참고해서 어떤 배려가 있는지 살펴보세요.
https://go.dev/blog/declaration-syntax
쉬어가는 광고타임
코딩에 있어서 제일 중요한 것은 좋은 환경입니다.
저는 항상 작업을 할 때 애플 제품을 사용하고 있습니다.
애플 제품은 제품들 끼리 모아두면 시너지가 두세배가 된다고 합니다.
편안한 환경에서 조용한 음악과 함께 코딩을 하면서 새로운 가치를 창출해 해나가는 여러분의 모습을 상상해볼까요?
2. 파라미터의 이름 뒤에 타입(type)이 오는 독특한 구성입니다.
이미 1번 핵심원리를 체득하였다면 이제부터는 나열되는 특징을 그냥 받아들이면 됩니다.
add 함수가 있습니다. 그리고 x, y 라는 인자(인자의 타입은 int 이네요)를 받아서 더하기(+) 연산을 합니다.
그리고 이를 함수 밖으로 리턴합니다. 리턴하는 타입도 int 입니다.
func add(x int, y int) int {
return x + y
}
그렇다면, add 함수는 어떻게 사용하는지 볼까요?
네 사용방법도 간단합니다. 아무런 장치 없이 함수의 이름과 데이터만 넣어주면 되네요.
- 여기서 fmt.Println 은 여러분이 계산한 결과를 화면에 출력하는 구문입니다.
func main() {
fmt.Println(add(42, 13))
}
3. 동일한 타입이 연속적으로 파라미터에 나열되면 생략할 수 있습니다.
그런데, 만약 여러분이 로또 번호를 출력하는 함수로 구현해야 된다고 생각해볼까요?
벌써부터 함수를 작성하는데 손이 아파올 것 같네요.
로또 번호는 총 45개로 이루어져 있고 이중에서 6개를 선택해야 합니다.
만약, 여러분이 오늘 산 로또의 숫자를 입력해서 당첨 여부를 확인할 경우 함수를 작성해봅시다.
네 실제로 함수를 아래와 같이 작성했고 손이 매우 아팠습니다.
중간 부터는 내가 무슨짓을 하고 있나 싶기도 했네요.
func lotto(n1 int, n2 int, n3 int, n4 int, n5 int, n6 int) bool {
}
이러한 여러분의 마음을 아는지 Go 는 단축법을 고안했습니다. 바로 이것이죠.
- 연속적으로 동일한 타입의 인자가 오는 경우는 생략해도 된다.
그리고 그 결과는 마법과 같습니다. 이제부터 인자가 많아도 우리에게 두려움이란 없습니다.
func lotto(n1, n2, n3, n4, n5, n6 int) bool {
}
4. 여러 개의 벨류를 리턴할 수 있습니다.
리턴이라고 한다면 함수에서 함수 밖으로 무언가를 전달하는 것을 말합니다.
그런데 여러 개의 벨류를 리턴할 수 있다는 것은 어떤 의미일까요?
배열과 같은 것을 말하는 것일까요?
아닙니다. 전혀 다른 성질의 변수들을 여러 개 리턴할 수 있음을 의미합니다.
두 문자열 서로 교환하는 함수 swap 이 있다고 해볼까요?
아래와 같은 형태로 코딩이 가능합니다.
func swap(x, y string) (string, string) {
return y, x
}
a, b := "hello", "world"
a, b = swap(a, b)
fmt.Println(a, b)
// output: world hello
그렇다면 여러개 벨류를 리턴할 수 없다면 위의 코드는 어떻게 되어야 할까요?
일단 함수는 리턴 벨류가 한개이므로 리턴을 해보았자 의미가 없습니다.
그렇다면 구현을 하려면 포인터라는 개념이 등장해야 됩니다.
그리고 교환을 위해 임시적으로 값을 보관할 변수가 한개 필요합니다.
즉, 단순한 벨류를 교환하는 swap 함수 하나 구현하는데 포인터까지 등장해야 한다는 의미입니다.
너무나 어처구니 없는 뇌 연산 낭비입니다.
func swap(x *string, y *string) {
var tmp string
tmp = *y
*y = *x
*x = tmp
}
a, b := "hello", "world"
swap2(&a, &b)
fmt.Println(a, b)
// output: world hello
자 이제 여러 개의 벨류를 리턴한다는 의미를 체득하셨습니다.
5. 미리 리턴되는 변수의 이름을 함수의 시그니처 선언부에 정의할 수 있습니다.
핵심원리 제목이 조금 어렵습니다.
시그니처(signature)란 무엇일까요?
시그니처란 그 함수의 주요 정보를 말합니다. 아래 예시 코드에서 함수 정의를 보겠습니다.
아래의 함수들은 모두 다른 함수들입니다. 그런데 공통점을 발견할 수 있습니다.
인자가 모두 int 이고 리턴 타입이 int 입니다. 인자의 갯수도 2개로 동일합니다.
이를 시그니처가 같다고 표현합니다.
시그니처에 대해서는 나중에 다루겠지만 궁금증이 많은 분들을 위해 힌트를 남겨두겠습니다.
- 동일한 함수 시그니처를 가지는 함수 벨류들을 처리하는 함수를 정의
다 드린것 같지만, 튜토리얼을 천천히 이어나가면서 위 문구가 어떤 의미를 가지는지 살펴보겠습니다.
func add(x, y int) int // 덧셈
func minus(x, y int) int // 뺄셈
func mutiple(x, y int) int // 곱셈
func divide(x, y int) int // 나눗셈의 몫
func remainder(x, y int) int // 나눗셈의 나머지
시그니처라는 단어로 인해 조금 돌아왔는데요.
결국 함수의 시그니처 선언부란 함수의 이름이 작성되는 라인을 의미합니다.
그렇다면 리턴되는 변수의 이름을 미리 정의하는 것은 무슨 의미일까요?
아래의 예제 코드를 살펴보겠습니다.
func split(sum int) (x, y int) { // (1) 이상한 괄호가 하나 더 생겼네요 :)
x = sum * 4 / 9
y = sum - x
return
}
fmt.Println(split(17))
인자 영역 뒤에 또다시 괄호가 옵니다. 그리고 이 영역에 선언된 x, y 가 버젓이 함수 내부에서 사용되고 있습니다.
원래라면 이 코드는 아래와 같이 쓰여져야 합니다.
즉, 다시 말해 var x, y int 가 그대로 함수의 시그니처 선언부에 쏙 들어갔음을 의미합니다.
func split(sum int) (int, int) {
var x, y int
x = sum * 4/ 9
y = sum - x
return x, y
}
이를 Go 언어에서는 named return value (이름이 붙여진 리턴 값)이라고 부릅니다.
그리고 named return values 를 사용할 경우에도 return 이라는 구문을 사용해야 합니다.
Go 언어에서는 return 문만 덩그러니 놓여져 있는 것을 naked return 이라고 합니다.
참 재밌는 이름 명명법이네요.
결론
오늘은 담백하게 Go 언에서 함수란 무엇인지 그리고 어떤 특징이 있는지 핵심을 정리해보았습니다.
위에서 소개해드린 핵심원리만 기억하시면 앞으로도 헷갈리지 않고 Go 언어를 즐길 수 있습니다.
이상으로 K-IN 이었습니다.
즐거운 하루되세요
'프로그래밍 > Go' 카테고리의 다른 글
고 (Golang) | 이것만 알면 나도 개발 전문가 | Gin 을 이용한 CRUD RESTful API 개발 Part-2 (1) | 2023.03.01 |
---|---|
고 (Golang) | 이것만 알면 나도 개발 전문가 | Gin 을 이용한 CRUD RESTful API 개발 Part-1 (1) | 2023.03.01 |
고 (Golang) | 이것만 알면 나도 개발 전문가 | Cobra 를 이용한 CLI 프로그램 정석으로 구현하기 (1) | 2023.02.19 |
고(Golang) | 튜토리얼 01 | 나도 모르게 고급 개발자 시리즈 | 패키지(package)와 import 구문 (0) | 2023.02.16 |
고(Golang) | Tutorial | 5분만에 Golang 개발 환경 세팅하기 (1) | 2023.01.27 |