C의 삼항 연산자와 동등한 관용어는 무엇입니까?


297

C / C ++ (및 그 계열의 여러 언어)에서 조건에 따라 변수를 선언하고 초기화하는 일반적인 관용구는 삼항 조건 연산자를 사용합니다.

int index = val > 0 ? val : -val

Go에는 조건부 연산자가 없습니다. 위와 동일한 코드를 구현하는 가장 관용적 인 방법은 무엇입니까? 나는 다음 해결책에 왔지만 꽤 장황한 것처럼 보인다.

var index int

if val > 0 {
    index = val
} else {
    index = -val
}

더 좋은 것이 있습니까?


else 부분을 사용하여 값을 초기화하고 조건이 변경되는지 확인
만해도

어쨌든 많은 if / thens는 제거되어야했습니다. 우리는 35 년 전에 처음으로 BASIC 프로그램을 작성한 날부터 항상 이렇게했습니다. 예를 들면 다음과 같습니다. int index = -val + 2 * val * (val > 0);
hyc

9
@ hyc 당신의 예제는 go의 관용 코드만큼이나 읽기가 어렵거나 삼항 연산자를 사용하는 C의 버전과는 거리가 멀습니다. 어쨌든, AFAIK, 부울을 숫자 값으로 사용할 수 없으므로 Go에서이 솔루션을 구현할 수 없습니다.
Fabien

왜 그런 연산자를 제공하지 않았는지 궁금하십니까?
Eric Wang

@EricWang 두 가지 이유, AFAIK : 1- 필요하지 않으며 가능한 한 언어를 작게 유지하려고했습니다. 2- 그것은 남용되는 경향이있다. 즉 복잡한 여러 줄 표현에 사용되며 언어 설계자들은 그것을 좋아하지 않는다.
Fabien

답변:


244

지적했듯이 (그리고 놀랍지 않게도) 사용하는 if+else것은 실제로 Go에서 조건부를 수행 하는 관용적 방법 입니다.

그러나 완전한 var+if+else코드 블록 외에도이 철자가 자주 사용됩니다.

index := val
if val <= 0 {
    index = -val
}

및와 같이 반복 가능한 코드 블록이 있으면이 int value = a <= b ? a : b를 보유하는 함수를 만들 수 있습니다.

func min(a, b int) int {
    if a <= b {
        return a
    }
    return b
}

...

value := min(a, b)

컴파일러는 이러한 간단한 함수를 인라인하므로 빠르고 명확하며 짧습니다.


183
이봐 요! 방금 삼위 일체 연산자를 골랑으로 옮겼습니다! play.golang.org/p/ZgLwC_DHm0 . 매우 효율적입니다!
thwd

28
@tomwilde 솔루션은 꽤 흥미롭게 보이지만 삼항 연산자의 주요 기능 중 하나 인 조건부 평가가 부족합니다.
Vladimir Matveev

12
@VladimirMatveev는 클로저의 가치를 감싼다;)
nemo

55
c := (map[bool]int{true: a, false: a - 1})[a > b]작동하더라도 난독 화 IMHO의 예입니다.
Rick-777

34
경우 if/else관용적 접근 방식은 아마도 Golang이시키는 고려할 수 if/else조항이 값을 반환 : x = if a {1} else {0}. 이런 식으로 작동하는 유일한 언어는 아닙니다. 주류 예는 스칼라입니다. 참조 : alvinalexander.com/scala/scala-ternary-operator-syntax
Max Murphy

80

No Go에는 if / else 구문 을 사용 하는 관용적 인 삼항 연산자가 없습니다 .

Go에? : 연산자가없는 이유는 무엇입니까?

Go에는 3 진 테스트 작업이 없습니다. 다음을 사용하여 동일한 결과를 얻을 수 있습니다.

if expr {
    n = trueVal
} else {
    n = falseVal
}

?:Go에 빠진 이유 는 언어 디자이너가 조작이 너무 자주 사용되어 복잡한 표현을 만들지 못했기 때문입니다. if-else형태는 이상하지만, 의심 할 여지없이 명확하다. 언어에는 조건부 제어 흐름 구성이 하나만 필요합니다.

— 자주 묻는 질문 (FAQ)-Go 프로그래밍 언어


1
언어 디자이너가 본 것 때문에 전체 if-else블록에 대해 하나의 라이너를 생략 했습니까? 그리고 누가 if-else같은 방식으로 학대되지 않습니까? 나는 당신을 공격하지 않고, 단지 디자이너들의 변명이 충분히 유효하지 않다고 생각합니다
Alf Moh

58

다음과 같은 삼항식이 있다고 가정합니다 (C).

int a = test ? 1 : 2;

Go의 관용적 접근 방식은 단순히 if블록을 사용하는 것입니다 .

var a int

if test {
  a = 1
} else {
  a = 2
}

그러나 이는 요구 사항에 맞지 않을 수 있습니다. 필자의 경우 코드 생성 템플릿에 인라인식이 필요했습니다.

즉시 평가 된 익명 함수를 사용했습니다.

a := func() int { if test { return 1 } else { return 2 } }()

이렇게하면 두 가지 모두 평가되지 않습니다.


인라인 된 anon 함수의 한 분기 만 평가된다는 것을 아는 것이 좋습니다. 그러나 이와 같은 경우는 C의 삼항 연산자 범위를 벗어납니다.
Wolf

1
C 조건식 (일반적으로 삼항 연산자라고 함)에는 세 개의 피연산자가 expr1 ? expr2 : expr3있습니다. 경우에 expr1평가하여이하는 true, expr2평가 식의 결과입니다. 그렇지 않으면 expr3결과가 평가 및 제공됩니다. 이것은 K & R의 ANSI C 프로그래밍 언어 섹션 2.11에 있습니다. My Go 솔루션은 이러한 특정 의미를 유지합니다. @Wolf 제안하는 내용을 명확히 할 수 있습니까?
피터 보이어 1

내가 아는 것이 확실하지 않다. 아논 함수는 C / C ++의 삼항 연산자와는 다른 범위 (로컬 네임 스페이스)를 제공 할 수도 있습니다. 이 범위를 사용
Wolf

39

지도 삼항은 괄호없이 읽기 쉽습니다.

c := map[bool]int{true: 1, false: 0} [5 > 4]

왜 -2를 얻었는지 완전히 알지 못합니다 ... 그렇습니다. 해결 방법이지만 작동하고 형식이 안전합니다.
Alessandro Santini

30
그렇습니다. 작동하고 형식이 안전하며 창의적입니다. 그러나 다른 메트릭이 있습니다. 3 차 ops는 if / else와 동등한 런타임입니다 (예 : 이 S / O post 참조 ). 이 응답은 1) 두 가지 분기가 모두 실행되고 2) 맵을 생성하기 때문에 3) 해시를 호출하기 때문이 아닙니다. 이들 모두는 "빠른"것이지만 if / else만큼 빠르지는 않습니다. 또한 조건 {r = foo ()} else {r = bar ()} 인 경우 var r T보다 읽기 쉽지 않다고 주장합니다.
knight

다른 언어에서는 여러 변수가 있고 클로저 또는 함수 포인터 또는 점프와 함께이 방법을 사용합니다. 변수 수가 증가함에 따라 중첩 ifs를 작성하면 오류가 발생하는 반면 {(0,0,0) => {code1}, (0,0,1) => {code2} ...} [(x> 1 변수의 수가 증가함에 따라, y> 1, z> 1)] (의사 코드)가 점점 더 매력적으로됩니다. 클로저는이 모델을 빠르게 유지합니다. 비슷한 트레이드 오프가 적용되기를 기대합니다.
Max Murphy

나는 그 모델에 스위치를 사용할 것이라고 가정합니다. 때때로 불편한 경우에도 스위치가 자동으로 끊어지는 방식이 마음에 듭니다.
Max Murphy

8
Cassy Foesch가 지적한 대로 :simple and clear code is better than creative code.
Wolf

11
func Ternary(statement bool, a, b interface{}) interface{} {
    if statement {
        return a
    }
    return b
}

func Abs(n int) int {
    return Ternary(n >= 0, n, -n).(int)
}

그렇지 않으면 캐스팅보다 성능이 뛰어나지 않지만 작동합니다. 참고 사항 :

벤치 마크 : AbsTernary-8 100000000 18.8 ns / op

BenchmarkAbsIfElse-8 2000000000 0.27 ns / op


이것이 최고의 솔루션입니다, 축하합니다! 가능한 모든 경우를 처리하는 한 줄
Alexandro de Oliveira

2
조건부 평가를 처리한다고 생각하지 않습니까? 부작용이없는 분기에서는 (예와 같이) 중요하지 않지만 부작용이있는 경우 문제가 발생할 수 있습니다.
Ashton Wiersdorf

7

경우 모든 당신의 가지 부작용을 만들 거나있는 많은 계산 다음은을 것이다 의미 보존 리팩토링 :

index := func() int {
    if val > 0 {
        return printPositiveAndReturn(val)
    } else {
        return slowlyReturn(-val)  // or slowlyNegate(val)
    }
}();  # exactly one branch will be evaluated

일반적으로 오버 헤드 (인라인 된)가 없으며 가장 중요 하게는 한 번만 사용되는 도우미 함수로 네임 스페이스를 어지럽히 지 않고 (가독성 및 유지 관리를 방해합니다). 라이브 예

구스타보의 접근 방식 을 순진하게 적용한다면 같습니다.

    index := printPositiveAndReturn(val);
    if val <= 0 {
        index = slowlyReturn(-val);  // or slowlyNegate(val)
    }

당신은 다른 행동을 가진 프로그램을 얻게 될 것입니다 ; val <= 0프로그램이 양수가 아닌 값을 인쇄하는 경우에는 그렇지 않습니다! (분명히 분기를 반전하면 불필요하게 느린 함수를 호출하여 오버 헤드가 발생합니다.)


1
흥미있는 글이지만, 구스타보의 접근에 대한 당신의 비판의 요점을 실제로 이해하고 있지 않습니다. 나는 (종류)를 참조 abs원래 코드 (물론, 내가 바꿀 줄의 기능 <=<). 귀하의 예에서 초기화가 표시되는 경우가 있습니다. 어떤 경우에는 중복되어 광범위 할 수 있습니다. 아이디어를 좀 더 설명해 주시겠습니까?
Wolf

가장 큰 차이점은 두 가지 외부 에서 함수를 호출 하면 해당 분기를 수행하지 않아도 부작용 이 발생한다는 것입니다. 필자의 경우 함수 printPositiveAndReturn는 양수 만 호출 되므로 양수 만 인쇄 됩니다. 반대로, 항상 하나의 브랜치를 실행 한 다음 다른 브랜치를 실행하여 값을 "고정"하면 첫 번째 브랜치의 부작용이 취소되지 않습니다 .
old

알지만 프로그래머는 일반적으로 부작용을 알고 있습니다. 이 경우 컴파일 된 코드가 동일 할지라도 Cassy Foesch의 임베디드 함수에 대한 명확한 솔루션 을 선호합니다 . 더 짧고 대부분의 프로그래머에게는 분명합니다. 나를 잘못 이해하지 마라 : 나는 Go의 폐쇄를 정말로 좋아 한다;)
Wolf

1
" 경험 프로그래머가 일반적으로 부작용을 알고있다 "-아니오. 용어 평가를 피하는 것은 삼항 연산자의 주요 특징 중 하나입니다.
Jonathan Hartley

6

서문 : 주장하지 않고if else 갈 길이 , 우리는 여전히 언어 기반 구조를 가지고 놀고 즐거움을 찾을 수 있습니다.

다음 If구성은 github.com/icza/gox라이브러리에서 다른 많은 방법으로 사용할 수 있으며 builtinx.If유형입니다.


Go 는와 같은 기본 유형을 포함 하여 모든 사용자 정의 유형 에 메소드를 첨부 할 수 있습니다 bool. 기본 유형bool 으로 사용자 정의 유형을 작성한 다음 조건에 대한 간단한 유형 변환 으로 메소드에 액세스 할 수 있습니다. 피연산자를 받고 선택하는 메소드입니다.

이 같은:

type If bool

func (c If) Int(a, b int) int {
    if c {
        return a
    }
    return b
}

어떻게 사용할 수 있습니까?

i := If(condition).Int(val1, val2)  // Short variable declaration, i is of type int
     |-----------|  \
   type conversion   \---method call

예를 들어 삼항 max():

i := If(a > b).Int(a, b)

삼항 abs():

i := If(a >= 0).Int(a, -a)

멋있고, 단순하고 우아하며 효율적입니다 ( 인라인 에도 적합 함) ).

"실제"삼항 연산자와 비교할 때 한 가지 단점 : 항상 모든 피연산자를 평가합니다.

지연되고 필요한 경우에만 평가를 수행하려면 필요한 경우 / 필요할 때만 호출되는 함수 ( 선언 된 함수 또는 메소드 또는 함수 리터럴 ) 를 사용하는 것이 유일한 옵션입니다 .

func (c If) Fint(fa, fb func() int) int {
    if c {
        return fa()
    }
    return fb()
}

그것을 사용 :하자의 우리가 계산에 이러한 기능이 가정 ab:

func calca() int { return 3 }
func calcb() int { return 4 }

그때:

i := If(someCondition).Fint(calca, calcb)

예를 들어, 현재 연도> 2020 인 조건 :

i := If(time.Now().Year() > 2020).Fint(calca, calcb)

함수 리터럴을 사용하려는 경우 :

i := If(time.Now().Year() > 2020).Fint(
    func() int { return 3 },
    func() int { return 4 },
)

마지막 참고 사항 : 서명이 다른 기능이있는 경우 여기서 사용할 수 없습니다. 이 경우 서명이 일치하는 함수 리터럴을 사용하여 여전히 적용 할 수 있습니다.

예를 들어 calca()calcb()매개 변수가있는 경우 (반환 값 외에) :

func calca2(x int) int { return 3 }
func calcb2(x int) int { return 4 }

이것은 당신이 그들을 사용할 수있는 방법입니다 :

i := If(time.Now().Year() > 2020).Fint(
    func() int { return calca2(0) },
    func() int { return calcb2(0) },
)

Go Playground 에서이 예제를 사용해보십시오 .


4

eold의 답변은 흥미롭고 창의적이며 영리한 것일 수도 있습니다.

그러나 대신 다음을 수행하는 것이 좋습니다.

var index int
if val > 0 {
    index = printPositiveAndReturn(val)
} else {
    index = slowlyReturn(-val)  // or slowlyNegate(val)
}

예, 둘 다 본질적으로 동일한 어셈블리로 컴파일되지만이 코드는 처음에 변수에 쓸 수있는 값을 반환하기 위해 익명 함수를 호출하는 것보다 훨씬 읽기 쉽습니다.

기본적으로 단순하고 명확한 코드가 광고 코드보다 낫습니다.

또한지도 리터럴을 사용하는 코드는 Go에서 전혀 경량이 아니기 때문에 좋은 생각이 아닙니다. Go 1.3부터는 작은 맵의 임의 반복 순서가 보장되며이를 적용하기 위해 작은 맵의 경우 메모리 측면에서 훨씬 덜 효율적입니다.

결과적으로 수많은 작은지도를 만들고 제거하는 데 공간과 시간이 많이 소모됩니다. 작은 맵을 사용하는 코드 조각이 있었지만 (두 개 또는 세 개의 키가있을 수 있지만 일반적인 사용 사례는 하나의 항목 일뿐입니다) 코드는 개가 느 렸습니다. 우리는 듀얼 슬라이스 키 [index] => data [index] 맵을 사용하도록 재 작성된 동일한 코드보다 3 ​​배 이상 느리다고 말합니다. 그리고 아마도 더 많았습니다. 이전에 실행하는 데 몇 분이 걸리던 일부 작업이 밀리 초 안에 완료되기 시작했습니다. \


1
simple and clear code is better than creative code-이것은 매우 마음에 들지만 마지막 섹션에서 약간 혼란스러워지고 있습니다. dog slow아마도 다른 사람들과 혼동을 줄 수 있습니까?
Wolf

1
기본적으로 ... 하나, 둘 또는 세 개의 항목으로 작은 맵을 작성하는 코드가 있었지만 코드는 매우 느리게 실행되었습니다. 따라서 많은 m := map[string]interface{} { a: 42, b: "stuff" }함수를 반복하고 다른 함수에서이를 반복합니다. for key, val := range m { code here } 두 개의 슬라이스 시스템으로 전환 한 후 : keys = []string{ "a", "b" }, data = []interface{}{ 42, "stuff" }, 그리고 for i, key := range keys { val := data[i] ; code here }1000 배나 빨라진 것처럼 반복합니다 .
Cassy Foesch

설명해 주셔서 감사합니다. ( 이 시점에서 답 자체 개선 될 수도 있습니다.)
Wolf

1
-.- ... touché, logic ... touché ... 나는 결국 그것을 얻을 것이다 ...;)
Cassy Foesch

3

하나의 라이너는 제작자에 의해 기절되었지만 자리를 차지합니다.

필요한 경우 평가할 함수를 선택적으로 전달하여 지연 평가 문제를 해결합니다.

func FullTernary(e bool, a, b interface{}) interface{} {
    if e {
        if reflect.TypeOf(a).Kind() == reflect.Func {
            return a.(func() interface{})()
        }
        return a
    }
    if reflect.TypeOf(b).Kind() == reflect.Func {
        return b.(func() interface{})()
    }
    return b
}

func demo() {
    a := "hello"
    b := func() interface{} { return a + " world" }
    c := func() interface{} { return func() string { return "bye" } }
    fmt.Println(FullTernary(true, a, b).(string)) // cast shown, but not required
    fmt.Println(FullTernary(false, a, b))
    fmt.Println(FullTernary(true, b, a))
    fmt.Println(FullTernary(false, b, a))
    fmt.Println(FullTernary(true, c, nil).(func() string)())
}

산출

hello
hello world
hello world
hello
bye
  • 전달 된 함수 interface{}는 내부 캐스트 조작을 만족시키기 위해를 리턴해야합니다 .
  • 컨텍스트에 따라 출력을 특정 유형으로 캐스트하도록 선택할 수 있습니다.
  • 이것에서 함수를 반환하려면으로 표시된대로 랩해야합니다 c.

독립형 솔루션 여기가 도 좋은,하지만 일부 용도 불분명 수 있습니다.


이것이 확실히 학문적이지 않더라도 이것은 꽤 좋습니다.
Fabien
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.