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+else
inderdaad de idiomatische manierconditionals doen in Go.
Naast het volledige var+if+else
codeblok 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. Hetif-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 else
de juiste keuze is, kunnen we nog steeds spelen met en plezier beleven aan taalgebaseerde constructies.
De volgende If
constructie is beschikbaar in mijn github.com/icza/gox
bibliotheek met tal van andere methoden, namelijk de gox.If
typ.
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 bool
als 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 a
en b
te 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 <= 0
een 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