C'nin üçlü operatörünün deyimsel Go eşdeğeri nedir?


297

C / C ++ 'da (ve bu ailenin birçok dilinde), bir koşula bağlı olarak bir değişkeni bildirmek ve başlatmak için ortak bir deyim üçlü koşullu işleci kullanır:

int index = val > 0 ? val : -val

Go'nun koşullu operatörü yok. Yukarıdaki ile aynı kod parçasını uygulamanın en deyimsel yolu nedir? Aşağıdaki çözüme geldim, ama oldukça ayrıntılı görünüyor

var index int

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

Daha iyi bir şey var mı?


değeri başka bir bölümle
başlatabilir

Yine de bir çok if / thens ortadan kaldırılmış olmalıdır. Bunu 35 yıl önce ilk BASIC programımı yazdığım günlerden beri yapıyorduk. Örneğin int index = -val + 2 * val * (val > 0);
şunlar

9
@hyc örneğiniz go'nun deyimsel kodu kadar okunabilir olmaktan, hatta üçlü operatörü kullanan C'nin sürümünden bile uzaktır. Her neyse, AFAIK, bir boolean sayısal değer olarak kullanılamayacağından Go'da bu çözümü uygulamak mümkün değildir.
Fabien

Go'nun neden böyle bir operatör sağlamadığını merak ediyor musunuz?
Eric Wang

@EricWang İki sebep, AFAIK: 1- buna ihtiyacınız yok ve dili olabildiğince küçük tutmak istediler. 2- istismar olma eğilimindedir, yani kıvrımlı çok satırlı ifadelerde kullanılır ve dil tasarımcıları bundan hoşlanmaz.
Fabien

Yanıtlar:


245

Belirtildiği gibi (ve umarım şaşırtıcı olmayan bir şekilde), kullanmak if+elsegerçekten Go'da koşullu olmanın deyimsel yoludur .

Bununla birlikte, tam var+if+elsekod bloğuna ek olarak , bu yazım sıklıkla kullanılır:

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

ve eşdeğeri gibi yeterince tekrarlayan bir kod bloğunuz varsa int value = a <= b ? a : b, bunu tutacak bir işlev oluşturabilirsiniz:

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

...

value := min(a, b)

Derleyici bu gibi basit işlevleri satır içi yapar, bu nedenle hızlı, daha net ve daha kısa olur.


184
Hey millet, bak! Sadece taşıdık ternarity golangs operatörün! play.golang.org/p/ZgLwC_DHm0 . Çok verimli!
thwd

28
@tomwilde çözümünüz oldukça ilginç görünüyor, ancak üçlü operatörün temel koşullarından biri yok - koşullu değerlendirme.
Vladimir Matveev

12
@VladimirMatveev değerleri kapanışlarda sarar;)
nemo

55
c := (map[bool]int{true: a, false: a - 1})[a > b]çalışıyor olsa bile, bir gizleme IMHO örneğidir.
Rick-777

34
Eğer if/elsedeyimsel yaklaşımdır o zaman belki Golang icar düşünebiliriz if/elsedeyimler bir değer döndürmek: x = if a {1} else {0}. Go hiçbir şekilde bu şekilde çalışan tek dil olmazdı. Ana akım bir örnek Scala'dır. Bakınız: alvinalexander.com/scala/scala-ternary-operator-syntax
Max Murphy

81

Hayır Git başka sözdizimi eğer / kullanarak, üçlü operatörü yok olduğunu deyimsel yol.

Go neden?: Operatörüne sahip değil?

Go'da üçlü test işlemi yoktur. Aynı sonucu elde etmek için aşağıdakileri kullanabilirsiniz:

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

?:Go'nun yok olmasının nedeni , dil tasarımcılarının operasyonu geçilmez karmaşık ifadeler oluşturmak için çok sık kullandığını görmesidir. if-elseBir şekilde, daha uzun olsa da, kuşkusuz daha nettir. Bir dilin yalnızca bir koşullu kontrol akış yapısına ihtiyacı vardır.

- Sık Sorulan Sorular (SSS) - Go Programlama Dili


1
Peki, dil tasarımcılarının gördükleri için, tüm if-elseblok için bir astarı atladılar mı? Kim if-elsedemiş böyle bir şekilde istismar edilmedi? Sana saldırmıyorum, tasarımcıların bahanesinin yeterince geçerli olmadığını hissediyorum
Alf Moh

58

Aşağıdaki üçlü ifadeye sahip olduğunuzu varsayalım (C cinsinden):

int a = test ? 1 : 2;

Go'daki deyimsel yaklaşım sadece bir ifblok kullanmak olacaktır :

var a int

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

Ancak, bu gereksinimlerinize uygun olmayabilir. Benim durumumda, bir kod oluşturma şablonu için satır içi bir ifade gerekiyordu.

Hemen değerlendirilen anonim bir işlev kullandım:

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

Bu, her iki dalın da değerlendirilmemesini sağlar.


Inline anon fonksiyonunun sadece bir dalının değerlendirildiğini bilmek güzel. Ancak bunun gibi vakaların C'nin üçlü operatörünün kapsamı dışında olduğunu unutmayın.
Kurt

1
(Yaygın üçlü operatörü olarak da bilinir), Cı-koşullu ifade üç işlenen var expr1 ? expr2 : expr3. Eğer expr1değerlendirir için true, expr2değerlendirilir ve ekspresyon sonucu olur. Aksi takdirde expr3sonuç olarak değerlendirilir ve sağlanır. Bu, K&R'nin ANSI C Programlama Dili bölümü 2.11'den alınmıştır. My Go çözümü bu belirli semantiği korur. @Wolf Ne önerdiğini açıklayabilir misin?
Peter Boyer

Aklımda ne olduğunu emin değilim, belki anon fonksiyonları C / C ++ üçlü operatör ile böyle bir kapsam (yerel ad alanı) sağlamak. Bu kapsamı kullanmak için
Wolf

39

Harita üçlüsü parantez olmadan kolayca okunabilir:

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

Neden -2'e sahip olduğundan tam olarak emin değilim ... evet, bu bir çözümdür, ancak çalışır ve tip güvenlidir.
Alessandro Santini

30
Evet, işe yarıyor, tip-güvenli ve hatta yaratıcı; ancak, başka metrikler de vardır. Üçlü ops, çalışma zamanı if / else ile eşdeğerdir (bkz. Örneğin bu S / O postası ). Bu yanıt, 1) her iki dalın yürütüldüğü, 2) bir harita oluşturduğu için 3) bir karma çağırdığı için değildir. Bunların hepsi "hızlı", ancak bir if / else kadar hızlı değil. Ayrıca, eğer şart {r = foo ()} başka {r = bar ()}
şövalye

Diğer dillerde, birden çok değişkenim olduğunda ve kapanışları veya işlev işaretçileri veya atlamalarıyla bu yaklaşımı kullanıyorum. Değişken sayısı arttıkça iç içe geçmiş if'lerin yazılması hataya açık hale gelirken, örneğin {(0,0,0) => {code1}, (0,0,1) => {code2} ...} [(x> 1 , y> 1, z> 1)] (sözde kod) değişkenlerin sayısı arttıkça daha da cazip hale gelir. Kapaklar bu modeli hızlı tutar. Benzer deneklerin hareket halinde uygulanmasını bekliyorum.
Max Murphy

Sanırım bu model için bir anahtar kullanacaksınız. Zaman zaman rahatsız edici olsa bile, anahtarların otomatik olarak kırılma şeklini seviyorum.
Max Murphy

8
Cassy Foesch'in işaret ettiği gibi :simple and clear code is better than creative code.
Kurt

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)
}

Bu, / else durumunda daha iyi performans göstermez ve cast gerektirir ancak çalışır. Bilginize:

KarşılaştırmaAbsTernary-8 100000000 18,8 ns / op

BenchmarkAbsIfElse-8 2000000000 0,27 ns / op


Bu en iyi çözüm, tebrikler! Tüm olası davaları ele alan bir satır
Alexandro de Oliveira

2
Bunun koşullu değerlendirmeyi işlediğini düşünmüyorum, değil mi? Yan etki serbest dalları ile bu önemli değildir (örneğin örnekte olduğu gibi), ancak yan etkilere sahip bir şey varsa sorunlarla karşılaşırsınız.
Ashton Wiersdorf

7

Eğer tüm senin dalları yan etkileri yapmak veya olan hesaplama pahalı şu bir olur anlamsal koruyarak üstlenmeden:

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

normalde ek yük olmadan (satır içi) ve en önemlisi, ad alanınızı yalnızca bir kez kullanılan bir yardımcı işlevle (okunabilirliği ve bakımı engelleyen) engellemeden. Canlı Örnek

Gustavo'nın yaklaşımını saf bir şekilde uygulayacak olsaydınız :

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

farklı bir davranışa sahip bir program alırsınız ; val <= 0Programın pozitif olmamalı bir değer yazdırması durumunda ! (Benzer şekilde, dalları tersine çevirirseniz, gereksiz bir şekilde yavaş bir işlevi çağırarak ek yük getirebilirsiniz.)


1
İlginç bir okuma, ama Gustavo'nın yaklaşımını eleştirmenizin nedenini gerçekten anlamıyorum. Ben (tür) bakınız absorijinal kod (iyi, ben değiştirmek istiyorum işlevi <=için <). Örneğinizde, bazı durumlarda gereksiz ve geniş olabilecek bir başlatma görüyorum. Lütfen açıklığa kavuşturabilir misiniz: fikrinizi biraz daha açıklayın?
Kurt

Asıl fark, her iki dalın dışında bir işlev çağırmanın , o dalın alınmamış olması durumunda bile yan etkiler yapmasıdır. Benim durumumda, işlev printPositiveAndReturnyalnızca pozitif sayılar için çağrıldığından yalnızca pozitif sayılar yazdırılır . Tersine, her zaman bir dalı yürütmek, sonra değeri farklı bir dalı yürütmekle "sabitlemek" ilk dalın yan etkilerini geri almaz .
eold

Anlıyorum, ancak deneyimler programcılar normalde yan etkilerin farkındadır. Bu durumda , derlenmiş kod aynı olsa bile Cassy Foesch'in gömülü bir işleve açık çözümünü tercih ederim : daha kısa ve çoğu programcı için açık görünüyor. Beni yanlış anlamayın: Go'nun kapanışlarını gerçekten çok seviyorum ;)
Wolf

1
" Deneyimler programcılar normalde yan etkilerin farkında " - Hayır. Terimlerin değerlendirilmesinden kaçınmak üçlü bir operatörün temel özelliklerinden biridir.
Jonathan Hartley

6

Önsöz: Bunu tartışmadanif else yolun , dil etkin yapılarda hala oynayabilir ve zevk bulabiliriz.

Aşağıdaki Ifyapı, github.com/icza/goxkütüphanemde tür olan birçok başka yöntemle kullanılabilir builtinx.If.


Go, ilkel türler de dahil olmak üzere kullanıcı tanımlı türlere yöntem eklenmesine izin verir bool. Biz sahip özel bir türü oluşturabilir boolonun kadar altta yatan tip ve sonra basit bir tip olan dönüşüm şartıyla, biz onun yöntemlerine erişim hakkına sahiptir. İşlenenlerden alma ve seçim yöntemleri.

Bunun gibi bir şey:

type If bool

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

Nasıl Kullanabiliriz?

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

Örneğin bir üçlü max():

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

Üçlü abs():

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

Bu harika görünüyor, basit, zarif ve verimli (aynı zamanda satır içi için de uygun ).

"Gerçek" üçlü operatörle karşılaştırıldığında bir dezavantaj: her zaman tüm işlenenleri değerlendirir.

Ertelenmiş ve yalnızca gerekirse değerlendirme yapmak için tek seçenek, yalnızca gerektiğinde / gerekirse çağrılan işlevleri ( bildirilen işlevler veya yöntemler veya işlev değişmezleri ) kullanmaktır:

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

Kullanmak: Diyelim ki hesaplamak için bu fonksiyonlara sahibiz ave b:

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

Sonra:

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

Örneğin, mevcut yıl> 2020 olan koşul:

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

İşlev değişmezlerini kullanmak istiyorsak:

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

Son not: Farklı imzalara sahip işlevleriniz varsa, burada kullanamazsınız. Bu durumda, hala uygulanabilir hale getirmek için eşleşen imzalı bir işlev değişmezi kullanabilirsiniz.

Örneğin , parametrelerde de calca()ve varsa calcb()(dönüş değerinin yanı sıra):

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

Bunları şu şekilde kullanabilirsiniz:

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

Go Playground'da bu örnekleri deneyin .


4

eold'un cevabı ilginç ve yaratıcı, belki de akıllı.

Ancak bunun yerine şunları yapmanız önerilir:

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

Evet, her ikisi de esasen aynı derlemeye derler, ancak bu kod, ilk etapta değişkene yazılmış olabilecek bir değeri döndürmek için anonim bir işlevi çağırmaktan çok daha okunaklıdır.

Temel olarak, basit ve net kod, reklam kodundan daha iyidir.

Ayrıca, harita hazır bilgisi kullanan herhangi bir kod iyi bir fikir değildir, çünkü Go'da haritalar hiç hafif değildir. Go 1.3'ten beri, küçük haritalar için rastgele yineleme sırası garanti edilir ve bunu uygulamak için küçük haritalar için bellek açısından biraz daha az verimli bir şekilde kazanılır.

Sonuç olarak, çok sayıda küçük harita oluşturmak ve kaldırmak hem alan hem de zaman alıcıdır. Küçük bir harita (iki ya da üç anahtar, büyük olasılıkla, ancak ortak kullanım durumu sadece bir giriş oldu) kullanılan bir kod parçası vardı Ama kod köpek yavaş oldu. İkili dilim anahtarı [index] => data [index] haritasını kullanmak için yeniden yazılan aynı koddan en az 3 büyüklükte daha az bahsediyoruz. Ve muhtemelen daha fazlaydı. Daha önce çalışması birkaç dakika süren bazı işlemler milisaniyeler içinde tamamlanmaya başladı. \


1
simple and clear code is better than creative code- Bu çok hoşuma gidiyor ama son bölümde biraz kafam karışıyor dog slow, belki bu başkaları için de kafa karıştırıcı olabilir?
Kurt

1
Yani, temelde ... Bir, iki veya üç girişli küçük haritalar oluşturan bazı kodlar vardı, ancak kod çok yavaş çalışıyordu. Yani, birçoğu m := map[string]interface{} { a: 42, b: "stuff" }ve sonra başka bir işlevde yineleme: for key, val := range m { code here } İki dilim sistemine geçtikten sonra: keys = []string{ "a", "b" }, data = []interface{}{ 42, "stuff" }ve sonra for i, key := range keys { val := data[i] ; code here }işler 1000 kat hızlandırarak tekrarlayın .
Cassy Foesch

Anladım, açıklama için teşekkürler. (Belki cevabı kendisi olabilir bu noktada geliştirilebilir.)
Kurt

1
-.- ... dokunma, mantık ...
dokunma

3

Tek gömlekler, içerik oluşturucular tarafından kapatılsa da yerlerini alır.

Bu, isteğe bağlı olarak, gerekirse değerlendirilecek işlevleri geçmenize izin vererek tembel değerlendirme sorununu çözer:

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)())
}

Çıktı

hello
hello world
hello world
hello
bye
  • Aktarılan işlevler bir interface{} dahili döküm işlemini tatmin etmek için a .
  • Bağlama bağlı olarak, çıktıyı belirli bir türe atamayı seçebilirsiniz.
  • Bundan bir işlev döndürmek isterseniz, işlevini gösterildiği gibi sarmanız gerekir c.

Tek başına çözüm burada da güzel, ama bazı kullanımlar için daha az açık olabilir.


Bu kesinlikle akademik olmasa bile, bu oldukça hoş.
Fabien
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.