Por que não anotar parâmetros de função?


28

Para responder a essa pergunta, vamos supor que o custo da ambiguidade na mente de um programador seja muito mais caro do que algumas teclas extras.

Dado isso, por que eu permitiria que meus colegas de equipe não anotassem seus parâmetros de função? Tome o código a seguir como um exemplo do que poderia ser um pedaço de código muito mais complexo:

let foo x y = x + y

Agora, um rápido exame da dica mostrará que o F # determinou que você pretendia que x e y fossem ints. Se é isso que você pretendia, tudo está bem. Mas eu não sei se é isso que você pretendia. E se você tivesse criado esse código para concatenar duas strings juntas? Ou então, se eu acho que você provavelmente pretendia adicionar duplas? Ou, se eu simplesmente não quiser passar o mouse sobre cada parâmetro de função para determinar seu tipo?

Agora tome isso como um exemplo:

let foo x y = "result: " + x + y

O F # agora supõe que você provavelmente pretendeu concatenar cadeias de caracteres, então x e y são definidos como cadeias de caracteres. No entanto, como o pobre coitado que está mantendo seu código, eu posso olhar para isso e me perguntar se talvez você pretendesse adicionar x e y (ints) juntos e depois anexar o resultado a uma string para fins de interface do usuário.

Certamente, para exemplos tão simples, é possível deixar para lá, mas por que não impor uma política de anotação explícita de tipo?

let foo (x:string) (y:string) = "result: " + x + y

Que mal há em ser inequívoco? Claro, um programador poderia escolher os tipos errados para o que eles estão tentando fazer, mas pelo menos eu sei que eles pretendiam, que não era apenas uma supervisão.

Essa é uma pergunta séria ... Ainda sou muito novo no F # e estou abrindo caminho para a minha empresa. Os padrões que eu adotar provavelmente serão a base para toda a futura codificação de F #, incorporada na infinita cópia de pasta que tenho certeza de que permeará a cultura nos próximos anos.

Então ... existe algo especial na inferência de tipo do F # que o torna um recurso valioso para se agarrar, anotando apenas quando necessário? Ou os especialistas em F # têm o hábito de anotar seus parâmetros para aplicativos não triviais?


A versão anotada é menos geral. É pela mesma razão que você preferencialmente usa IEnumerable <> em assinaturas e não SortedSet <>.
22413 Patrick

2
@ Patrick - Não, não é, porque F # está inferindo o tipo de string. Não alterei a assinatura da função, apenas a tornei explícita.
JDB #

11
@ Patrick - E mesmo que fosse verdade, você pode explicar por que isso é uma coisa ruim? Talvez a assinatura deva ser menos geral, se é isso que o programador pretendia. Talvez a assinatura mais geral esteja realmente causando problemas, mas não tenho certeza de quão específico o programador pretendia ser sem uma grande quantidade de pesquisa.
JDB

11
Eu acho justo dizer que, em linguagens funcionais, você prefere generalidade e anota no caso mais específico; por exemplo, quando você deseja um melhor desempenho e pode obtê-lo usando dicas de tipo. O uso de funções anotadas em todos os lugares exigiria que você escrevesse sobrecargas para cada tipo específico, o que pode não ser garantido no caso geral.
Robert Harvey

Respostas:


30

Eu não uso o F #, mas no Haskell é considerado uma boa forma anotar (pelo menos) definições de nível superior e, às vezes, definições locais, mesmo que o idioma tenha inferência generalizada de tipo. Isso ocorre por alguns motivos:

  • Leitura
    Quando você quer saber como usar uma função, é incrivelmente útil ter a assinatura de tipo disponível. Você pode simplesmente lê-lo, em vez de tentar inferir você mesmo ou confiar em ferramentas para fazer isso por você.

  • Refatoração
    Quando você deseja alterar uma função, ter uma assinatura explícita garante a você que suas transformações preservam a intenção do código original. Em uma linguagem deduzida por tipo, você pode achar que o código altamente polimórfico verificará tipicamente, mas não fará o que você pretendia. A assinatura de tipo é uma “barreira” que concretiza informações de tipo em uma interface.

  • Desempenho
    No Haskell, o tipo inferido de uma função pode estar sobrecarregado (por meio de classes de tipos), o que pode implicar um despacho em tempo de execução. Para tipos numéricos, o tipo padrão é um número inteiro de precisão arbitrária. Se você não precisar da generalidade completa desses recursos, poderá melhorar o desempenho, especializando a função para o tipo específico de que precisa.

Para definições locais, letvariáveis ​​de saída e parâmetros formais para lambdas, acho que as assinaturas de tipo geralmente custam mais em código do que o valor que elas adicionariam. Portanto, na revisão de código, sugiro que você insista em assinaturas para definições de nível superior e apenas peça anotações criteriosas em outros lugares.


3
Isso soa como uma resposta muito razoável e bem equilibrada.
JDB

11
Concordo que ter a definição com os tipos definidos explicitamente pode ajudar na refatoração, mas acho que o teste de unidade também pode resolver esse problema e porque acredito que o teste de unidade deve ser usado com programação funcional para garantir que uma função funcione conforme o planejado antes de usar o função com outra função, isso me permite deixar os tipos fora da definição e ter a confiança de que, se eu fizer uma alteração de quebra, ela será capturada. Exemplo
Guy Coder

4
No Haskell, é considerado correto anotar suas funções, mas acredito que o F # idiomático e o OCaml tendem a omitir anotações, a menos que seja necessário remover a ambiguidade. Isso não quer dizer que seja prejudicial (embora a sintaxe para fazer anotações em F # seja mais feia do que em Haskell).
KChaloux

4
@KChaloux No OCaml, já existem anotações de tipo no .mliarquivo interface ( ) (se você escrever uma que é altamente recomendável. As anotações de tipo tendem a ser omitidas das definições porque seriam redundantes com a interface.
Gilles ' Então, pare de ser mau ''

11
@Gilles @KChaloux de fato, o mesmo nos .fsiarquivos de assinatura do F # ( ), com uma ressalva: o F # não usa arquivos de assinatura para inferência de tipo; portanto, se algo na implementação for ambíguo, você ainda precisará anotá-lo novamente. books.google.ca/…
Yawar Amin

1

Jon deu uma resposta razoável que não vou repetir aqui. No entanto, mostrarei uma opção que pode satisfazer suas necessidades e, no processo, você verá um tipo de resposta diferente de sim / não.

Ultimamente, tenho trabalhado com a análise usando combinadores de analisadores. Se você conhece a análise, sabe que normalmente usa um lexer na primeira fase e um analisador na segunda fase. O lexer converte texto em tokens e o analisador converte tokens em um AST. Agora, com o F # sendo uma linguagem funcional e combinadores projetados para serem combinados, os combinadores de analisadores são projetados para usar as mesmas funções no lexer e no analisador; no entanto, se você definir o tipo para as funções do combinador de analisador, você só poderá usá-los para lex ou analisar e não ambos.

Por exemplo:

/// Parser that requires a specific item.

// a (tok : 'a) : ('a list -> 'a * 'a list)                     // generic
// a (tok : string) : (string list -> string * string list)     // string
// a (tok : token)  : (token list  -> token  * token list)      // token

ou

/// Parses iterated left-associated binary operator.


// leftbin (prs : 'a -> 'b * 'c) (sep : 'c -> 'd * 'a) (cons : 'd -> 'b -> 'b -> 'b) (err : string) : ('a -> 'b * 'c)                                                                                    // generic
// leftbin (prs : string list -> string * string list) (sep : string list -> string * string list) (cons : string -> string -> string -> string) (err : string) : (string list -> string * string list)  // string
// leftbin (prs : token list  -> token  * token list)  (sep : token list  -> token  * token list)  (cons : token  -> token  -> token  -> token)  (err : string) : (token list  -> token  * token list)   // token

Como o código é protegido por direitos autorais, não o incluirei aqui, mas está disponível no Github . Não copie aqui.

Para que as funções funcionem, elas devem ser deixadas com os parâmetros genéricos, mas eu incluo comentários que mostram os tipos inferidos, dependendo do uso das funções. Isso facilita o entendimento da função para manutenção, deixando a função genérica para uso.


11
Por que você não gravaria a versão genérica na função? Isso não funcionaria?
precisa

@ Rick Não entendo sua pergunta. Você quer dizer que eu deixei os tipos fora da definição da função, mas poderia ter adicionado os tipos genéricos às definições da função, pois os tipos genéricos não alterariam o significado da função? Nesse caso, o motivo é mais uma preferência pessoal. Quando eu comecei com o F #, eu os adicionei. Quando comecei a trabalhar com usuários mais avançados de F #, eles preferiram que fossem deixados como comentários, porque era mais fácil modificar o código sem as assinaturas existentes. ...
Guy Coder

A longo prazo, como mostro a eles como os comentários funcionaram para todos, uma vez que o código estava funcionando. Quando começo a escrever uma função, insiro os tipos. Depois que a função está funcionando, movo a assinatura de tipo para um comentário e removo o maior número possível de tipos. Algumas vezes com F # você precisa deixar o tipo para ajudar na inferencia. Por um tempo, eu estava experimentando criar o comentário com let e = para que você pudesse descomentar a linha de teste e comentar novamente quando terminar, mas eles pareciam bobos e adicionar uma permissão e = não é tão difícil.
precisa

Se esses comentários responderem ao que você está perguntando, avise-me para que eu possa movê-los para a resposta.
Guy Coder

2
Então, quando você modifica o código, não modifica os comentários, levando a documentação desatualizada? Isso não me parece uma vantagem. E realmente não vejo como um comentário bagunçado é melhor do que um código bagunçado. Para mim, parece que essa abordagem combina as desvantagens das duas opções.
precisa

-8

O número de bugs é diretamente proporcional ao número de caracteres em um programa!

Isso geralmente é declarado como o número de erros proporcional às linhas de código. Mas ter menos linhas com a mesma quantidade de código gera a mesma quantidade de bugs.

Portanto, embora seja bom ser explícito, qualquer parâmetro extra digitado pode levar a erros.


5
“O número de bugs é diretamente proporcional ao número de caracteres em um programa!” Portanto, todos devemos mudar para o APL ? Ser muito conciso é um problema, assim como ser muito detalhado.
svick

5
" Para tornar essa pergunta responsável, vamos supor que o custo da ambiguidade na mente de um programador seja muito mais caro do que alguns pressionamentos de tecla extras. "
JDB
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.