Julia: Revisión de consistencia de API

Creado en 2 feb. 2017  ·  131Comentarios  ·  Fuente: JuliaLang/julia

Estoy comenzando esto como un lugar para dejar notas sobre las cosas que se deben tener en cuenta al verificar la coherencia de la API en Julia 1.0.

  • [x] Priorización de la Convención. Enumerar y priorizar nuestras convenciones de lo que viene primero en términos de argumentos de función para bloques do, argumentos IO para funciones que se imprimen, salidas para funciones in situ, etc. (https://github.com/JuliaLang/julia/issues/ 19150).

  • [] Argumentos posicionales frente a palabras clave. Hace mucho tiempo no teníamos argumentos de palabras clave. Todavía a veces se evitan por consideraciones de rendimiento. Debemos tomar esta decisión basándonos en lo que hace la mejor API, no en ese tipo de bagaje histórico (los problemas de rendimiento de las palabras clave también deben abordarse para que esto ya no sea una consideración).

  • [] Herramientas de metaprogramación. Tenemos muchas herramientas como @code_xxx que están emparejadas con funciones subyacentes como code_xxx . Estos deben comportarse de manera coherente: firmas similares, si hay funciones con firmas similares, asegúrese de que tengan versiones de macro similares. Idealmente, todos deberían devolver valores, en lugar de que algunos devuelvan valores y otros impriman resultados, aunque eso podría ser difícil para cosas como el código LLVM y el código ensamblador.

  • [] IO <=> equivalencia de nombre de archivo. Por lo general, permitimos que los nombres de archivo como cadenas se pasen en lugar de objetos IO y el comportamiento estándar es abrir el archivo en el modo apropiado, pasar el objeto IO resultante a la misma función con los mismos argumentos y luego asegurarnos de que el objeto IO se cierra después. Verifique que todas las funciones de aceptación de E / S adecuadas sigan este patrón.

  • [] API de Reductores. Asegúrese de que los reductores tengan comportamientos consistentes: todos toman una función de mapa antes de la reducción; argumentos de dimensión congruente, etc.

  • [] Argumentos de dimensión. Tratamiento coherente de los argumentos de entrada "calcular a través de esta [estas] dimensión [s]", qué tipos están permitidos, etc., considerar si sería conveniente hacerlos como argumentos de palabras clave.

  • [] Pares mutantes / no mutantes. Compruebe que las funciones no mutantes estén emparejadas con funciones mutantes cuando tenga sentido y viceversa.

  • [] Tuple vs. vararg. Compruebe que hay coherencia general entre si las funciones toman una tupla como último argumento o un vararg.

  • [] Uniones vs. nullables vs. errores. Reglas coherentes sobre cuándo las funciones deben generar errores y cuándo deben devolver Nullables o Unions (por ejemplo, parse / tryparse, match, etc.).

  • [] Apoyar a los generadores lo más ampliamente posible. Asegúrese de que cualquier función que pueda funcionar sensatamente con generadores lo haga. Ya somos bastante buenos con esto, pero supongo que nos hemos perdido algunos.

  • [] Selección del tipo de salida. Sea coherente sobre si las API de "tipo de salida" deben ser en términos de tipo de elemento o tipo de contenedor general (ref. # 11557 y # 16740).

  • [x] Elige un nombre. Hay algunas funciones / operadores con alias. Creo que esto está bien en los casos en que uno de los nombres no es ASCII y se proporciona la versión ASCII para que las personas aún puedan escribir código ASCII puro, pero también hay casos como <: que es un alias para issubtype donde ambos nombres son ASCII. Deberíamos elegir uno y desaprobar el otro. Desaprobamos is a favor de === y deberíamos hacer lo mismo aquí.

  • [] Coherencia con estructuras de datos . Está un poco más allá del alcance de Base Julia, pero debemos asegurarnos de que todas las colecciones en DataStructures tengan API consistentes con las proporcionadas por Base. La conexión en la otra dirección es que algunos de esos tipos pueden informar cómo terminamos diseñando las API en Base, ya que queremos que se extiendan sin problemas y de manera consistente.

  • [] NaN frente a DomainErrors. Consulte https://github.com/JuliaLang/julia/issues/5234 : tenga una política sobre cuándo hacer qué y asegúrese de que se siga de manera constante.

  • [] Colección <=> generador. A veces quieres una colección, a veces quieres un generador. Deberíamos revisar todas nuestras API y asegurarnos de que haya una opción para ambas cuando tenga sentido. Érase una vez, hubo una convención para usar un nombre en mayúsculas para la versión del generador y un nombre en minúsculas para la versión que está ansiosa y devuelve una nueva colección. Pero nadie le prestó atención a eso, así que tal vez necesitemos una nueva convención.

  • [] Funciones de orden superior sobre asociativos. Actualmente, algunas funciones de orden superior iteran sobre colecciones asociativas con la firma (k,v) , por ejemplo, map , filter . Otros iteran sobre pares, es decir, con la firma kv , requiriendo que el cuerpo desestructura explícitamente el par en k y v - por ejemplo, all , any . Esto debe revisarse y ser coherente.

  • [x] Convertir vs. construir. Permita la conversión cuando corresponda. Por ejemplo, ha habido varios problemas / preguntas sobre convert(String, 'x') . En general, la conversión es apropiada cuando hay una única transformación canónica. La conversión de cadenas en números en general no es apropiada porque hay muchas formas textuales de representar números, por lo que necesitamos analizar en su lugar, con opciones. Sin embargo, hay una única forma canónica de representar los números de versión como cadenas, por lo que podemos convertirlos. Deberíamos aplicar esta lógica de manera cuidadosa y universal.

  • [] Revise la integridad de la API de colecciones. Deberíamos mirar las funciones de biblioteca estándar para colecciones proporcionadas por otros lenguajes y asegurarnos de que tenemos una forma de expresar las operaciones comunes que tienen. Por ejemplo, no tenemos una función flatten o una función concat . Probablemente deberíamos.

  • [] Subrayar la auditoría.

deprecation

Comentario más útil

Subrayar auditoría

El siguiente es un análisis de todos los símbolos exportados desde Base que contienen guiones bajos, no están obsoletos y no son macros de cadena. Lo principal a tener en cuenta aquí es que estos son solo nombres exportados; esto no incluye nombres no declarados a los que les decimos a las personas que llamen calificados.

He separado las cosas por categoría. Con suerte, eso es más útil que molesto.

Reflexión

Tenemos las siguientes macros con funciones correspondientes:

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

Cualquier cambio que se aplique a las macros, si lo hay, debe aplicarse de manera similar a las funciones.

  • [x] module_name -> nameof (# 25622)
  • [x] module_parent -> parentmodule (# 25629, ver # 25436 para un intento anterior de cambiar el nombre)
  • [x] method_exists -> hasmethod (# 25615)
  • [x] object_id -> objectid (# 25615)
  • [] pointer_from_objref

pointer_from_objref tal vez le vendría bien un nombre más descriptivo, tal vez algo como address ?

Alias ​​para la interoperabilidad de C

Los alias de tipo que contienen guiones bajos son C_NULL , Cintmax_t , Cptrdiff_t , Csize_t , Cssize_t , Cuintmax_t y Cwchar_t . Aquellos que terminan en _t deben quedarse, ya que se nombran para ser consistentes con sus tipos C correspondientes.

C_NULL es el extraño aquí, siendo el único alias de C que contiene un guión bajo que no se refleja en C (ya que en C esto es solo NULL ). Podríamos considerar llamar a esto CNULL .

  • [] C_NULL

Conteo de bits

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

Para una discusión sobre cómo cambiarles el nombre, vea # 23531. Estoy muy a favor de eliminar los guiones bajos para estos, así como algunos de los reemplazos propuestos en ese RP. Creo que debería reconsiderarse.

Operaciones inseguras

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

Probablemente esté bien mantenerlos como están; la fealdad del subrayado subraya aún más su inseguridad.

Indexación

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

Aparentemente existen broadcast_getindex y broadcast_setindex! . No entiendo lo que hacen. ¿Quizás podrían usar un nombre más descriptivo?

Curiosamente, la versión de índice único de to_indices , Base.to_index , no se exporta.

Huellas

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

Presumiblemente, estos son los equivalentes en bloque catch de backtrace y stacktrace , respectivamente.

Tareas, procesos y señales

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

Corrientes

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

Sería bueno tener una función de redirección IO -> IO más general en la que se pudieran combinar todos estos, por ejemplo, redirect(STDOUT, io) , eliminando así tanto los guiones bajos como las exportaciones.

Promoción

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

Vea # 23999 para una discusión relevante sobre promote_rule .

Impresión

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

escape_string y unescape_string son un poco extraños porque pueden imprimir en una secuencia o devolver una cadena. Consulte el n. ° 25620 para ver una propuesta para moverlos o cambiarles el nombre.

Carga de código

  • [] include_dependency
  • [] include_string

include_dependency . ¿Esto incluso se usa fuera de la Base? No puedo pensar en una situación en la que quieras esto en lugar de include en cualquier escenario típico.

include_string . ¿No es solo una versión aprobada oficialmente de eval(parse()) ?

Cosas que no me molesté en categorizar

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

get_zero_subnormals y set_zero_subnormals podrían utilizar nombres más descriptivos. ¿Necesitan ser exportados?

Todos 131 comentarios

Disculpas si este no es el lugar apropiado para mencionar esto, pero sería bueno ser más consistente con los guiones bajos en los nombres de las funciones en el futuro.

No, este es un buen lugar para eso. Y sí, deberíamos esforzarnos por eliminar todos los nombres donde los guiones bajos son necesarios :)

  • tratamiento coherente de los argumentos de entrada "calcular a través de esta [estas] dimensión [es]", qué tipos están permitidos, etc., considerar si sería conveniente utilizarlos como argumentos de palabras clave
  • enumerar y priorizar nuestras convenciones de lo que viene primero en términos de argumentos de función para bloques do, argumentos IO para funciones que imprimen, salidas para funciones en el lugar, etc. (editar: pensé que ya podría haber uno abierto para esto)

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

También hubo un Julep reciente sobre la API por find y funciones relacionadas: https://github.com/JuliaLang/Juleps/blob/master/Find.md

¿Deberíamos desaprobar put! y take! en los canales (y tal vez hacer lo mismo con los futuros) ya que tenemos push! y shift! en ellos? Solo sugiero eliminar 2 palabras redundantes en la API.

Sospecho que shift! es fácil de usar. Un candidato es fetch! ya tenemos fetch que es la versión no mutante de take!

ref # 13538 # 12469

@amitmurthy @malmaud

Editar: Incluso tendría sentido reutilizar send y recv en los canales. (Me sorprende que estos solo se usen para UDPSockets en este momento)

+1 para reemplazar put! / take! con push! / fetch!

Agregaré el cambio @inferred nombre de @test_inferred .

Verifique que las especializaciones sean consistentes con las funciones más genéricas, es decir, no algo como # 20233.

Revise todas las funciones exportadas para verificar si alguna se puede eliminar reemplazándolas con envío múltiple, por ejemplo, print_with_color

El emparejamiento típico es push! y shift! cuando se trabaja con una estructura de datos similar a una cola.

Si no vamos a utilizar el emparejamiento de nombres típico para este tipo de estructura de datos porque nos preocupa que la operación implique una sobrecarga de comunicación que esos nombres no transmiten adecuadamente, entonces no creo que push! tampoco tiene sentido. send y recv realmente podrían ser mejores.

Tal vez verifique dos veces que existe una coherencia general entre si las funciones toman una tupla como último argumento o un vararg.

Quizás demasiado grande para este problema, pero sería bueno tener reglas consistentes sobre cuándo las funciones deben arrojar errores y cuándo deben devolver Nullable so Union s (por ejemplo, parse / tryparse , match , etc.)

No hay problema demasiado grande, @simonbyrne : esta es la lista de lavandería.

Por cierto: esto no es realmente para cambios específicos (por ejemplo, cambiar el nombre de funciones específicas), se trata más de tipos de cosas que podemos revisar. Para cambios específicos propuestos, simplemente abra un problema que proponga ese cambio.

Tenemos muchas herramientas como @code_xxx que están emparejadas con funciones subyacentes como code_xxx

No estoy seguro de si esto es de lo que está hablando, pero consulte CreateMacrosFrom.jl

  • Si las API de "tipo de salida" deben ser en términos de tipo de elemento o tipo de contenedor general (ref. # 11557 y # 16740)
  • Documentar todas las funciones exportadas (incluidas las pruebas de documentación)

Documentar todas las funciones exportadas (incluidas las pruebas de documentación)

si esto es parte de esto, entonces quizás también: recuerde etiquetar sus pruebas con el número de problema / pr. Hace que sea mucho más fácil entender por qué existe esa prueba. Sé cómo funciona git blame, pero al agregar conjuntos de pruebas (solo para dar un ejemplo) a veces es un poco misterioso lo que se está probando, y sería genial si el número de problema / pr estuviera siempre ahí.

@dpsanders : ¡y macros exportadas! por ejemplo, @fastmath no tiene docstring.

Esto es muy pequeño, pero las funciones string y Symbol hacen casi lo mismo y tienen diferentes mayúsculas. Creo que symbol tendría más sentido.

@amellnik La diferencia es que Symbol es un constructor de tipos y string es una función regular. IIRC solíamos tener symbol pero fue obsoleto en favor del constructor de tipos. No estoy convencido de que sea necesario un cambio para esto, pero en todo caso, creo que deberíamos usar el constructor String en lugar de string .

en todo caso, creo que deberíamos usar el constructor String en lugar de string.

No, son funciones diferentes y no deberían fusionarse

julia> String(UInt8[])
""

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

No, son funciones diferentes y no deberían fusionarse

Esto parece una situación en la que string(args...) debería quedar en desuso en favor de sprint(print, args...) , entonces - tener tanto string como String es confuso. Podríamos especializarnos en sprint(::typeof(print), args...) para recuperar cualquier rendimiento perdido. En este sentido, también podría tener sentido desaprobar repr(x) por sprint(showall, args...) .

Eso suena bien, aunque llamar a string para convertir algo en una cadena parece bastante estándar ...

llamar a una cadena para convertir algo en una cadena parece bastante estándar

Sí, pero ahí es donde entra la desconexión entre String y string .

sprint(print, ...) siente redundante. Si nos deshacemos de string , podemos cambiar el nombre de sprint a string para obtener string(print, foo) y string(showall, foo) que se lee bien en mi opinión .

Este podría ser un caso en el que la coherencia esté sobrevalorada. Creo que está bien tener string(x) para "solo dame una representación de cadena de x". Si va a ser más complicado que eso, por ejemplo, requiriendo que especifique qué función de impresión usar, entonces usar otro nombre como sprint tiene sentido.

También estaría bien para mí cambiar el nombre de String(UInt8[]) a otra cosa y usar String lugar de string . string nos da un poco más de flexibilidad en el futuro para cambiar el tipo de cadena que devolvemos, pero eso no parece probable que suceda.

¿Tiene sentido reinterpret(String, ::Vector{UInt8} o es un juego de palabras con reinterpret ?

Eso parece tener sentido.

Un problema es que esta función a veces copia, por lo que el nombre es algo engañoso.

Es cierto, pero se supone que las cadenas son inmutables, por lo que probablemente podamos salirse con la nuestra.

También hay un método String(::IOBuffer) , pero parece que podría quedar obsoleto en readstring .

También pensé en su cambio de API propuesto, pero la interfaz de string(a, b...) es que encadena y concatena sus argumentos, y esto haría una molesta excepción de gotcha para los primeros argumentos invocables. Si eliminamos la concatenación de string entonces se podría hacer que funcione.

Sí, estoy de acuerdo; la consistencia y evitar trampas es lo más importante.

Observando los problemas # 18326 y # 3893 en la categoría "argumentos de dimensión".

Si puedo agregar otro elemento: asegurándome de que el comportamiento de los contenedores de mutables esté documentado y sea consistente.

@ JaredCrean2 : ¿puede explicar lo que quiere decir con eso?

Ciertamente espero que no implique hacer muchas "copias defensivas".

Por ejemplo, si tengo una matriz de tipos mutables y llamo a sort en ella, ¿la matriz devuelta apunta a los mismos objetos que la matriz de entrada, o copia los objetos y hace que la matriz devuelta apunte a ¿ellos?

Los mismos objetos. Estoy bastante seguro de que todos nuestros métodos de clasificación de colecciones, getindex, filtrado, búsqueda, etc. siguen esta regla, ¿no?

No creo que haya falta de claridad o coherencia en ese punto, siempre son los mismos objetos.

De hecho, creo que la única función estándar donde ese no es el caso es deepcopy donde el punto es que obtienes todos los objetos nuevos.

¿Está eso documentado en alguna parte?

No, podríamos, pero no estoy seguro de dónde sería mejor documentarlo. ¿Por qué las funciones harían copias innecesariamente? ¿De dónde sacaste la impresión de que podrían hacerlo?

Hola. No he visto, creo, ningún comentario sobre la serialización de datos.

Tarde o temprano los programas de julia se escribirán y ejecutarán públicamente, los datos comenzarán a estratificarse a veces, durante años. Serialización de datos, por ejemplo. la cadena: el objeto a bytes impulsado por tipo (tal vez sobre json o ...) tiene que construirse para ser resistente al tiempo. Pensar en versiones semánticas y api web también puede contar.

¿Podríamos esperar que la serialización de los datos del usuario se mantenga cerca de https://github.com/JuliaLang/julia/blob/v0.5.1/base/serialize.jl ?

¿Por qué las funciones harían copias innecesariamente? ¿De dónde sacaste la impresión de que podrían hacerlo?

No sé si lo hacen o no. Por lo que puedo decir, el comportamiento no está definido. Desde el comentario de @JeffBezanson , hay personas que abogan por hacer copias defensivas, a lo que él se opone. Entonces, la documentación debería abordar la cuestión de las copias defensivas en algún lugar.

Parece estar implicando algún tipo de principio de mínima acción, pero dependiendo de los detalles del algoritmo, cuál es la "mínima acción" se vuelve ambigua. Para lograr coherencia en la API, creo que se requieren orientaciones más específicas.

@ o314 : este es un problema de revisión de consistencia de API, no estoy seguro de cómo se relaciona la serialización.

@ JaredCrean2 : sin duda, es necesario documentar si el objeto de nivel superior se copia o no. Lo que estoy diciendo es que los objetos más profundos nunca se copian, excepto por deepcopy (obviamente).

Lo que estoy diciendo es que los objetos más profundos nunca se copian, excepto por deepcopy (obviamente).

Hubo una discusión reciente sobre esto en el contexto de copy para algunos de los contenedores de matriz, por ejemplo, SubArray y SparseMatrixCSC pero también Symmetric , LowerTriangular . Me parece que bajo la política mencionada anteriormente, copy sería un error para este tipo de envoltorios. ¿Es la política que menciona el nivel correcto de abstracción aquí? Por ejemplo, creo que implica que si Array s se implementaron en Julia (envolviendo un búfer), el comportamiento de copy en Array s debería cambiar a un noop.

Si la convención es que los objetos más profundos nunca se copian, lo único que queda es documentarlo. La documentación es una parte realmente importante de una API. Este comportamiento puede parecerle obvio (quizás porque escribió partes del código), pero desde una perspectiva externa no es tan obvio.

Editar: no vi la publicación de Andreas. Esa es una consideración interesante.

@StefanKarpinski Estoy de acuerdo con tu punto.
Y todos los temas principales que se invocan aquí son muy buenos e inteligentes.

Pero a veces tengo un poco de miedo con respecto al equilibrio entre el proceso y los datos en Julia:

Podemos llamar a fortran oc fácilmente con seguridad,
Pero el código se implementará con la misma facilidad en los centros de datos modernos, por ejemplo. aws lambda con su función como patrón de servicio. ¿El código se podrá llamar fácilmente a través de Internet, api abierto?

A veces, uno tiene que reducir la carga funcional para escalar (sin genérico, sin vaargs en la firma de la función, sin orden superior en la API pública) y vincular datos de forma más sistemática (esquema json / openapi).

He visto una biblioteca de Python muy buena hundiéndose de esta manera y es una pena.

Creo que es un punto crucial para un lenguaje 1.0 mantener los datos y las funciones equilibrados y modulares para poder implementarlos fácilmente en la web. Y para esta función, la interfaz tiene que estar menos orientada a las mascotas y más al ganado cuando sea necesario.

Puede que ese no sea el punto en este tema.

@StefanKarpinski Puede que haya entendido mal tu publicación. Cuando dijiste

si el objeto de nivel superior se copia o no, ciertamente debe documentarse

¿Qué significa "objeto de nivel superior"? Si tengo x::Vector{MyMutableType} , ¿el objeto de nivel superior es x o los elementos de x ?

El objeto de nivel superior se refiere a x sí mismo, no a los elementos de x .

@andreasnoack La noción de objeto de nivel superior debe referirse a la estructura abstracta implementada, no a los detalles de implementación.

tal vez agregar float y otra función similar que actúe tanto en tipos como en valores?

Repasando las notas de la versión 0.6, parece extraño que se introduzca iszero(A::Array{T}) , mientras que muchas otras funciones (por ejemplo, sumabs , isinteger , isnumber ) sobre matrices son en desuso a favor de all(f,A) .

No hay matrices y son elementos cero en su espacio vectorial. iszero prueba genéricamente si algo es el inverso aditivo, que son las matrices cero.

Re. consistencia de los guiones bajos en los nombres de las funciones, aquí hay una ruta de navegación a count_ones y count_zeros .

Me parece que si desea mantener la API de Julia consistentemente consistente, necesitará algún software que le permita (a) especificar cuáles son las reglas / convenciones de la API, (b) realizar un análisis estático del código de Julia para detectar desviaciones de esas reglas / convenciones, y (c) ofrecer sugerencias. Esta herramienta beneficiaría tanto a Julia Base como a todos los paquetes de Julia. Un nuevo paquete de Julia puede ser suficiente. (Lengua en la mejilla: lo primero que debe hacer este paquete es sugerir su propio nombre; APICheck.jl, ApiCheck.jl, API_Check.jl, APIChecker.jl, JuliaAPIChecker.jl, etc.) Soy bastante nueva en Julia, así que no quiero tomar la iniciativa en tal cosa. Sin embargo, no me importaría contribuir. ¿Alguna sugerencia sobre cómo hacer esto?

¡Nos encantaría tener eso en Lint.jl!

num2hex y hex2num (# 22031 y # 22088)

Yo también creo que Lint.jl es el paquete adecuado para comprobar la coherencia de la API de Julia. Pero si tomamos ese camino, la lista original de Stefan debe hacerse mucho más precisa. Por ejemplo, cuando escribe "debemos asegurarnos de que todas las colecciones en DataStructures tengan API consistentes", las preguntas que vienen a la mente son:

  • ¿Qué hace que una estructura de datos sea una colección? (Lista de colecciones en base)
  • ¿Cuáles son las funciones que DEBE admitir una colección? (Una hoja de cálculo de funciones por colecciones)
  • ¿Cuál debe ser la firma de cada una de esas funciones?
  • ¿Las API proporcionadas por Base para las colecciones ya son realmente coherentes?

Para administrar tales inventarios y análisis, es posible que deseemos agregar un proyecto al repositorio de Julia (proyecto n. ° 8), al repositorio de JuliaPraxis (sugerido sin conexión por TotalVerb) o al repositorio de Lint. En ese caso, tendríamos que decidir quién sería el propietario de un proyecto de este tipo, qué personas deberían participar desde el principio y quién debería tomar las decisiones finales sobre cuáles son en realidad las convenciones de Julia (con el fin de aclarar).

Pero antes de avanzar más en estas líneas, me gustaría preguntarle a @StefanKarpinski : ¿cuáles son sus ideas sobre cómo trabajar en su lista de problemas de coherencia de la API de Julia?

Estoy de acuerdo en que especificar esto específicamente es una buena idea. Averiguar cuál debería ser esa lista es parte del trabajo aquí; si desea intentarlo, sería genial.

¿Realmente necesitamos un Base.datatype_module y un Base.function_module?

Un "módulo" de función unificada (tal vez getmodule) que se distribuye sobre el tipo de datos y la función me parece más consistente.

¿Qué pasa con los guiones bajos en @code_typed y amigos?

Esa es una buena oportunidad para refactorizar (la razón declarada para la prohibición del subrayado). Podría tener una macro @code con el primer argumento como el tipo de código que desea.

( @bramtayl , recuerde poner comillas invertidas alrededor de las macros ya que esto hace ping al "código" de usuario de github si no es así; @code )

FWIW, la finalización de tabulación solo funciona con nombres de funciones. Ser capaz de hacer @code_<TAB> es bueno ...

Si se considera la refactorización, pero se rechaza, entonces es discutible si hay guiones bajos o no, porque el único punto de la prohibición de guiones bajos es fomentar la refactorización. De hecho, en ese caso, parece que se deben fomentar los guiones bajos para que el lenguaje sea más claro.

Subrayar la auditoría.

contrapropuesta: todavía auditamos estos nombres, pero en su lugar agregamos más guiones bajos donde haría que el código sea más fácil de leer (tipo de código frente a tipo de código, isos2 frente a is_os2).

No soy absolutista en esto. Creo que code_typed está bien, útilmente se completa con la pestaña como señala @KristofferC , y realmente no hay nada obvio para pasar como argumento para seleccionar qué salida desea.

Para mí, parece que el barco ha navegado al agregar más guiones bajos, ya que tendríamos que desaprobar básicamente la mitad de Base. Como ejemplo, hay 74 funciones de predicado que comienzan con is y solo 6 que comienzan con is_ . ¿Qué tiene más sentido, depreciar 6 o 74?

Ok, aquí hay varios objetivos en conflicto:

1) Hacer los nombres más legibles
2) Reducir la rotación de códigos
3) Fomentar la refactorización

La eliminación de guiones bajos al combinar palabras entre sí falla en los 3 frentes.

¿Que los métodos show que aceptan una transmisión no son ! terminados parece inconsistente con la convención habitual? Árbitro. https://github.com/JuliaLang/julia/pull/22604/commits/db9d70a279763ded5088016d9c3d4439a49e3fca#r125115063. ¡Mejor! (Editar: supongo que esto coincide con los métodos write que aceptan una transmisión).

Hay inconsistencias con la API de rasgos. Algunos rasgos se calculan llamando al rasgo como
TypeArithmetic(Float64)
mientras que otros esta función debe escribirse en minúsculas:
iteratorsize(Vector{Float64})

Considere cambiar el nombre de size -> shape (xref # 22665)

Array{T,1}() probablemente también debería estar obsoleto:

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

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

He estado pensando en colecciones. Básicamente tenemos tres tipos:

  • Colecciones simples en forma de conjunto (o bolsa), que solo contienen algunos valores.
  • Colecciones tipo matriz, que agregan un índice junto con los datos.
  • Colecciones similares a dictados, que tienen índices como parte de los datos, es decir, pares k=>v .

Me he vuelto escéptico ante el comportamiento de dictado. El canario en la mina de carbón es map , donde operar con pares clave-valor no es natural, ya que generalmente no desea cambiar el conjunto de claves. También es posible que las matrices y los dictados implementen la misma interfaz:

  • keys corresponde a eachindex
  • mapindexed y filterindexed serían útiles tanto para dictados como para matrices. Estos son como mapa y filtro, excepto que también le pasan a su función el índice del elemento en cuestión.
  • Las matrices y los dictados (y tuplas con nombre, y tal vez otras cosas) podrían usar un iterador pairs , que es básicamente un atajo para zip(keys(c), values(c)) .

Considere cambiar el nombre de ind2sub y sub2ind , que aparentemente son matlabismos, y que tienen un nombre no juliano y extraño para un usuario que no es de matlab. Los nombres posibles serían indice y linearindice respectivamente. No me atreví a hacer un PR porque no estoy seguro de lo que la gente piensa al respecto, pero lo haré si hay apoyo.

Lo mismo con rad2deg y deg2rad .

Árbitro. # 22791 ( select -> partialsort ). ¡Mejor!

Una cosa que no he visto aquí: ¿los argumentos posicionales opcionales van primero o al final? A veces, los argumentos posicionales opcionales van primero, como en sum(f, itr) y rand([rng,] ..) . Pero en otros lugares van últimos, por ejemplo, en median(v[, region]) o split(s::AbstractString[, chars]) . A veces pueden ir primero o al final, ¡pero no ambos! (Por ejemplo, mean puede tomar una función primero o una dimensión al final, pero no ambas).

La semántica del lenguaje actual obliga a los argumentos opcionales a ir en último lugar: puede escribir f(a, b=1) pero no f(b=1, a) . Pero si todos los argumentos opcionales van en último lugar, ¿qué sucede con los bloques do convenientes?

Si nada más, es una verruga menor que el lenguaje tenga que definir métodos así, desde rand.jl : shuffle!(a::AbstractVector) = shuffle!(GLOBAL_RNG, a) . La sintaxis opcional del argumento posicional debería ocuparse exactamente de este caso de uso.

Tal vez debería ir en una edición separada, pero parece posible mover los argumentos opcionales a donde desee. Entonces, por ejemplo, f(a = 1, b, c = 2) definiría f(x) = f(1, x, 2) y f(x, y) = f(x, y, 2)

xref # 22460 para un intento (impopular) de habilitar argumentos predeterminados en cualquier posición.

Tal vez cambie el nombre de warn a warning (matlab también usa esto), no es gran cosa, pero ¿pensé que lo mencionaría?

Me gusta warn porque es un verbo, como throw .

Me confundí mucho con esto:

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

Sugiero permitir solo las palabras clave al final al llamar a funciones, similar a cuando se definen funciones de palabras clave.

Sugiero permitir solo las palabras clave al final al llamar a funciones, similar a cuando se definen funciones de palabras clave.

Hay muchas API en las que pasar palabras clave en otras posiciones es útil y ergonómico.

Por curiosidad, ¿existe un orden de evaluación definido para los argumentos posicionales y de palabras clave? Vi algunos problemas anteriores y https://docs.julialang.org/en/latest/manual/functions/#Evaluation -Scope-of-Default-Values-1 habla sobre alcances, pero nada de lo que he encontrado indica si, por ejemplo, los argumentos se evalúan de izquierda a derecha, o todos los argumentos posicionales se evalúan antes que todos los argumentos de palabras clave, o si no hay un orden de evaluación definido (o algo más).

@yurivish , para ver las palabras clave, consulte los documentos (también https://github.com/JuliaLang/julia/issues/23926). Para los opcionales, la historia es un poco más complicada, quizás lea aquí . (Tenga en cuenta que es mejor hacer preguntas en https://discourse.julialang.org/)

No parece que valga la pena su propio problema, pero siempre me parece extraño que bits(1) devuelva un String , parece que debería ser BitVector o Vector{Bool} .

Me gustaría sugerir una revisión de las tablas de métodos que se envían en Function o Callable . Tratemos de asegurarnos de que estas API sean como las queremos… y que no estamos perdiendo la oportunidad de permitir la reestructuración de las tablas de manera que podamos permitir la llamada de pato a cualquier objeto.

En cuanto a all y any , parecen sencillos de desaprobar a all(f(x) for x in xs) , que ya está reducido a all(Generator(f, xs)) y, por lo tanto, no debería tener gastos generales.

No estoy seguro si eso es lo que quería decir, pero me di cuenta de que vale la pena que indica si acaso: Soy duro contra la desaprobación cualquier API de estilo funcional para los generadores. Tenemos any(f, x) y all(f, x) y son ampliamente utilizados; -10000000 por eliminar esos (o cualquiera de esos métodos, en realidad).

Soy un generador profesional. Parece un bloque de construcción fundamental de la programación perezosa y debería exportarse. all(Generator(f, xs)) veces es más conveniente para funciones definidas que all(f(x) for x in xs) . También +10000000 para restablecer el equilibrio

Prefiero tener menos sintaxis aquí si es posible. Si es fácil, legible y eficaz expresar la idea de "tomar xs, aplicar f a todo y devolver verdadero si todo eso es cierto", entonces ¿por qué deberíamos ponerlo en un verbo?

Si la preocupación es el sustantivo innecesario x en f(x) for x in xs , entonces la sugerencia de @bramtayl de exportar Generator (¿quizás usando un nombre mejor como Map ?) tiene sentido.

Cosas como all(isnull, x) son bastante más simples que all(isnull(v) for v in x) . Hemos desaprobado la función allnull de NullableArrays en favor de all(isnull, x) ; si esa sintaxis desapareciera, probablemente tendríamos que reintroducirla.

¿Qué tal cambiar el nombre de strwidth a stringwidth (creo que esta es la única función de manipulación de cadenas exportada que ha abreviado cadena a str)

En realidad, se le cambió el nombre a textwidth (https://github.com/JuliaLang/julia/pull/23667).

En mi opinión, este problema es demasiado amplio para tener el hito 1.0, dado que queremos que la función se congele en breve. Es posible que necesitemos varios propietarios y asignar conjuntos de funciones para su revisión si vamos a hacer esto.

Además, este es otro lugar donde FemtoCleaner puede actualizar automáticamente muchas cosas posteriores a la 1.0, aunque sería bueno hacerlo bien.

solo un comentario sobre el ancho del 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

¿El mensaje de error parece bastante claro sobre cuál es el problema? Cairo necesita extender el 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)

No tenía ninguna duda de que el mensaje (que recibí a través de travis) está bien, pero ¿por qué Compat exporta el ancho de texto si no está en 0.6?

¿Porque ese es el punto de Compat? Sin embargo, esta discusión se está saliendo mucho de su alcance, por lo que sugiero que podamos continuar con el discurso o la holgura.

Sugiero cambiar copy a shallowcopy y deepcopy a copy ya que recientemente me tomó bastante tiempo darme cuenta de que la copia es una copia "superficial" y La función que escribí estaba mutando la matriz de matrices. Creo que sería mucho más intuitivo si copy hiciera una copia "profunda" y algo como shallowcopy se usara para copias superficiales. Lo sé ahora cuándo usar deepcopy , pero creo que muchos otros usuarios se encontrarán con el mismo problema.

Intentemos mantener este problema para mantener la coherencia de la API, no como una bolsa de sorpresas de "cosas específicas que no me gustan".

Associative el nombre parece bastante inconsistente con todos los otros nombres de tipos en Julia.

En primer lugar, los tipos no suelen ser adjetivos. El sustantivo es Association , y lo usan al menos algunos documentos de Mathematica que encontré.

Creo que AbstractDict sería mucho más consistente con otros tipos como AbstractArray , AbstractRange , AbstractSet y AbstractString , cada uno de los cuales tiene a tipos de hormigón prototípicos Dict , Array , Range , Set y String .

Nuestros tipos de excepciones están un poco por todas partes: algunos se llaman FooError , otros se llaman BarException ; algunos se exportan, la mayoría no. Esto podría usar un paso a través de la coherencia.

Entonces, ¿qué se preferiría, FooError o BarException ? ¿Exportado o no?

Para mí, BarException implica en algún lugar un patrón de levantamiento / captura.

Prefiero mucho, y algunos otros en el mundo funcional también, usar el patrón Some / None (*) donde el flujo de control es más directo y predecible.

Entonces +1 por FooError

(*) Some / Void ex Optional en julia # 23642.

¿Siguen estas cosas sobre la mesa dada la función congelada? En especial, me gustaría abordar los argumentos opcionales frente a los de palabra clave, pero la lista de funciones con múltiples argumentos opcionales (el caso más claro para usar argumentos de palabra clave) es bastante larga.

¡Por favor echa un vistazo! No he tenido la oportunidad de abordar estos problemas de manera sistemática.

Por cierto, he notado una inconsistencia en el nombre de los rasgos: tenemos iteratorsize , iteratoreltype , pero IndexStyle , TypeRangeStep , TypeArithmetic y TypeOrder . Parece que las variantes de CamelCase son más numerosas y más recientes, por lo que tal vez deberíamos adoptar esa convención en todas partes.

Definitivamente, esos deben ser consistentes. ¿Quieres hacer relaciones públicas?

Creo que esto debería solucionarse como parte de https://github.com/JuliaLang/julia/pull/25356.

EDITAR: ver también https://github.com/JuliaLang/julia/issues/25440

Esto se hace principalmente o se puede hacer en versiones 1.x. Puedo actualizar las casillas de verificación, pero las revisamos en la llamada de clasificación y todo, excepto el # 25395 y la auditoría de subrayado, están listos.

Subrayar auditoría

El siguiente es un análisis de todos los símbolos exportados desde Base que contienen guiones bajos, no están obsoletos y no son macros de cadena. Lo principal a tener en cuenta aquí es que estos son solo nombres exportados; esto no incluye nombres no declarados a los que les decimos a las personas que llamen calificados.

He separado las cosas por categoría. Con suerte, eso es más útil que molesto.

Reflexión

Tenemos las siguientes macros con funciones correspondientes:

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

Cualquier cambio que se aplique a las macros, si lo hay, debe aplicarse de manera similar a las funciones.

  • [x] module_name -> nameof (# 25622)
  • [x] module_parent -> parentmodule (# 25629, ver # 25436 para un intento anterior de cambiar el nombre)
  • [x] method_exists -> hasmethod (# 25615)
  • [x] object_id -> objectid (# 25615)
  • [] pointer_from_objref

pointer_from_objref tal vez le vendría bien un nombre más descriptivo, tal vez algo como address ?

Alias ​​para la interoperabilidad de C

Los alias de tipo que contienen guiones bajos son C_NULL , Cintmax_t , Cptrdiff_t , Csize_t , Cssize_t , Cuintmax_t y Cwchar_t . Aquellos que terminan en _t deben quedarse, ya que se nombran para ser consistentes con sus tipos C correspondientes.

C_NULL es el extraño aquí, siendo el único alias de C que contiene un guión bajo que no se refleja en C (ya que en C esto es solo NULL ). Podríamos considerar llamar a esto CNULL .

  • [] C_NULL

Conteo de bits

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

Para una discusión sobre cómo cambiarles el nombre, vea # 23531. Estoy muy a favor de eliminar los guiones bajos para estos, así como algunos de los reemplazos propuestos en ese RP. Creo que debería reconsiderarse.

Operaciones inseguras

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

Probablemente esté bien mantenerlos como están; la fealdad del subrayado subraya aún más su inseguridad.

Indexación

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

Aparentemente existen broadcast_getindex y broadcast_setindex! . No entiendo lo que hacen. ¿Quizás podrían usar un nombre más descriptivo?

Curiosamente, la versión de índice único de to_indices , Base.to_index , no se exporta.

Huellas

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

Presumiblemente, estos son los equivalentes en bloque catch de backtrace y stacktrace , respectivamente.

Tareas, procesos y señales

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

Corrientes

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

Sería bueno tener una función de redirección IO -> IO más general en la que se pudieran combinar todos estos, por ejemplo, redirect(STDOUT, io) , eliminando así tanto los guiones bajos como las exportaciones.

Promoción

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

Vea # 23999 para una discusión relevante sobre promote_rule .

Impresión

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

escape_string y unescape_string son un poco extraños porque pueden imprimir en una secuencia o devolver una cadena. Consulte el n. ° 25620 para ver una propuesta para moverlos o cambiarles el nombre.

Carga de código

  • [] include_dependency
  • [] include_string

include_dependency . ¿Esto incluso se usa fuera de la Base? No puedo pensar en una situación en la que quieras esto en lugar de include en cualquier escenario típico.

include_string . ¿No es solo una versión aprobada oficialmente de eval(parse()) ?

Cosas que no me molesté en categorizar

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

get_zero_subnormals y set_zero_subnormals podrían utilizar nombres más descriptivos. ¿Necesitan ser exportados?

+1 por method_exists => methodexists y object_id => objectid . También es un poco tonto que catch_stacktrace incluso exista. Se puede desaprobar su definición, stacktrace(catch_backtrace()) .

¿Cómo nos sentimos al restarle importancia a C_NULL ? Me he acostumbrado bastante, pero también compro el argumento de que ninguno de los otros nombres C* tiene un guión bajo.

Los otros nombres de C son tipos, mientras que C_NULL es una constante. Creo que está bien como está y sigue las pautas de nomenclatura.

y sigue las pautas de nomenclatura.

¿Cómo es eso?

Las constantes suelen estar en mayúsculas con guiones bajos: C_NULL sigue a eso. Como dijo @ iamed2 , es un valor, no un tipo, por lo que la convención de nomenclatura Cfoo no se aplica necesariamente.

Pensé erróneamente que https://github.com/JuliaLang/julia/blob/master/doc/src/manual/variables.md#stylistic -conventions hacía referencia a constantes, pero no es así. Probablemente debería.

Sugiero una interfaz consistente y matemáticamente sólida para los espacios generales de Hilbert en los que los vectores no son matrices de Julia. Los nombres de funciones como vecdot , vecnorm , etc. bien podrían ser reemplazados por los conceptos generales de inner y norm como se explica en https: // github. com / JuliaLang / julia / issues / 25565.

Como he dicho varias veces, este no es un problema general para las cosas que uno quiere cambiar.

Creo que los únicos elementos que quedan bajo este paraguas para 1.0 son # 25501 y # 25717.

Me gustaría hacer algo con (get|set)_zero_subnormals pero tal vez la mejor solución a corto plazo sea simplemente no exportarlos.

Algo que probablemente debería revisarse es cómo se tratan los números en el contexto de las operaciones de recolección como map y collect . Se señaló que el primero devuelve un escalar, pero el último devuelve una matriz 0D.

¿Fue útil esta página
0 / 5 - 0 calificaciones