Wat is het idiomatische Go-equivalent van de ternaire operator van C?

In C/C++ (en veel talen van die familie) gebruikt een algemeen idioom om een variabele te declareren en te initialiseren, afhankelijk van een voorwaarde, de ternaire voorwaardelijke operator :

int index = val > 0 ? val : -val

Go heeft geen voorwaardelijke operator. Wat is de meest idiomatische manier om hetzelfde stuk code als hierboven te implementeren? Ik kwam tot de volgende oplossing, maar het lijkt nogal uitgebreid

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

Is er iets beters?


Antwoord 1, autoriteit 100%

Zoals aangegeven (en hopelijk niet verrassend), is het gebruik van if+elseinderdaad de idiomatische manierconditionals doen in Go.

Naast het volledige var+if+elsecodeblok wordt deze spelling echter ook vaak gebruikt:

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

en als je een codeblok hebt dat repetitief genoeg is, zoals het equivalent van int value = a <= b ? a : b, je kunt een functie maken om deze vast te houden:

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

De compiler zal zulke eenvoudige functies inline zetten, dus het is snel, duidelijker en korter.


Antwoord 2, autoriteit 34%

No Go heeft geen ternaire operator, waarbij de if/else-syntaxis isde idiomatische manier is.

Waarom heeft Go niet de operator ?:?

Er is geen ternaire testoperatie in Go. U kunt het volgende gebruiken om hetzelfde resultaat te bereiken:

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

De reden waarom ?:afwezig is in Go, is dat de ontwerpers van de taal hadden gezien dat de bewerking te vaak werd gebruikt om ondoordringbaar complexe uitdrukkingen te creëren. Het if-else-formulier, hoewel langer, is ongetwijfeld duidelijker. Een taal heeft slechts één voorwaardelijke controlestroomconstructie nodig.

— Veelgestelde vragen (FAQ) – De programmeertaal Go


Antwoord 3, autoriteit 20%

Stel dat je de volgende ternaire uitdrukking hebt (in C):

int a = test ? 1 : 2;

De idiomatische benadering in Go zou zijn om gewoon een If-blok te gebruiken:

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

Het is echter mogelijk dat dit niet aan uw eisen voldoet. In mijn geval had ik een inline-expressie nodig voor een sjabloon voor het genereren van codes.

Ik heb een onmiddellijk geëvalueerde anonieme functie gebruikt:

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

Dit zorgt ervoor dat beide takken niet even goed worden geëvalueerd.


Antwoord 4, autoriteit 14%

De ternaire kaart is gemakkelijk te lezen zonder haakjes:

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

Antwoord 5, autoriteit 5%

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

Dit zal niet beter presteren dan als/anders en vereist casten, maar het werkt. Ter info:

BenchmarkAbsTernary-8 100000000 18,8 ns/op

BenchmarkAbsIfElse-8 2000000000 0,27 ns/op


Antwoord 6, autoriteit 4%

Voorwoord:Zonder te argumenteren dat if elsede juiste keuze is, kunnen we nog steeds spelen met en plezier beleven aan taalgebaseerde constructies.

De volgende Ifconstructie is beschikbaar in mijn github.com/icza/goxbibliotheek met tal van andere methoden, namelijk de gox.Iftyp.


Go staat toe om methoden toe te voegen aan alle door de gebruiker gedefinieerde typen, inclusief primitieve typen zoals bool. We kunnen een aangepast type maken met boolals het onderliggende type, en dan hebben we met een eenvoudig type conversieop voorwaarde, toegang tot de methoden. Methoden die ontvangen en selecteren uit de operanden.

Zoiets:

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

Hoe kunnen we het gebruiken?

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

Bijvoorbeeld een ternair die max()doet:

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

Een ternaire die abs()doet:

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

Dit ziet er cool uit, het is eenvoudig, elegant en efficiënt (het komt ook in aanmerking voor inlining).

Een nadeel vergeleken met een “echte” ternaire operator: het evalueert altijd alle operanden.

Om uitgestelde en alleen indien nodig evaluatie te bereiken, is de enige optie het gebruik van functies (ofwel gedeclareerde functies of methoden, of functionele letterlijke waarden), die alleen worden aangeroepen wanneer / indien nodig:

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

Het gebruiken: laten we aannemen dat we deze functies hebben om aen bte berekenen:

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

Dan:

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

De voorwaarde is bijvoorbeeld het huidige jaar > 2020:

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

Als we functieletterwoorden willen gebruiken:

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

Laatste opmerking: als je functies met verschillende handtekeningen zou hebben, zou je ze hier niet kunnen gebruiken. In dat geval mag je een letterlijke functie met bijpassende handtekening gebruiken om ze nog steeds toepasbaar te maken.

Bijvoorbeeld als calca()en calcb()ook parameters zouden hebben (naast de retourwaarde):

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

Zo zou je ze kunnen gebruiken:

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

Probeer deze voorbeelden op de Go Playground.


Antwoord 7, autoriteit 2%

Als aluw branches bijwerkingen makenof computationeel duurzijn, zou het volgende een semantisch behoudzijn refactoring:

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

met normaal gesproken geen overhead (inline) en, belangrijker nog, zonder uw naamruimte vol te proppen met hulpfuncties die slechts één keer worden gebruikt(wat de leesbaarheid en het onderhoud belemmert). Live voorbeeld

Let op als u naïef de Gustavo’s aanpak toepast :

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

je krijgt een programma met ander gedrag; in het geval dat het programma val <= 0een niet-positieve waarde zou afdrukken terwijl dit niet zou moeten! (Naar analogie, als je de vertakkingen zou omdraaien, zou je overhead introduceren door onnodig een langzame functie aan te roepen.)


Antwoord 8

Eolds antwoord is interessant en creatief, misschien zelfs slim.

Het wordt echter aanbevolen om in plaats daarvan het volgende te doen:

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

Ja, ze compileren allebei naar in wezen dezelfde assembly, maar deze code is veel leesbaarder dan het aanroepen van een anonieme functie om een waarde te retourneren die in de eerste plaats naar de variabele had kunnen worden geschreven.

Kortom, eenvoudige en duidelijke code is beter dan creatieve code.

Bovendien is elke code die een letterlijke kaart gebruikt geen goed idee, omdat kaarten helemaal niet licht zijn in Go. Sinds Go 1.3 is willekeurige herhalingsvolgorde voor kleine kaarten gegarandeerd, en om dit af te dwingen, is het geheugen een stuk minder efficiënt geworden voor kleine kaarten.

Als gevolg hiervan is het maken en verwijderen van talloze kleine kaarten zowel ruimte- als tijdrovend. Ik had een stukje code dat een kleine kaart gebruikte (twee of drie sleutels zijn waarschijnlijk, maar veelvoorkomend gebruik was slechts één invoer) Maar de code was traag. We hebben het over minstens 3 ordes van grootte langzamer dan dezelfde code die herschreven is om een dual slice key[index]=>data[index] map te gebruiken. En waarschijnlijk was het meer. Omdat sommige bewerkingen die voorheen een paar minuten duurden, in milliseconden werden voltooid.\


Antwoord 9

Oneliners, hoewel gemeden door de makers, hebben hun plaats.

Deze lost het luie evaluatieprobleem op door u, optioneel, functies door te geven die indien nodig moeten worden geëvalueerd:

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

Uitvoer

hello
hello world
hello world
hello
bye
  • Functies die zijn ingevoerd, moeten een interface{}retourneren om aan de interne gegoten bediening te voldoen.
  • Afhankelijk van de context, kunt u ervoor kiezen om de uitvoer naar een specifiek type te werpen.
  • Als u hiervan een functie van dit wilde retourneren, moet u het inpakken zoals weergegeven met c.

De stand-alone oplossing hier is ook leuk, maar kan minder duidelijk zijn voor sommige toepassingen.


Antwoord 10

Zoals anderen hebben opgemerkt, heeft Golang geen ternaire operator of een equivalent. Dit is een opzettelijke beslissing om de leesbaarheid te vangen.

Dit leidde me onlangs naar een scenario die een bit-masker construeert op een zeer efficiënte manier werd moeilijk te lezen toen het idiomatisch is geschreven omdat het veel regels van het scherm inbracht, zeer inefficiënt bij ingekapseld als een functie, of beide, als De code produceert takken:

package lib
func maskIfTrue(mask uint64, predicate bool) uint64 {
  if predicate {
    return mask
  }
  return 0
}

Produceren:

       text    "".maskIfTrue(SB), NOSPLIT|ABIInternal, $0-24
        funcdata        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        funcdata        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        movblzx "".predicate+16(SP), AX
        testb   AL, AL
        jeq     maskIfTrue_pc20
        movq    "".mask+8(SP), AX
        movq    AX, "".~r2+24(SP)
        ret
maskIfTrue_pc20:
        movq    $0, "".~r2+24(SP)
        ret

Wat ik hiervan leerde, was om een beetje meer Go te gebruiken; het gebruik van een benoemd resultaat in de functie (result int)bespaart me een regel die het inde functie declareert (en je kunt hetzelfde doen met captures), maar de compiler herkent ook dit idioom (ken alleen een waarde IF toe) en vervangt het – indien mogelijk – door een voorwaardelijke instructie.

func zeroOrOne(predicate bool) (result int) {
  if predicate {
    result = 1
  }
  return
}

een resultaat zonder vertakkingen:

   movblzx "".predicate+8(SP), AX
    movq    AX, "".result+16(SP)
    ret

die dan vrij inline gaan.

package lib
func zeroOrOne(predicate bool) (result int) {
  if predicate {
    result = 1
  }
  return
}
type Vendor1 struct {
    Property1 int
    Property2 float32
    Property3 bool
}
// Vendor2 bit positions.
const (
    Property1Bit = 2
    Property2Bit = 3
    Property3Bit = 5
)
func Convert1To2(v1 Vendor1) (result int) {
    result |= zeroOrOne(v1.Property1 == 1) << Property1Bit
    result |= zeroOrOne(v1.Property2 < 0.0) << Property2Bit
    result |= zeroOrOne(v1.Property3) << Property3Bit
    return
}

produceert https://go.godbolt.org/z/eKbK17

   movq    "".v1+8(SP), AX
    cmpq    AX, $1
    seteq   AL
    xorps   X0, X0
    movss   "".v1+16(SP), X1
    ucomiss X1, X0
    sethi   CL
    movblzx AL, AX
    shlq    $2, AX
    movblzx CL, CX
    shlq    $3, CX
    orq     CX, AX
    movblzx "".v1+20(SP), CX
    shlq    $5, CX
    orq     AX, CX
    movq    CX, "".result+24(SP)
    ret

Other episodes