Em C / C ++ (e em muitos idiomas dessa família), um idioma comum para declarar e inicializar uma variável dependendo de uma condição usa o operador condicional ternário:
int index = val > 0 ? val : -val
Go não tem o operador condicional. Qual é a maneira mais idiomática de implementar o mesmo trecho de código acima? Cheguei à seguinte solução, mas parece bastante detalhada
var index int
if val > 0 {
index = val
} else {
index = -val
}
Existe algo melhor?
int index = -val + 2 * val * (val > 0);
Respostas:
Como apontado (e espero que não surpreenda), usar
if+else
é realmente a maneira idiomática de fazer condicionais no Go.Além do
var+if+else
bloco de código completo , porém, essa ortografia também é usada com frequência:e se você tiver um bloco de código que seja repetitivo o suficiente, como o equivalente a
int value = a <= b ? a : b
, poderá criar uma função para mantê-lo:O compilador incorporará funções simples, por isso é rápido, mais claro e mais curto.
fonte
c := (map[bool]int{true: a, false: a - 1})[a > b]
é um exemplo de ofuscação IMHO, mesmo que funcione.if/else
é a abordagem idiomática então talvez golang poderia considerar deixarif/else
cláusulas retornar um valor:x = if a {1} else {0}
. O Go não seria de modo algum o único idioma para funcionar dessa maneira. Um exemplo principal é o Scala. Veja: alvinalexander.com/scala/scala-ternary-operator-syntaxO No Go não possui um operador ternário, usando a sintaxe if / else é a maneira idiomática.
fonte
if-else
bloco inteiro ? E quem disse queif-else
não é abusado da mesma maneira? Não estou atacando você, apenas sinto que a desculpa dos designers não é válida o suficienteSuponha que você tenha a seguinte expressão ternária (em C):
A abordagem idiomática no Go seria simplesmente usar um
if
bloco:No entanto, isso pode não atender aos seus requisitos. No meu caso, eu precisava de uma expressão embutida para um modelo de geração de código.
Eu usei uma função anônima imediatamente avaliada:
Isso garante que as duas ramificações também não sejam avaliadas.
fonte
expr1 ? expr2 : expr3
. Seexpr1
avalia comotrue
,expr2
é avaliado e é o resultado da expressão. Caso contrário,expr3
é avaliado e fornecido como resultado. Isto é da seção de linguagem de programação ANSI C 2.11 da K&R. A solução My Go preserva essas semânticas específicas. @Wolf Você pode esclarecer o que está sugerindo?O ternário do mapa é fácil de ler sem parênteses:
fonte
simple and clear code is better than creative code.
Isso não terá um desempenho superior a if / else e requer conversão, mas funciona. PARA SUA INFORMAÇÃO:
BenchmarkAbsTernary-8 100000000 18,8 ns / op
BenchmarkAbsIfElse-8 2000000000 0,27 ns / op
fonte
Se todas as suas ramificações produzem efeitos colaterais ou são computacionalmente caras, o seguinte seria uma refatoração que preserva semanticamente :
normalmente sem sobrecarga (embutida) e, o mais importante, sem sobrecarregar seu espaço de nome com funções auxiliares que são usadas apenas uma vez (o que dificulta a legibilidade e a manutenção). Exemplo ao vivo
Observe se você deveria aplicar ingenuamente a abordagem de Gustavo :
você obteria um programa com um comportamento diferente ; caso o
val <= 0
programa imprima um valor não positivo enquanto não deveria! (Analogamente, se você revertesse as ramificações, introduziria sobrecarga chamando uma função lenta desnecessariamente.)fonte
abs
função no código original (bem, eu mudaria<=
para<
). No seu exemplo, vejo uma inicialização que é redundante em alguns casos e pode ser expansiva. Você pode esclarecer: explique um pouco mais a sua ideia?printPositiveAndReturn
é chamada apenas para números positivos. Por outro lado, sempre executando uma ramificação, "fixar" o valor com a execução de uma ramificação diferente não desfaz os efeitos colaterais da primeira ramificação .Prefácio: Sem argumentando que
if else
é o caminho a seguir, ainda podemos brincar e encontrar prazer em construções habilitadas para idiomas.A
If
construção a seguir está disponível na minhagithub.com/icza/gox
biblioteca com muitos outros métodos, sendo obuiltinx.If
tipoGo permite anexar métodos a qualquer tipo definido pelo usuário , incluindo tipos primitivos como
bool
. Podemos criar um tipo personalizado tendobool
como tipo subjacente e, em seguida, com uma simples conversão de tipo na condição, teremos acesso a seus métodos. Métodos que recebem e selecionam dos operandos.Algo assim:
Como podemos usá-lo?
Por exemplo, um ato ternário
max()
:Um ato ternário
abs()
:Parece legal, é simples, elegante e eficiente (também é elegível para inlining ).
Uma desvantagem em comparação com um operador ternário "real": ele sempre avalia todos os operandos.
Para obter uma avaliação adiada e apenas se necessário, a única opção é usar funções ( funções ou métodos declarados ou literais de função ), que são chamados apenas quando / se necessário:
Usando-o: Vamos assumir que temos essas funções para calcular
a
eb
:Então:
Por exemplo, a condição sendo o ano atual> 2020:
Se queremos usar literais de função:
Nota final: se você tiver funções com diferentes assinaturas, não poderá usá-las aqui. Nesse caso, você pode usar uma função literal com assinatura correspondente para torná-las ainda aplicáveis.
Por exemplo, se
calca()
ecalcb()
também teria parâmetros (além do valor de retorno):É assim que você pode usá-los:
Experimente estes exemplos no Go Playground .
fonte
A resposta de Eold é interessante e criativa, talvez até inteligente.
No entanto, seria recomendável fazer:
Sim, ambos são compilados essencialmente no mesmo assembly, no entanto, esse código é muito mais legível do que chamar uma função anônima apenas para retornar um valor que poderia ter sido gravado na variável em primeiro lugar.
Basicamente, código simples e claro é melhor que código criativo.
Além disso, qualquer código que use um literal de mapa não é uma boa ideia, porque os mapas não são leves no Go. Desde o Go 1.3, a ordem de iteração aleatória para mapas pequenos é garantida e, para impor isso, é um pouco menos eficiente em termos de memória para mapas pequenos.
Como resultado, fazer e remover vários pequenos mapas consome espaço e consome tempo. Eu tinha um pedaço de código que usava um mapa pequeno (provavelmente duas ou três chaves, mas o caso de uso comum era apenas uma entrada). Mas o código era lento. Estamos falando de pelo menos três ordens de magnitude mais lentas que o mesmo código reescrito para usar um mapa de chaves de índice duplo [index] => data [index]. E provavelmente era mais. Como algumas operações que estavam demorando alguns minutos para serem executadas, começaram a ser concluídas em milissegundos. \
fonte
simple and clear code is better than creative code
- isso eu gosto muito, mas estou ficando um pouco confuso na última seção depoisdog slow
, talvez isso possa ser confuso para os outros também?m := map[string]interface{} { a: 42, b: "stuff" }
e, em seguida, em outra função iterando através dele:for key, val := range m { code here }
Depois de mudar para um sistema de duas fatiaskeys = []string{ "a", "b" }, data = []interface{}{ 42, "stuff" }
:, e depois iterar como se asfor i, key := range keys { val := data[i] ; code here }
coisas acelerassem 1000 vezes.As frases de linha, embora evitadas pelos criadores, têm seu lugar.
Este resolve o problema de avaliação lenta, permitindo que você, opcionalmente, passe as funções a serem avaliadas, se necessário:
Resultado
interface{}
para satisfazer a operação de conversão interna.c
.A solução independente aqui também é boa, mas pode ser menos clara para alguns usos.
fonte