Julia: Revisão de consistência de API

Criado em 2 fev. 2017  ·  131Comentários  ·  Fonte: JuliaLang/julia

Estou começando isso como um lugar para deixar notas sobre coisas a serem consideradas ao verificar a consistência da API no Julia 1.0.

  • [x] Priorização da convenção. Listar e priorizar nossas convenções do que vem primeiro em termos de argumentos de função para blocos do, argumentos de IO para funções que imprimem, saídas para funções no local etc. (https://github.com/JuliaLang/julia/issues/ 19150).

  • [] Argumentos posicionais vs palavras-chave. Há muito tempo não tínhamos argumentos de palavra-chave. Às vezes, eles ainda são evitados por questões de desempenho. Devemos fazer essa escolha com base no que constitui a melhor API, não nesse tipo de bagagem histórica (problemas de desempenho de palavras-chave também devem ser resolvidos para que isso não seja mais levado em consideração).

  • [] Ferramentas de metaprogramação. Temos várias ferramentas como @code_xxx que são combinadas com funções subjacentes como code_xxx . Eles devem se comportar de forma consistente: assinaturas semelhantes, se houver funções com assinaturas semelhantes, certifique-se de que tenham versões de macro semelhantes. O ideal é que todos eles retornem valores, em vez de alguns retornando valores e outros imprimindo resultados, embora isso possa ser difícil para coisas como código LLVM e código assembly.

  • Equivalência de nome de arquivo []

  • [] APIs de redutores. Certifique-se de que os redutores tenham comportamentos consistentes - todos usam uma função de mapa antes da redução; argumentos de dimensão congruente, etc.

  • [] Argumentos de dimensão. Tratamento consistente de argumentos de entrada "calcular através desta [estas] dimensão [s]", quais tipos são permitidos etc, considere se fazer isso como argumentos de palavra-chave pode ser desejado.

  • [] Pares mutantes / não mutantes. Verifique se as funções não mutantes estão emparelhadas com funções mutantes onde faz sentido e vice-versa.

  • [] Tupla vs. vararg. Verifique se há consistência geral entre as funções tomarem uma tupla como o último argumento ou um vararg.

  • [] Uniões vs. anuláveis ​​vs. erros. Regras consistentes sobre quando as funções devem lançar erros e quando devem retornar Nullables ou Unions (por exemplo, parse / tryparse, match, etc.).

  • [] Apoie geradores o mais amplamente possível. Certifique-se de que qualquer função que possa funcionar sensatamente com geradores o faça. Já estamos muito bem sobre isso, mas acho que perdemos alguns.

  • [] Seleção do tipo de saída. Seja consistente sobre se as APIs de "tipo de saída" devem ser em termos de tipo de elemento ou tipo geral de contêiner (ref # 11557 e # 16740).

  • [x] Escolha um nome. Existem algumas funções / operadores com aliases. Acho que isso é adequado nos casos em que um dos nomes não é ASCII e a versão ASCII é fornecida para que as pessoas ainda possam escrever código ASCII puro, mas também há casos como <: que é um apelido para issubtype onde ambos os nomes são ASCII. Devíamos escolher um e descontinuar o outro. Substituímos is em favor de === e devemos fazer o mesmo aqui.

  • [] Consistência com DataStructures . Está um pouco além do escopo do Base Julia, mas devemos ter certeza de que todas as coleções em DataStructures têm APIs consistentes com aquelas fornecidas pelo Base. A conexão na outra direção é que alguns desses tipos podem informar como acabamos projetando as APIs no Base, já que queremos que elas se estendam de maneira uniforme e consistente.

  • [] NaNs vs. DomainErrors. Consulte https://github.com/JuliaLang/julia/issues/5234 - tenha uma política para quando fazer isso e certifique-se de que seja seguida de forma consistente.

  • [] Gerador de coleção <=>. Às vezes você quer uma coleção, às vezes quer um gerador. Devemos examinar todas as nossas APIs e nos certificar de que haja uma opção para ambas quando fizer sentido. Era uma vez, havia uma convenção para usar um nome em maiúsculas para a versão do gerador e um nome em minúsculas para a versão que está ansiosa e retorna uma nova coleção. Mas ninguém nunca prestou atenção a isso, então talvez precisemos de uma nova convenção.

  • [] Funções de ordem superior em associativos. Atualmente, algumas funções de ordem superior iteram sobre coleções associativas com assinatura (k,v) - por exemplo, map , filter . Outros iteram sobre pares, ou seja, com a assinatura kv , exigindo que o corpo desestruture explicitamente o par em k e v - por exemplo, all , any . Isso deve ser revisado e tornado consistente.

  • [x] Converter vs. construir. Permita a conversão quando apropriado. Por exemplo, houve vários problemas / dúvidas sobre convert(String, 'x') . Em geral, a conversão é apropriada quando há uma única transformação canônica. A conversão de strings em números em geral não é apropriada porque existem muitas maneiras textuais de representar números, então precisamos analisar em vez disso, com opções. Há uma única maneira canônica de representar os números de versão como strings, portanto, podemos convertê-los. Devemos aplicar essa lógica cuidadosa e universalmente.

  • [] Revise a integridade da API de coleções. Devemos olhar para as funções de biblioteca padrão para coleções fornecidas por outras linguagens e ter certeza de que temos uma maneira de expressar as operações comuns que elas possuem. Por exemplo, não temos uma função flatten ou uma função concat . Provavelmente deveríamos.

  • [] Auditoria de sublinhado.

deprecation

Comentários muito úteis

Auditoria de sublinhado

A seguir está uma análise de todos os símbolos exportados do Base que contêm sublinhados, não são reprovados e não são macros de string. O principal a ser observado aqui é que esses são apenas nomes exportados; isso não inclui nomes não divulgados que pedimos às pessoas que chamem de qualificados.

Separei as coisas por categoria. Esperançosamente, isso é mais útil do que irritante.

Reflexão

Temos as seguintes macros com funções correspondentes:

  • [] @code_llvm , code_llvm
  • [] @code_lowered , code_lowered
  • [] @code_native , code_native
  • [] @code_typed , code_typed
  • [] @code_warntype , code_warntype

Qualquer alteração aplicada às macros, se houver, deve ser aplicada de forma semelhante às funções.

  • [x] module_name -> nameof (# 25622)
  • [x] module_parent -> parentmodule (# 25629, consulte # 25436 para uma tentativa anterior de renomeação)
  • [x] method_exists -> hasmethod (# 25615)
  • [x] object_id -> objectid (# 25615)
  • [] pointer_from_objref

pointer_from_objref talvez pudesse usar um nome mais descritivo, talvez algo como address ?

Aliases para interoperabilidade C

Os aliases de tipo contendo sublinhados são C_NULL , Cintmax_t , Cptrdiff_t , Csize_t , Cssize_t , Cuintmax_t e Cwchar_t . Aqueles que terminam em _t devem permanecer, pois são nomeados para serem consistentes com seus tipos C correspondentes.

C_NULL é o estranho aqui, sendo o único alias C contendo um sublinhado que não é espelhado em C (já que em C isso é apenas NULL ). Poderíamos considerar isso CNULL .

  • [] C_NULL

Contando bits

  • [] count_ones
  • [] count_zeros
  • [] trailing_ones
  • [] trailing_zeros
  • [] leading_ones
  • [] leading_zeros

Para uma discussão sobre como renomeá-los, consulte # 23531. Eu defendo a remoção dos sublinhados para estes, bem como algumas das substituições propostas nesse PR. Acho que deve ser reconsiderado.

Operações inseguras

  • [] unsafe_copyto!
  • [] unsafe_load
  • [] unsafe_pointer_to_objref
  • [] unsafe_read
  • [] unsafe_store!
  • [] unsafe_string
  • [] unsafe_trunc
  • [] unsafe_wrap
  • [] unsafe_write

Provavelmente não há problema em mantê-los como estão; a feiura do sublinhado ressalta ainda mais sua insegurança.

Indexando

  • [] broadcast_getindex
  • [] broadcast_setindex!
  • [] to_indices

Aparentemente, broadcast_getindex e broadcast_setindex! existem. Eu não entendo o que eles fazem. Talvez eles pudessem usar um nome mais descritivo?

Curiosamente, a versão de índice único de to_indices , Base.to_index , não é exportada.

Traços

  • [] catch_backtrace
  • [x] catch_stacktrace -> stacktrace(catch_backtrace()) (# 25615)

Presumivelmente, esses são os catch equivalentes em bloco de backtrace e stacktrace , respectivamente.

Tarefas, processos e sinais

  • [] current_task
  • [] task_local_storage
  • [] disable_sigint
  • [] reenable_sigint
  • [] process_exited
  • [] process_running

Streams

  • [] redirect_stderr
  • [] redirect_stdin
  • [] redirect_stdout
  • [x] nb_available -> bytesavailable (# 25634)

Seria bom ter uma função de redirecionamento IO -> IO mais geral na qual todos eles pudessem ser combinados, por exemplo, redirect(STDOUT, io) , removendo assim tanto sublinhados quanto exportações.

Promoção

  • [] promote_rule
  • [] promote_shape
  • [] promote_type

Veja # 23999 para uma discussão relevante sobre promote_rule .

Impressão

  • [x] print_with_color -> printstyled (consulte # 25522)
  • [] print_shortest (consulte # 25745)
  • [] escape_string (consulte # 25620)
  • [] unescape_string

escape_string e unescape_string são um pouco estranhos porque podem imprimir em um stream ou retornar uma string. Veja # 25620 para uma proposta para mover / renomear estes.

Carregando código

  • [] include_dependency
  • [] include_string

include_dependency . Isso é usado fora da Base? Não consigo pensar em uma situação em que você desejaria isso em vez de include em qualquer cenário típico.

include_string . Esta não é apenas uma versão oficialmente sancionada de eval(parse()) ?

Coisas que não me incomodei em categorizar

  • [x] gc_enable -> GC.enable (# 25616)
  • [] get_zero_subnormals
  • [] set_zero_subnormals
  • [] time_ns

get_zero_subnormals e set_zero_subnormals poderiam ser usados ​​com nomes mais descritivos. Eles precisam ser exportados?

Todos 131 comentários

Pedimos desculpas se este não for o lugar apropriado para mencionar isso, mas seria bom ser mais consistente com sublinhados em nomes de função daqui para frente.

Não, este é um bom lugar para isso. E sim, devemos nos esforçar para eliminar todos os nomes onde os sublinhados são necessários :)

  • tratamento consistente de argumentos de entrada "calcular através desta [estas] dimensão [s]", quais tipos são permitidos etc, considere se fazer isso como argumentos de palavra-chave pode ser desejado
  • listar e priorizar nossas convenções do que vem primeiro em termos de argumentos de função para blocos do, argumentos IO para funções que imprimem, saídas para funções no local, etc (editar: pensei que já poderia haver um aberto para isso)

Para o segundo ponto de @tkelman , consulte https://github.com/JuliaLang/julia/issues/19150

Também houve um Julep recente sobre a API para find e funções relacionadas: https://github.com/JuliaLang/Juleps/blob/master/Find.md

Devemos descontinuar put! e take! nos canais (e talvez fazer o mesmo para os futuros), uma vez que temos push! e shift! neles? Apenas sugerindo a remoção de 2 palavras redundantes na API.

Suspeito que shift! seja amigável. Um candidato é fetch! nós já temos fetch que é a versão não mutante de take!

ref # 13538 # 12469

@amitmurthy @malmaud

Editar: faria até sentido reutilizar send e recv nos canais. (Estou surpreso que eles sejam usados ​​apenas para UDPSockets no momento)

+1 para substituir put! / take! por push! / fetch!

Adicionarei a renomeação de @inferred a @test_inferred .

Verifique novamente se as especializações são consistentes com as funções mais genéricas, ou seja, não algo como # 20233.

Revise todas as funções exportadas para verificar se alguma pode ser eliminada, substituindo-as por envio múltiplo, por exemplo, print_with_color

O emparelhamento típico é push! e shift! ao trabalhar com uma estrutura de dados do tipo fila.

Se não formos usar o emparelhamento de nomes típico para este tipo de estrutura de dados porque estamos preocupados que a operação acarrete sobrecarga de comunicação que não é transmitida adequadamente por esses nomes, então não acho que push! faz sentido. send e recv realmente podem ser melhores.

Verifique novamente se há consistência geral entre as funções tomarem uma tupla como o último argumento ou um vararg.

Talvez muito grande para este problema, mas seria bom ter regras consistentes sobre quando as funções devem lançar erros e quando devem retornar Nullable s ou Union s (por exemplo, parse / tryparse , match , etc.)

Nenhum problema muito grande, @simonbyrne - esta é a lista de lavanderia.

Btw: isso não é realmente para mudanças específicas (por exemplo, renomear funções específicas) - é mais sobre tipos de coisas que podemos revisar. Para alterações propostas específicas, basta abrir um exemplar propondo essa alteração.

Temos muitas ferramentas como @code_xxx que são emparelhadas com funções subjacentes como code_xxx

Não tenho certeza se é disso que você está falando, mas consulte CreateMacrosFrom.jl

  • Se APIs de "tipo de saída" devem ser em termos de tipo de elemento ou tipo geral de contêiner (ref # 11557 e # 16740)
  • Documente todas as funções exportadas (incluindo doctests)

Documente todas as funções exportadas (incluindo doctests)

se isso faz parte disso, então talvez também: lembre-se de rotular seus testes com o número do problema / pr. Isso torna muito mais fácil entender por que esse teste existe. Eu sei como o git blame funciona, mas ao adicionar conjuntos de testes (só para dar um exemplo), às vezes é um pouco um mistério o que está sendo testado e seria ótimo se o número do problema / pr sempre estivesse lá.

@dpsanders : e macros exportadas! por exemplo, @fastmath não tem docstring.

Isso é muito pequeno, mas as funções string e Symbol fazem quase a mesma coisa e têm capitalização diferente. Acho que symbol faria mais sentido.

@amellnik A diferença é que Symbol é um construtor de tipo e string é uma função regular. O IIRC tínhamos symbol mas foi substituído em favor do construtor de tipo. Não estou convencido de que uma mudança seja necessária para isso, mas acho que devemos usar o construtor String no lugar de string .

se houver alguma coisa, acho que devemos usar o construtor String no lugar da string.

Não, são funções diferentes e não devem ser mescladas

julia> String(UInt8[])
""

julia> string(UInt8[])
"UInt8[]"

Não, são funções diferentes e não devem ser mescladas

Isso parece uma situação em que string(args...) deveria ser substituído em favor de sprint(print, args...) , então - ter string e String é confuso. Poderíamos nos especializar em sprint(::typeof(print), args...) para recuperar qualquer desempenho perdido. Junto com essas linhas, também pode fazer sentido suspender repr(x) por sprint(showall, args...) .

Isso parece normal, embora chamar string para transformar algo em uma string pareça bastante normal ....

chamar string para transformar algo em uma string parece bastante padrão

Sim, mas é aí que entra a desconexão entre String e string .

sprint(print, ...) parece redundante. Se nos livrarmos de string , podemos renomear sprint para string e obteremos string(print, foo) e string(showall, foo) que parece bom em minha opinião .

Este pode ser um caso em que a consistência é superestimada. Eu acho que é bom ter string(x) para "apenas me dê uma representação de string de x". Se for mais complicado do que isso, por exemplo, exigir que você especifique qual função de impressão usar, então usar outro nome como sprint faz sentido.

Também não haveria problema em renomear String(UInt8[]) para outra coisa e usar String vez de string . string nos dá um pouco mais de flexibilidade no futuro para alterar o tipo de string que retornamos, mas isso não parece provável de acontecer.

reinterpret(String, ::Vector{UInt8} faz algum sentido ou é um trocadilho com reinterpret ?

Isso parece fazer sentido.

Um problema é que esta função às vezes está copiando, então esse nome pode ser um tanto enganoso.

É verdade, mas as strings devem ser imutáveis, então provavelmente podemos nos safar com isso.

Também existe um método String(::IOBuffer) , mas parece que ele pode ser descontinuado para readstring .

Também pensei em sua proposta de alteração de API, mas a interface de string(a, b...) é que ela codifica e concatena seus argumentos, e isso seria uma exceção irritante para primeiros argumentos que podem ser chamados. Se removermos a concatenação de string , ela poderá funcionar.

Sim combinado; consistência e evitar pegadinhas é o mais importante.

Observando os problemas # 18326 e # 3893 na categoria "argumentos de dimensão".

Se eu puder acrescentar outro item: certificar-se de que o comportamento dos recipientes de mutáveis ​​seja documentado e consistente.

@ JaredCrean2 : você pode explicar o que você quer dizer com isso?

Eu certamente espero que não envolva fazer muitas "cópias defensivas".

Por exemplo, se eu tenho uma matriz de tipos mutáveis ​​e chamo sort nela, a matriz retornada aponta para os mesmos objetos da matriz de entrada ou copia os objetos e faz com que a matriz retornada aponte para eles?

Os mesmos objetos. Tenho certeza de que todos os nossos métodos de classificação, getindex, filtragem, pesquisa, etc. de coleção seguem esta regra, não?

Não acho que falte clareza ou consistência nesse ponto - são sempre os mesmos objetos.

Na verdade, acho que a única função padrão em que esse não é o caso é deepcopy onde o objetivo é obter todos os objetos novos.

Isso está documentado em algum lugar?

Não - poderíamos, mas não tenho certeza de onde seria melhor documentá-lo. Por que as funções fariam cópias desnecessariamente? De onde você teve a impressão de que sim?

Olá. Eu não vi nenhum comentário sobre serialização de dados.

Mais cedo ou mais tarde, os programas julia serão escritos e executados publicamente, os dados começarão a estratificar às vezes, durante anos. Serialização de dados, por exemplo. a cadeia: objeto para bytes orientado por tipo (talvez sobre json ou ...) deve ser construída para ser resistente ao tempo. Pensar em versionamento semântico e API da web também pode contar.

Podemos esperar que a serialização dos dados do usuário fique próxima a https://github.com/JuliaLang/julia/blob/v0.5.1/base/serialize.jl ?

Por que as funções fariam cópias desnecessariamente? De onde você teve a impressão de que sim?

Eu não sei se eles fazem ou não. Pelo que eu posso dizer, o comportamento é indefinido. Pelo comentário de @JeffBezanson , há pessoas que defendem a realização de cópias defensivas, às quais ele se opõe. Portanto, a documentação deve abordar a questão das cópias defensivas em algum lugar.

Você parece estar sugerindo algum tipo de princípio de ação mínima, mas dependendo dos detalhes do algoritmo, o que é a "ação mínima" fica ambíguo. Para obter consistência na API, acho que orientações mais específicas são necessárias.

@ o314 : este é um problema de revisão de consistência da API, não tenho certeza de como a serialização está relacionada.

@ JaredCrean2 : se o objeto de nível superior é copiado ou não, certamente precisa ser documentado. O que estou dizendo é que objetos mais profundos nunca são copiados, exceto por deepcopy (obviamente).

O que estou dizendo é que objetos mais profundos nunca são copiados, exceto por deepcopy (obviamente).

Houve uma discussão recente sobre isso no contexto de copy para alguns dos wrappers de array, por exemplo, SubArray e SparseMatrixCSC mas também Symmetric , LowerTriangular . Parece-me que, de acordo com a política mencionada acima, copy seria um noop para esses tipos de invólucro. A política que você mencionou é o nível certo de abstração aqui? Por exemplo, eu acho que isso implica que se Array s foram implementados em Julia (envolvendo um buffer), o comportamento de copy em Array s deve então mudar para um noop.

Se a convenção é que objetos mais profundos nunca são copiados, então tudo o que resta é documentá-los. A documentação é uma parte realmente importante de uma API. Esse comportamento pode parecer óbvio para você (talvez porque você escreveu partes do código), mas de uma perspectiva externa não é tão óbvio.

Edit: não vi a postagem de Andreas. Essa é uma consideração interessante.

@StefanKarpinski Eu concordo com seu ponto.
E todos os tópicos principais invocados aqui são muito bons e inteligentes.

Mas às vezes tenho um pouco de medo em relação ao equilíbrio entre processo e dados em Julia:

Podemos chamar fortran ou c facilmente com certeza,
Mas o código será implantado com a mesma facilidade em um datacenter moderno, por exemplo. aws lambda com sua função como padrão de serviço. o código será facilmente chamado pela Internet, API aberta?

Às vezes, é necessário reduzir a carga funcional para escalar (sem genérico, sem vaargs na assinatura da função, sem alta ordem na API pública) e vincular mais sistematicamente os dados (esquema json / openapi).

Eu vi algumas bibliotecas python muito boas afundando assim e isso é uma pena.

Acho que é um ponto crucial para uma linguagem 1.0 manter os dados e funções balanceados e modulares para ser capaz de implantar facilmente na web. E, para esta função, a interface deve ser menos orientada para animais de estimação e mais para gado quando necessário.

Pode ser que esse não seja o ponto neste tópico.

@StefanKarpinski Posso ter entendido mal sua postagem. Quando você disse

se o objeto de nível superior é copiado ou não certamente precisa ser documentado

o que significa "objeto de nível superior"? Se eu tiver x::Vector{MyMutableType} , o objeto de nível superior é x ou os elementos de x ?

O objeto de nível superior se refere ao próprio x , não aos elementos de x .

@andreasnoack A noção de objeto de nível superior deve se referir à estrutura abstrata implementada, não aos detalhes de implementação.

talvez adicionar float e outras funções semelhantes que atuam tanto em tipos quanto em valores?

Revendo as notas de versão 0.6, parece estranho que iszero(A::Array{T}) seja introduzido, enquanto muitas outras funções (por exemplo, sumabs , isinteger , isnumber ) sobre matrizes são preterido em favor de all(f,A) .

Existem zero matrizes e são zero elementos em seu espaço vetorial. iszero testa genericamente se algo é o inverso aditivo, o que matrizes zero são.

Ré. consistência de sublinhados em nomes de função, aqui está uma localização atual para count_ones e count_zeros .

Parece-me que se você quiser manter a API Julia consistentemente consistente, você precisará ter algum software que permita (a) especificar quais são as regras / convenções da API, (b) realizar análise estática do código Julia para detectar desvios dessas regras / convenções e (c) oferecer sugestões. Essa ferramenta beneficiaria tanto Julia Base quanto todos os Pacotes Julia. Um novo pacote de Julia pode resolver o problema. (Língua de mau humor: a primeira coisa que este pacote deve fazer é sugerir seu próprio nome; APICheck.jl, ApiCheck.jl, API_Check.jl, APIChecker.jl, JuliaAPIChecker.jl, etc.) Eu sou bastante novo para Julia, então não quero assumir a liderança em tal coisa. No entanto, eu não me importaria de contribuir. Alguma sugestão de como fazer isso?

Adoraríamos ter isso no Lint.jl!

num2hex e hex2num (# 22031 e # 22088)

Eu também acredito que Lint.jl é o pacote certo para verificação de consistência da API Julia. Mas se formos por esse caminho, a lista original de Stefan deve ser muito mais precisa. Por exemplo, quando ele escreve "devemos ter certeza de que todas as coleções em DataStructures têm APIs consistentes", as perguntas que vêm à mente são:

  • O que torna uma estrutura de dados uma coleção? (Lista de coleções na base)
  • Quais são as funções que uma coleção DEVE suportar? (Uma planilha de funções por coleções)
  • Qual deve ser a assinatura de cada uma dessas funções?
  • As APIs fornecidas pelo Base para coleções já são realmente consistentes?

Para gerenciar tais inventários e análises, podemos querer adicionar um projeto ao repositório Julia (projeto # 8), ou ao repositório JuliaPraxis (sugerido offline pelo TotalVerb) ou ao repositório Lint. Nesse caso, precisaríamos decidir quem seria o proprietário de tal projeto, quais pessoas deveriam estar envolvidas desde o início e quem deveria tomar as decisões finais sobre o que as convenções de Julia realmente são (para fins de linting).

Mas antes de avançar mais nessas linhas, gostaria de perguntar a @StefanKarpinski : quais são suas idéias sobre como trabalhar sua lista de problemas de consistência da API Julia?

Concordo que especificar isso especificamente é uma boa ideia. Descobrir o que essa lista deve ser faz parte do trabalho aqui - se você quiser dar uma olhada nisso, seria ótimo.

Nós realmente precisamos de um Base.datatype_module e um Base.function_module?

Uma função unificada "módulo" (talvez getmodule) despachando em tipo de dados e função parece mais consistente para mim.

E os sublinhados em @code_typed e amigos?

Essa é uma boa oportunidade para refatorar (a razão declarada para a proibição do sublinhado). Você poderia ter uma macro @code com o primeiro argumento sendo o tipo de código que você deseja.

( @bramtayl , lembre-se de colocar crases em torno das macros, pois isso envia um ping para o "código" do usuário do github, se não; @code )

FWIW, o preenchimento da guia só funciona com nomes de funções. Ser capaz de fazer @code_<TAB> é bom ....

Se a refatoração for considerada, mas rejeitada, então se há ou não sublinhados é discutível, porque o único ponto da proibição do sublinhado é encorajar a refatoração. Na verdade, nesse caso, parece que sublinhados devem ser incentivados para tornar a linguagem mais clara

Auditoria de sublinhado.

contra-proposta: ainda auditamos esses nomes, mas, em vez disso, adicionamos mais sublinhados onde isso tornaria o código mais fácil de ler (codetyped vs code_typed, isos2 vs is_os2).

Não sou absolutista quanto a isso. Acho que code_typed está bem, é útil completar a tabulação como

Para mim, parece que o navio navegou ao adicionar mais sublinhados, já que teríamos que desativar basicamente metade da Base. Como exemplo, existem 74 funções de predicado que começam com is e apenas 6 que começam com is_ . O que faz mais sentido, desaprovar 6 ou 74?

Ok, há vários objetivos conflitantes aqui:

1) Tornando os nomes mais legíveis
2) Reduzindo a rotatividade de código
3) Encorajar a refatoração

A eliminação de sublinhados pela colisão de palavras falha em todas as três frentes.

Que show métodos que aceitam um fluxo não são ! terminados parece inconsistente com a convenção usual? Ref. https://github.com/JuliaLang/julia/pull/22604/commits/db9d70a279763ded5088016d9c3d4439a49e3fca#r125115063. Melhor! (Editar: suponho que corresponda aos métodos write que aceitam um fluxo.)

Existem inconsistências com a API de características. Alguns traços são calculados chamando o traço como
TypeArithmetic(Float64)
enquanto outras, esta função deve ser escrita em letras minúsculas:
iteratorsize(Vector{Float64})

Considere renomear size -> shape (xref # 22665)

Array{T,1}() provavelmente também deve ser descontinuado:

julia> Array{Int,1}()                                                                                                                  
0-element Array{Int64,1}                                                                                                               

julia> Array{Int,2}()                                                                                                                  
WARNING: Matrix{T}() is deprecated, use Matrix{T}(0, 0) instead.                                                                       

Tenho pensado em coleções. Basicamente, temos três tipos:

  • Coleções simples semelhantes a conjuntos (ou bolsas), que contêm apenas alguns valores.
  • Coleções do tipo array, que adicionam um índice ao lado dos dados.
  • Coleções do tipo dict, que têm índices como parte dos dados, ou seja, k=>v pares.

Tornei-me cético em relação ao comportamento do tipo dict. O canário na mina de carvão é map , onde operar em pares de valores-chave não é natural, já que você geralmente não deseja alterar o conjunto de chaves. Também é possível que arrays e dicts implementem a mesma interface:

  • keys corresponde a eachindex
  • mapindexed e filterindexed seriam úteis para dicts e arrays. Eles são como mapear e filtrar, mas também passam para sua função o índice do item em questão.
  • Arrays e dicts (e tuplas nomeadas, e talvez outras coisas) podem usar um iterador pairs , que é basicamente um atalho para zip(keys(c), values(c)) .

Considere renomear ind2sub e sub2ind , que são matlabismos aparentemente, e que têm um nome não juliano e estranho para um usuário não matlab. Os nomes possíveis seriam indice e linearindice respectivamente. Não me atrevi a fazer uma RP porque não tenho certeza do que as pessoas pensam sobre isso, mas farei se houver apoio.

Mesma coisa com rad2deg e deg2rad .

Ref. # 22791 ( select -> partialsort ). Melhor!

Uma coisa que não vi aqui: os argumentos posicionais opcionais vêm primeiro ou por último? Às vezes, os argumentos posicionais opcionais vão primeiro, como em sum(f, itr) e rand([rng,] ..) . Mas para outro lugar eles vão por último, por exemplo, em median(v[, region]) ou split(s::AbstractString[, chars]) . Às vezes, eles podem ir primeiro ou por último, mas não os dois! (Por exemplo, mean pode assumir uma função primeiro ou uma dimensão por último, mas não ambos.)

A semântica da linguagem atual força os argumentos opcionais a irem por último: você pode escrever f(a, b=1) mas não f(b=1, a) . Mas se todos os argumentos opcionais forem por último, o que acontece com os blocos convenientes do?

Se nada mais, é uma pequena verruga que a linguagem tem de definir métodos como tal, de rand.jl : shuffle!(a::AbstractVector) = shuffle!(GLOBAL_RNG, a) . A sintaxe do argumento posicional opcional deve cuidar exatamente desse caso de uso.

Talvez devesse ir em uma edição separada, mas parece possível mover os argumentos opcionais para onde você quiser. Assim, por exemplo, f(a = 1, b, c = 2) definiria f(x) = f(1, x, 2) e f(x, y) = f(x, y, 2)

xref # 22460 para uma tentativa (impopular) de habilitar argumentos padrão em qualquer posição.

Talvez renomear warn para warning (matlab também usa isso), não é grande coisa, mas pensei em mencionar?

Eu gosto de warn porque é um verbo, como throw .

Eu fiquei muito confuso com isso:

julia> f(;a=1,b=1) = a+b                                                                                                                              
f (generic function with 1 method)                                                                                                                    

julia> f(a=4,5)            # I intended to write f(a=4,b=5)                                                                                                                           
ERROR: MethodError: no method matching f(::Int64; a=4)                                                                                                
Closest candidates are:
  f(; a, b) at REPL[13]:1

Eu sugiro apenas permitir palavras-chave por último ao chamar funções, semelhante a ao definir funções de palavra-chave.

Eu sugiro apenas permitir palavras-chave por último ao chamar funções, semelhante a ao definir funções de palavra-chave.

Existem muitas APIs em que passar palavras-chave em outras posições é útil e ergonômico.

Por curiosidade, existe uma ordem de avaliação definida para argumentos posicionais e de palavras-chave? Vi alguns problemas mais antigos e https://docs.julialang.org/en/latest/manual/functions/#Evaluation -Scope-of-Default-Values-1 fala sobre escopos, mas nada que encontrei indica se, por exemplo, os argumentos são avaliados da esquerda para a direita, ou todos os argumentos posicionais são avaliados antes de todos os argumentos de palavra-chave, ou se não houver uma ordem de avaliação definida (ou outra coisa).

@yurivish , para palavras-chave, consulte os documentos (também https://github.com/JuliaLang/julia/issues/23926). Para opcionais a história é um pouco mais complicada, talvez leia aqui . (Observe, porém, que as perguntas são mais bem feitas em https://discourse.julialang.org/)

Não parece valer a pena, mas sempre me parece estranho que bits(1) retorne um String , parece que deveria ser BitVector ou Vector{Bool} .

Eu gostaria de sugerir uma revisão das tabelas de métodos que despacham em Function ou Callable . Vamos tentar ter certeza de que essas APIs são do jeito que queremos ... e que não estamos perdendo a oportunidade de permitir a reestruturação das tabelas de forma que possamos permitir a chamada de pato de qualquer objeto.

Quanto a all e any , eles parecem simples de depreciar para all(f(x) for x in xs) , que já está eta reduzido para all(Generator(f, xs)) e, portanto, não deve ter despesas indiretas.

Não tenho certeza se é o que você quis dizer, mas achei que vale a pena afirmar apenas no caso: sou totalmente contra a depreciação de qualquer API de estilo funcional para geradores. Temos any(f, x) e all(f, x) e eles são amplamente usados; -10000000 para remover esses (ou quaisquer métodos, na verdade).

Im gerador profissional. Parece um bloco de construção fundamental da programação preguiçosa e deve ser exportado. all(Generator(f, xs)) às vezes mais conveniente para funções definidas do que all(f(x) for x in xs) . Também +10000000 para restaurar o equilíbrio

Eu prefiro ter menos sintaxe aqui, se possível. Se for fácil, legível e eficaz expressar a ideia de "tomar xs, aplicar f a tudo e retornar verdadeiro se todos forem verdadeiros", então por que deveríamos colocá-lo em um verbo?

Se a preocupação for o substantivo desnecessário x em f(x) for x in xs , então a sugestão de @bramtayl de exportar Generator (talvez usando um nome melhor como Map ?) faz sentido.

Coisas como all(isnull, x) são bem mais simples do que all(isnull(v) for v in x) . Substituímos a função allnull de NullableArrays em favor de all(isnull, x) ; se essa sintaxe fosse embora, provavelmente teríamos que reintroduzi-la.

Que tal renomear strwidth para stringwidth (acho que esta é a única função de manipulação de string exportada que abreviou string para str)

Na verdade, ele foi renomeado para textwidth (https://github.com/JuliaLang/julia/pull/23667).

IMO, este problema é muito amplo para ter o marco 1.0 nele, visto que queremos obter o congelamento de recursos em breve. Podemos precisar de vários proprietários e atribuir conjuntos de funções para revisão se quisermos fazer isso.

Além disso, este é outro lugar onde o FemtoCleaner pode atualizar automaticamente muitas coisas após a 1.0, mesmo que seja bom fazer tudo certo.

apenas um comentário sobre a largura do texto:

INFO: Testing Cairo
Test Summary:   | Pass  Total
Image Surface   |    7      7
Test Summary:   | Pass  Total
Conversions     |    4      4
Test Summary:   | Pass  Total
TexLexer        |    1      1
WARNING: both Compat and Cairo export "textwidth"; uses of it in module Main must be qualified
Samples        : Error During Test
  Got an exception of type LoadError outside of a <strong i="6">@test</strong>
  LoadError: UndefVarError: textwidth not defined
  Stacktrace:
   [1] include_from_node1(::String) at .\loading.jl:576
   [2] include(::String) at .\sysimg.jl:14
   [3] macro expansion at C:\Users\appveyor\.julia\v0.6\Cairo\test\runtests.jl:86 [inlined]
   [4] macro expansion at .\test.jl:860 [inlined]
   [5] anonymous at .\<missing>:?
   [6] include_from_node1(::String) at .\loading.jl:576
   [7] include(::String) at .\sysimg.jl:14
   [8] process_options(::Base.JLOptions) at .\client.jl:305
   [9] _start() at .\client.jl:371
  while loading C:\Users\appveyor\.julia\v0.6\Cairo\samples\sample_pango_text.jl, in expression starting on line 28

A mensagem de erro parece bastante clara sobre qual é o problema? Cairo precisa estender o método base.

help?>  Base.textwidth
  No documentation found.

  Binding Base.textwidth does not exist.

julia> versioninfo()
Julia Version 0.6.0
julia> Compat.textwidth
textwidth (generic function with 2 methods)

Não tive dúvidas de que a mensagem (que recebi via travis) está OK, mas por que o Compat exporta a largura de texto se não está em 0,6?

Porque esse é o objetivo do Compat? Esta discussão está saindo muito do escopo, então eu sugiro que possamos continuar com o discurso ou com folga.

Eu sugiro mudar copy para shallowcopy e deepcopy para copy , pois recentemente demorei bastante para perceber que a cópia é uma cópia "superficial" e que função que escrevi estava alterando o array de arrays. Acho que seria muito mais intuitivo se copy fizesse uma cópia "profunda" e algo como shallowcopy fosse usado para cópias rasas? Eu sei agora quando usar deepcopy , mas acho que muitos outros usuários terão o mesmo problema.

Por favor, vamos tentar manter este problema para a consistência da API, não um monte de "coisas específicas que eu não gosto".

Associative o nome parece bastante inconsistente com todos os outros nomes de tipo em Julia.

Em primeiro lugar, os tipos geralmente não são adjetivos. O substantivo é Association , e é usado pelo menos por alguns documentos do Mathematica que encontrei.

Acho que AbstractDict seria muito mais consistente com outros tipos como AbstractArray , AbstractRange , AbstractSet e AbstractString , cada um dos quais um concreto prototípico tipifica Dict , Array , Range , Set e String .

Nossos tipos de exceção estão um pouco espalhados: alguns são nomeados FooError , outros são nomeados BarException ; alguns são exportados, a maioria não. Isso poderia usar uma passagem para consistência.

Então, o que seria preferido, FooError ou BarException ? Exportado ou não?

Para mim, BarException envolve em algum lugar um padrão de aumentar / pegar.

Eu prefiro muito, e alguns outros no mundo funcional também, use o padrão Some / None (*) onde o fluxo de controle é mais direto e previsível.

Então, +1 por FooError

(*) Some / Void ex Optional em julho # 23642.

Essas coisas ainda estão sobre a mesa devido ao congelamento de recursos? Eu gostaria especialmente de lidar com argumentos opcionais versus argumentos de palavra-chave, mas a lista de funções com vários argumentos opcionais (o caso mais claro para usar argumentos de palavra-chave) é muito longa.

Por favor dê uma olhada! Não tive a chance de examinar essas questões sistematicamente.

BTW, notei uma inconsistência na nomenclatura dos traços: temos iteratorsize , iteratoreltype , mas IndexStyle , TypeRangeStep , TypeArithmetic e TypeOrder . Parece que as variantes do CamelCase são mais numerosas e mais recentes, então talvez devêssemos adotar essa convenção em todos os lugares?

Isso definitivamente deve ser consistente. Você quer fazer um PR?

Acho que isso deve ser corrigido como parte de https://github.com/JuliaLang/julia/pull/25356.

EDITAR: veja também https://github.com/JuliaLang/julia/issues/25440

Isso é feito principalmente ou pode ser feito em versões 1.x. Posso atualizar as caixas de seleção, mas acabamos de examiná-las na chamada de triagem e tudo, exceto o # 25395 e a auditoria de sublinhado, está concluída.

Auditoria de sublinhado

A seguir está uma análise de todos os símbolos exportados do Base que contêm sublinhados, não são reprovados e não são macros de string. O principal a ser observado aqui é que esses são apenas nomes exportados; isso não inclui nomes não divulgados que pedimos às pessoas que chamem de qualificados.

Separei as coisas por categoria. Esperançosamente, isso é mais útil do que irritante.

Reflexão

Temos as seguintes macros com funções correspondentes:

  • [] @code_llvm , code_llvm
  • [] @code_lowered , code_lowered
  • [] @code_native , code_native
  • [] @code_typed , code_typed
  • [] @code_warntype , code_warntype

Qualquer alteração aplicada às macros, se houver, deve ser aplicada de forma semelhante às funções.

  • [x] module_name -> nameof (# 25622)
  • [x] module_parent -> parentmodule (# 25629, consulte # 25436 para uma tentativa anterior de renomeação)
  • [x] method_exists -> hasmethod (# 25615)
  • [x] object_id -> objectid (# 25615)
  • [] pointer_from_objref

pointer_from_objref talvez pudesse usar um nome mais descritivo, talvez algo como address ?

Aliases para interoperabilidade C

Os aliases de tipo contendo sublinhados são C_NULL , Cintmax_t , Cptrdiff_t , Csize_t , Cssize_t , Cuintmax_t e Cwchar_t . Aqueles que terminam em _t devem permanecer, pois são nomeados para serem consistentes com seus tipos C correspondentes.

C_NULL é o estranho aqui, sendo o único alias C contendo um sublinhado que não é espelhado em C (já que em C isso é apenas NULL ). Poderíamos considerar isso CNULL .

  • [] C_NULL

Contando bits

  • [] count_ones
  • [] count_zeros
  • [] trailing_ones
  • [] trailing_zeros
  • [] leading_ones
  • [] leading_zeros

Para uma discussão sobre como renomeá-los, consulte # 23531. Eu defendo a remoção dos sublinhados para estes, bem como algumas das substituições propostas nesse PR. Acho que deve ser reconsiderado.

Operações inseguras

  • [] unsafe_copyto!
  • [] unsafe_load
  • [] unsafe_pointer_to_objref
  • [] unsafe_read
  • [] unsafe_store!
  • [] unsafe_string
  • [] unsafe_trunc
  • [] unsafe_wrap
  • [] unsafe_write

Provavelmente não há problema em mantê-los como estão; a feiura do sublinhado ressalta ainda mais sua insegurança.

Indexando

  • [] broadcast_getindex
  • [] broadcast_setindex!
  • [] to_indices

Aparentemente, broadcast_getindex e broadcast_setindex! existem. Eu não entendo o que eles fazem. Talvez eles pudessem usar um nome mais descritivo?

Curiosamente, a versão de índice único de to_indices , Base.to_index , não é exportada.

Traços

  • [] catch_backtrace
  • [x] catch_stacktrace -> stacktrace(catch_backtrace()) (# 25615)

Presumivelmente, esses são os catch equivalentes em bloco de backtrace e stacktrace , respectivamente.

Tarefas, processos e sinais

  • [] current_task
  • [] task_local_storage
  • [] disable_sigint
  • [] reenable_sigint
  • [] process_exited
  • [] process_running

Streams

  • [] redirect_stderr
  • [] redirect_stdin
  • [] redirect_stdout
  • [x] nb_available -> bytesavailable (# 25634)

Seria bom ter uma função de redirecionamento IO -> IO mais geral na qual todos eles pudessem ser combinados, por exemplo, redirect(STDOUT, io) , removendo assim tanto sublinhados quanto exportações.

Promoção

  • [] promote_rule
  • [] promote_shape
  • [] promote_type

Veja # 23999 para uma discussão relevante sobre promote_rule .

Impressão

  • [x] print_with_color -> printstyled (consulte # 25522)
  • [] print_shortest (consulte # 25745)
  • [] escape_string (consulte # 25620)
  • [] unescape_string

escape_string e unescape_string são um pouco estranhos porque podem imprimir em um stream ou retornar uma string. Veja # 25620 para uma proposta para mover / renomear estes.

Carregando código

  • [] include_dependency
  • [] include_string

include_dependency . Isso é usado fora da Base? Não consigo pensar em uma situação em que você desejaria isso em vez de include em qualquer cenário típico.

include_string . Esta não é apenas uma versão oficialmente sancionada de eval(parse()) ?

Coisas que não me incomodei em categorizar

  • [x] gc_enable -> GC.enable (# 25616)
  • [] get_zero_subnormals
  • [] set_zero_subnormals
  • [] time_ns

get_zero_subnormals e set_zero_subnormals poderiam ser usados ​​com nomes mais descritivos. Eles precisam ser exportados?

+1 para method_exists => methodexists e object_id => objectid . Também é meio bobo que catch_stacktrace exista. Ele pode ser reprovado de acordo com sua definição, stacktrace(catch_backtrace()) .

Como nos sentimos sobre a eliminação de sublinhado C_NULL ? Eu me acostumei com isso, mas também acredito que nenhum dos outros C* nomes tem um sublinhado.

Os outros nomes C são tipos, enquanto C_NULL é uma constante. Acho bom como é e segue as diretrizes de nomenclatura.

e segue as diretrizes de nomenclatura.

Como assim?

Freqüentemente, as constantes são todas maiúsculas com sublinhados - C_NULL segue isso. Como @ iamed2 disse, é um valor, não um tipo, então a convenção de nomenclatura Cfoo não se aplica necessariamente.

Por engano, pensei https://github.com/JuliaLang/julia/blob/master/doc/src/manual/variables.md#stylistic -conventions referenciadas constantes, mas isso não acontece. Provavelmente deveria.

Eu sugiro uma interface consistente e matematicamente sólida para espaços de Hilbert gerais nos quais os vetores não são Julia Arrays. Nomes de funções como vecdot , vecnorm , etc. podem ser substituídos pelos conceitos gerais de inner e norm conforme discutido em https: // github. com / JuliaLang / julia / edições / 25565.

Como eu disse algumas vezes, essa não é uma questão abrangente para as coisas que se deseja mudar.

Acredito que os únicos itens restantes sob este guarda-chuva para 1.0 são # 25501 e # 25717.

Eu gostaria de fazer algo com (get|set)_zero_subnormals mas talvez a melhor solução de curto prazo seja apenas desexportá-los.

Algo que provavelmente deveria ser revisado é como os números são tratados no contexto de operações de coleção como map e collect . Foi apontado que o primeiro retorna um escalar, mas o último retorna um array 0D.

Esta página foi útil?
0 / 5 - 0 avaliações