Go

Go 1.22의 새로운 기능: cmp.Or

드리프트2 2024. 7. 29. 21:12

 

안녕하세요?

Go 1.22가 출시된 지 꽤 많은 시간이 지났는데요.

 

이제 제가 1.22 버전을 정리하는 시리즈의 마지막을 장식할 때가 왔습니다.

 

이전에 작성한 reflect.TypeForslices.Concat 함수에 대한 글을 먼저 읽으시는게 좋을 듯 합니다.

 

오늘 소개할 마지막 함수는 바로 cmp.Or입니다.

 

실제로 이 함수의 개발자는 Go Time 팟캐스트에서 이 함수를 "1.22의 숨겨진 보석"이라고 소개한 바 있습니다.

 

간단한 기능이지만 다양한 활용 가능성을 가지고 있으며, 그 탄생 배경에는 놀랍도록 긴 이야기가 숨겨져 있습니다.

 

cmp.Or: 제네릭 기반 조건부 값 선택의 새로운 접근 방식

 

cmp.Or 함수는 가변 개수의 인자를 받아, 첫 번째 non-zero 값을 반환하는 제네릭 함수입니다.

 

모든 인자가 zero 값인 경우에는 zero 값을 반환합니다.

 

이 함수의 코드는 다음과 같습니다.

// Or returns the first of its arguments that is not equal to the zero value.
// If no argument is non-zero, it returns the zero value.
func Or[T comparable](vals ...T) T {
        var zero T
        for _, val := range vals {
                if val != zero {
                        return val
                }
        }
        return zero
}

 

Go에 대한 저의 기여가 대부분 그렇듯, cmp.Or 함수 역시 매우 간결하고 직관적인 코드로 구현되었습니다.

 

단순히 입력받은 인자들을 순회하면서 zero 값이 아닌 첫 번째 값을 반환하는 로직입니다.

 

cmp.Or의 활용: 다양한 상황에서의 유연한 값 선택

 

cmp.Or 함수는 주로 문자열 처리에서 빈 문자열("")을 대체할 기본값을 설정하는 데 사용됩니다.

 

예를 들어, 환경 변수를 가져오는 코드에서 환경 변수가 설정되지 않은 경우 기본값을 사용하고 싶을 때 cmp.Or(os.Getenv("SOME_VARIABLE"), "default")와 같이 사용할 수 있습니다.

 

숫자나 포인터 타입에도 적용 가능하며, 저의 실제 코드베이스에서 발췌한 몇 가지 예시는 다음과 같습니다.

body := cmp.Or(page.Body, rawContent)
name := cmp.Or(jwt.Username(), "Almanack")
credits = append(credits, cmp.Or(credit.Name, credit.Byline))
metadata.InternalID = cmp.Or(
    xhtml.InnerText(rows.Value("slug")),
    xhtml.InnerText(rows.Value("internal id")),
    metadata.InternalID,
)
scope.SetTag("username", cmp.Or(userinfo.Username(), "anonymous"))
currentUl = cmp.Or(
    xhtml.Closest(currentUl.Parent, xhtml.WithAtom(atom.Ul)),
    currentUl,
)

 

대부분의 경우 문자열에 대한 fallback 값을 제공하는 데 사용되지만, 마지막 예시에서는 non-nil *html.Node를 찾는 데 사용되는 것을 볼 수 있습니다.

 

또한 cmp.Compare 함수와 함께 사용하여 다중 조건 비교를 구현할 수도 있습니다.

// ... (Order struct 및 orders slice 정의)

// Sort by customer first, product second, and last by higher price
slices.SortFunc(orders, func(a, b Order) int {
        return cmp.Or(
                cmp.Compare(a.Customer, b.Customer),
                cmp.Compare(a.Product, b.Product),
                cmp.Compare(b.Price, a.Price),
        )
})

 

하지만 cmp.Or 함수는 short-circuit evaluation을 지원하지 않기 때문에, 첫 번째 조건이 참이더라도 나머지 조건들을 모두 평가하게 됩니다. 이는 성능 측면에서 고려해야 할 사항입니다.

 

cmp.Or의 탄생 배경: 오랜 기다림 끝에 얻은 결실

 

cmp.Or 함수가 탄생하기까지는 오랜 시간이 걸렸습니다.

 

2016년 Stephen Kampmann이 strings.First 함수를 제안했지만, 당시에는 체계적인 제안 시스템이 없었기 때문에 제대로 논의되지 못했습니다.

 

2020년에는 제가 short-circuiting을 지원하는 ?? 연산자를 제안했지만, Go 언어에 제네릭이 도입되면 제네릭 함수로 구현할 수 있다는 의견이 제시되었습니다.

 

제네릭이 Go 1.18 베타 버전에 추가된 후, 저는 reflect.Value.IsZero()를 사용하여 zero 값 여부를 판단하는 truthy 패키지를 작성했지만, 리플렉션 사용으로 인한 성능 저하 문제가 발생했습니다.

 

결국 2022년에 comparable 타입에 대한 제네릭 함수인 cmp.Or를 제안하게 되었고, 이 제안이 받아들여져 Go 1.22에 포함되었습니다.

 

cmp.Or의 미래: 더욱 강력하고 유연한 Go 언어를 향하여

 

cmp.Or 함수는 Go 언어의 표현력과 유연성을 향상시키는 데 기여할 것입니다.

 

특히 제네릭과의 시너지를 통해 더욱 강력하고 안전한 코드 작성을 가능하게 할 것으로 기대됩니다.

 

앞으로 Go 언어가 어떻게 발전해 나갈지 기대하며, cmp.Or 함수가 Go 생태계에 긍정적인 영향을 미치기를 바랍니다.

참고:

더 깊이 있는 학습을 위한 자료:

  • Go Time Podcast Episode on cmp.Or: [관련 에피소드 링크]
  • Understanding Go Generics: [제네릭 이해를 위한 자료 링크]
  • The History of Go Proposals: [Go 제안 시스템의 역사에 대한 자료 링크]

마무리하며:

 

cmp.Or 함수는 Go 개발자들에게 매우 유용한 도구가 될 것입니다.

 

특히 조건부 값 선택이 필요한 상황에서 코드를 더욱 간결하고 명확하게 작성할 수 있도록 도와줄 것입니다.