Julia: permitir la sobrecarga de la sintaxis de acceso al campo ab

Creado en 10 ene. 2013  ·  249Comentarios  ·  Fuente: JuliaLang/julia

Comentario más útil

Aquí hay una implementación divertida de 3 líneas de esto:

diff --git a/base/boot.jl b/base/boot.jl
index cd3ae8b..a58bb7e 100644
--- a/base/boot.jl
+++ b/base/boot.jl
@@ -266,6 +266,9 @@ Void() = nothing

 (::Type{Tuple{}})() = ()

+struct Field{name} end
+(::Field{f})(x) where {f} = getfield(x, f)
+
 struct VecElement{T}
     value::T
     VecElement{T}(value::T) where {T} = new(value) # disable converting constructor in Core
diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm
index b4cb4b5..59c9762 100644
--- a/src/julia-syntax.scm
+++ b/src/julia-syntax.scm
@@ -1685,7 +1685,7 @@
     (if (and (pair? e) (eq? (car e) '|.|))
         (let ((f (cadr e)) (x (caddr e)))
           (if (or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
-              `(call (core getfield) ,f ,x)
+              `(call (new (call (core apply_type) (core Field) ,x)) ,f)
               (make-fuse f (cdr x))))
         (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e)))
             (make-fuse (undotop (cadr e)) (cddr e))

Mi opinión es que a.b debería llamar a una función de proyección Field{:b}() lugar de getfield , de modo que obtenga funciones como x->x.a ya de forma gratuita. Eso también permite que getfield siempre signifique acceso de campo de bajo nivel.

La implementación anterior funciona completamente, pero es bastante difícil para el compilador (sysimg + 5%, lo cual es una sorpresa agradable). Por lo tanto, esto necesitará algunas heurísticas de especialización y algunas optimizaciones tempranas deben actualizarse, pero luego, con suerte, esto será viable.

Todos 249 comentarios

La capacidad de usar puntos como azúcar sintáctico para los métodos de acceso / mutación sería buena para muchas cosas. Siempre he apreciado esto en los lenguajes que lo proporcionan, para que pueda convertir los campos de estructura en abstracciones más complicadas sin romper la API.

+1

Tengo una manera absolutamente increíble de implementar esto.

¿Interesado en hablar de ello? Sé que Tom Short está realmente interesado en tener esto para DataFrames, aunque me he vuelto cada vez más escéptico sobre la conveniencia de usar esta función.

Esto haría que llamar al código Python (a través de PyCall) sea significativamente más agradable, ya que actualmente estoy obligado a hacer a[:b] lugar de a.b .

@JeffBezanson , ¿alguna posibilidad de tener esto por 0.3? Sería genial para la interoperabilidad entre idiomas, tanto para PyCall como para JavaCall (cc @aviks).

@JeffBezanson si _no_, ¿hay alguna posibilidad de que puedas dar alguna dirección sobre cómo quieres que esto se implemente? ( I have an absolutely awesome way to implement this. )

En mi experiencia, no hay forma más rápida ni más segura de lograr que Jeff implemente algo que implementar una versión que no le gusta ;-)

La idea básica es que implementes

getfield(x::MyType, ::Field{:name}) = ...

para que pueda sobrecargarlo por campo. Eso permite el acceso a campos "reales" para seguir trabajando de forma transparente. Con alternativas adecuadas, getfield(::MyType, ::Symbol) también funciona.

El mayor problema es que los módulos tienen un comportamiento especial con respecto a . . En teoría, este sería solo otro método de getfield , pero el problema es que necesitamos resolver las referencias de módulo _antes_ ya que básicamente se comportan como variables globales. Creo que tendremos que mantener este caso especial en el comportamiento de . . También existe un pequeño problema de eficiencia del compilador, debido al análisis de (# tipos) * (# campos) definiciones de funciones adicionales. Pero para eso solo veremos qué pasa.

@JeffBezanson ¿También se refiere al comportamiento de const en los módulos? Sería útil tener un tipo de usuario emulando un módulo y poder decirle al compilador cuándo el resultado de una búsqueda de campo dinámico es de hecho constante. (otro enfoque sería comenzar con un módulo real y poder "atrapar" un jl_get_global fallido e inyectar nuevos enlaces a pedido)

Me parece muy útil en combinación con # 5395. Entonces, uno podrá interceptar una llamada a una función o método no definido MyMod.newfunction(new signature) y generar enlaces a una API (posiblemente grande) a pedido. Esto luego se almacenaría en caché como enlaces constantes habituales, supongo.

Permítanme, un simple novato de Julia, presentar una pequeña preocupación: creo que la posibilidad de sobrecargar el operador de puntos podría implicar que la "pureza" del acceso al campo se pierde de alguna manera.

El usuario generalmente perdería el conocimiento si hacer ab es solo un acceso a una referencia / valor o si puede haber una enorme maquinaria de función que se está llamando detrás. Sin embargo, no estoy seguro de cómo eso podría ser malo, es solo un sentimiento ...

Por otro lado, veo que, de hecho, este es un gran deseo de azúcar sintáctico para muchos casos (PyCall, Dataframes ...), lo cual es perfectamente comprensible.
Tal vez sea hora de .. # 2614?

Apoyo hacer esto.

Pero la pureza tiene algo que decir, incluso si uno puede usar names(Foo) para averiguar cuáles son los componentes reales de Foo .

El argumento de la pureza está estrechamente relacionado con la principal preocupación práctica que tengo, que es cómo se manejan los conflictos de nombres cuando los campos del tipo interfieren con los nombres que podría esperar usar. En DataFrames, creo que resolveríamos esto prohibiendo el uso de columns y colindex como nombres de columna, pero queríamos saber cuál era el plan de las personas para esto.

Supongo que getfield(x::MyType, ::Field{:foo}) = ... tendría que estar prohibido cuando MyType tiene un campo foo , de lo contrario se perdería el acceso al campo real (o una forma de forzar el acceso al campo tendría que estar disponible).
Pero entonces getfield solo podría definirse para tipos concretos, ya que los abstractos no saben nada sobre campos.

(Mientras tanto, me topé con esto sobre C ++ ).

No es un problema importante. Podemos proporcionar algo como Core.getfield(x, :f) para forzar el acceso a los campos reales.

Ok, tal vez estoy vendido. Pero luego definir un acceso directo a Core.getfield(x, :f) (por ejemplo, x..f ) será bueno, de lo contrario, el código interno de tipos que sobrecargan el . para todos los símbolos (marcos de datos, probablemente diccionarios) deben estar lleno de Core.getfield s.

No me preocupa el aspecto de la pureza, hasta que tengamos este, el único código
que debería usar el acceso de campo es un código que pertenece al
implementación de un tipo dado. Cuando el acceso al campo es parte de una API,
tienes que documentarlo, como con cualquier api. Estoy de acuerdo en que podría ser útil con
Sin embargo, alguna sintaxis de atajo para core.getfield, al escribir esos
implementaciones.

Ya se había señalado en el n. ° 4935, pero vayamos aquí: la sobrecarga de puntos puede superponerse un poco con el envío múltiple clásico de Julian si no se usa correctamente, ya que podemos comenzar a hacer

getfield (x :: MyType, :: Field {: size}) = .........
para i = 1: tamaño y .....

en vez de

tamaño (x :: MyType) = ..........
para i = 1: tamaño (y) ....

Si bien el punto sería excelente para acceder a elementos en colecciones (marcos de datos, dictados, PyObjects), de alguna manera puede cambiar la forma en que se accede a las propiedades del objeto (no a los campos).

Creo que una cosa a considerar es que si puede sobrecargar el campo de acceso, también debería poder sobrecargar _configurar_ un campo. De lo contrario, esto será inconsistente y frustrante. ¿Estás bien para llegar tan lejos?

@nalimilan , uno necesita absolutamente un setfield! además de getfield . (Similar a setindex! frente a getindex por [] ). No creo que esto sea controvertido.

De acuerdo con @stevengj : DataFrames definitivamente implementará setfield! para las columnas.

Yo apoyo esto.

La experiencia con otros lenguajes (por ejemplo, C # y Python) muestra que la sintaxis de puntos tiene mucho valor práctico. La forma en que se implementa a través de métodos especializados aborda en gran medida la preocupación de la regresión del desempeño.

Sin embargo, es importante asegurarse de que la _inlineability_ de un método no se vea seriamente afectada por este cambio. Por ejemplo, algo como f(x) = g(x.a) + h(x.b) no se volverá repentinamente inlineable después de esto.

Si decidimos que esto suceda, es útil también proporcionar macros para facilitar la definición de propiedad, que podría verse así:

# let A be a type, and foo a property name
<strong i="10">@property</strong> (a::A).foo = begin
    # compute the return the property value
end

# for simpler cases, this can be simplified to
<strong i="11">@property</strong> (a::A).foo2 = (2 * a.foo)

# set property 
<strong i="12">@setproperty</strong> (a::A).foo v::V begin
    # codes for setting value v to a property a.foo
end

Detrás de escena, todo esto se puede traducir a las definiciones de métodos.

No estoy convencido de que <strong i="5">@property</strong> (a::A).foo = sea ​​mucho más fácil que getproperty(a::A, ::Field{foo}) = ...

En cualquier caso, un mejor azúcar sintáctico es algo que se puede agregar después de que aterrice la funcionalidad básica.

Con respecto a la alineación, siempre que el acceso al campo esté alineado antes de que se tome la decisión de alinear la función circundante, no veo por qué se vería afectado. ¿Pero tal vez este no es el orden en el que se realiza actualmente la inserción?

getproperty(a::A, ::Field{:foo}) = me llama la atención porque hay demasiados dos puntos :-) Estoy de acuerdo en que esto es algo menor, y probablemente no tenemos que preocuparnos por eso ahora mismo.

Mi preocupación es si esto provocaría una regresión del rendimiento. No tengo muy claro el mecanismo de generación de código interno. @JeffBezanson probablemente diga algo sobre esto.

El acceso al campo es de muy bajo nivel, por lo que no haré esto sin asegurarme de que se mantenga el rendimiento.

Después de todo, no estoy convencido de que sobrecargar campos sea una buena idea. Con esta propuesta, siempre habría dos formas de configurar una propiedad: x.property = value y property!(x, value) . Si se implementa la sobrecarga de campos, necesitaremos una guía de estilo muy sólida para evitar terminar en un desastre total en el que nunca se sabe de antemano qué solución ha elegido el autor para un tipo determinado.

Y luego estaría la cuestión de si los campos son públicos o privados. No permitir la sobrecarga de campos haría que el sistema de tipos fuera más claro: los campos siempre serían privados. Los métodos serían públicos y los tipos podrían declarar que implementan interfaces / protocolos / rasgos, es decir, que proporcionan un conjunto determinado de métodos. Esto iría en contra de @stevengj 's https://github.com/JuliaLang/julia/issues/1974#issuecomment -12083268 acerca de la sobrecarga de los campos con los métodos para evitar la ruptura de una API: sólo ofrecen métodos como parte de la API, y nunca campos .

El único lugar donde me arrepentiría de la sobrecarga de campos es por DataFrames , ya que df[:a] no es tan bueno como df.a . Pero eso no parece que deba requerir por sí solo un cambio tan importante. El otro caso de uso parece ser PyCall, que puede indicar que se debe permitir la sobrecarga de campo, pero solo para casos de uso no julianos altamente específicos. Pero, ¿cómo evitar que las personas hagan un mal uso de una función una vez que esté disponible? ¿Ocultarlo en un módulo especial?

@nalimilan , diría que la preferencia debería ser utilizar la sintaxis x.property tanto como sea posible. La cosa es que a la gente le gusta mucho esta sintaxis, es muy agradable. Tomar una sintaxis tan agradable y decir específicamente que solo debería usarse para el acceso interno a objetos parece francamente perverso: "ja, esta sintaxis agradable existe; ¡no la uses!" Parece mucho más razonable hacer que la sintaxis para acceder a cosas privadas sea menos conveniente y bonita en lugar de forzar a las API a usar la sintaxis más fea. Quizás este sea un buen caso de uso para el operador .. : el operador de acceso al campo privado _real_.

De hecho, creo que este cambio puede hacer que las cosas sean más claras y coherentes en lugar de hacerlo menos. Considere los rangos: actualmente hay una especie de mezcla horrible de estilos step(r) versus r.step en este momento. Especialmente desde que presenté FloatRange esto es peligroso porque solo el código que usa step(r) es correcto. El motivo de la combinación es que algunas propiedades de los rangos se almacenan y otras se calculan, pero han cambiado con el tiempo y, de hecho, son diferentes para diferentes tipos de rangos. Sería mejor estilo si cada acceso fuera del estilo step(r) excepto la definición de step(r) sí. Pero hay algunas barreras psicológicas pronunciadas contra eso. Si hacemos r.step una llamada de método que por defecto es r..step , entonces la gente puede hacer lo que está naturalmente inclinada a hacer.

Para jugar al abogado del diablo (conmigo mismo), ¿deberíamos escribir r.length o length(r) ? La inconsistencia entre las funciones y los métodos genéricos es un problema que ha afectado a Python, mientras que Ruby se comprometió completamente con el estilo r.length .

+1 por .. como Core.getfield !

@StefanKarpinski Tiene sentido, pero luego deberá agregar sintaxis para campos privados, y las interfaces deberán especificar tanto métodos como campos públicos. Y, de hecho, necesita una guía de estilo para garantizar cierta coherencia; el caso de length es difícil, pero también existe, por ejemplo, size , que es muy similar pero necesita un índice de dimensión. Esta decisión abre una lata de gusanos ...

En ese caso, también apoyo .. para acceder a los campos reales, y . para acceder a los campos, ya sean métodos o valores reales.

Para jugar al abogado del diablo (conmigo mismo), ¿deberíamos escribir r.length o length(r) ? La inconsistencia entre las funciones y los métodos genéricos es un problema que ha afectado a Python, mientras que Ruby se comprometió completamente con el estilo r.length .

El factor clave que puede disipar la ambigüedad de este problema es si desea poder usar algo como una función de orden superior o no. Es decir, el f en f(x) es algo que puede map sobre una colección, mientras que el f en x.f no lo es (sin escribir x -> x.f ), que es la misma situación para todos los métodos en lenguajes de envío único.

¿Por qué detenerse en el acceso al campo? ¿Qué hay de tener x.foo(args...) equivalente a getfield(x::MyType, ::Field{:foo}, args...) = ... ? Entonces podríamos tener x.size(1) para el tamaño a lo largo de la primera dimensión. (No estoy seguro de si me agrada mi sugerencia, pero tal vez algo a considerar. ¿O probablemente no, ya que la gente simplemente escribirá un código similar a OO?)

Eso sería posible con esta funcionalidad. Cuál es una de las cosas que me da que pensar. No tengo ningún problema con el código de estilo oo como ese, como dije, es bastante agradable y a la gente realmente le gusta, pero introduce suficientes opciones en las formas de escribir cosas que _realmente_ necesitamos una política sólida sobre lo que debe hacer ya que serás muy libre con lo que puedas hacer.

Cuando comencé a aprender sobre Julia, la sintaxis sin puntos me ayudó mucho a dejar de lado mentalmente el estilo de programación OO. Entonces, solo por esa razón, creo que mi sugerencia es mala.

Además, para una sobrecarga simple (es decir, solo a.b sans (args...) ), estoy de acuerdo con el comentario de @nalimilan anterior. En el número 4935, el consenso parece ser que los campos no deben ser parte de la API sino solo métodos; en consecuencia, parece que ese tema se resolverá. Tener la sintaxis de sobrecarga de . hará que sea mucho menos claro que los campos normales no deberían ser parte de la API y probablemente animará a que los campos formen parte de la API.

Pero sí, la sintaxis . es conveniente ...

¿Qué tal: el único . debería _sólo_ ser azúcar sintáctico para getfield(x::MyType, ::Field{:name}) = ... y el acceso al campo es _sólo_ a través de .. (es decir, lo que . es ahora).

Esto permitiría hacer una clara distinción:

  • el . es para que la API pública acceda a cosas similares a valores de instancias de tipo
  • .. es para acceso de campo y generalmente no debe usarse en la API pública

Por supuesto, este sería un cambio radical.

Eso es básicamente lo que estaba proponiendo, excepto que . defecto es .. por lo que no se rompe.

Lo siento, debería haber vuelto a leer!

Pero creo que el . no está predeterminado en .. podría ser bueno (aparte de eso, se está rompiendo), ya que obligaría al desarrollador a tomar una decisión sobre qué es una API pública y qué no. Además, si el usuario usa un .. , puede esperar que su código se rompa, mientras que . no debería.

Ese es un buen punto. Podemos ir por esa ruta teniendo a.b predeterminado en a..b con una advertencia de depreciación.

Desde una perspectiva de estilo, creo que preferiría ver

a = [1:10]
a.length()
a.size()

que

a.length
a.size

Creo que ayuda a preservar la idea de que se está llamando a una función en lugar de simplemente recuperar una propiedad que de alguna manera está almacenada en el tipo (volviendo a la preocupación de "pureza" anterior). Me pregunto si hay alguna manera de ayudar a garantizar este tipo de estilo para que las cosas no se vuelvan tan complicadas como en otros idiomas.

Realmente no me gusta

a.length()

desde entonces no puedo decir si había un campo de función en el tipo original. Si . nunca accede a los campos, obviamente no es un problema. De lo contrario, me parece confuso.

A priori, siento que no deberíamos hacer ni a.length() ni a.length . Pero la pregunta es ¿porqué? ¿Qué hace que r.step diferente de r.length ? ¿Es diferente? Si no son diferentes, ¿deberíamos usar step(r) y length(r) o r.step y r.length ?

Con la semántica sugerida por Stefan y la adición mía, estaría claro que . siempre es una llamada de función (al igual que + también), mientras que .. siempre es un campo acceso.

Sobre la cuestión de si a.length , etc. es una buena idea: ¿qué tal si . acceso solo debe usarse para acceder a datos reales en el tipo, más o menos como se usarían las entradas de un dictado? . Mientras que nos quedamos con funciones para propiedades sin datos como size , length , step etc. Porque algunas de ellas necesitarán parámetros adicionales y, creo, a.size(1) tipo de sintaxis es malo.

Aquí está mi opinión sobre este tema:

  • La sintaxis de puntos solo debe usarse para atributos de un tipo / clase. Tenga en cuenta que esto no se trata solo de captadores sino también de setters y algo como a.property() = ... siente completamente mal.
  • Si bien me gusta la situación actual en la que la función define la API pública y los campos son privados, comparto la opinión de Stefan de que la sintaxis de puntos es demasiado agradable para prohibirla para las API públicas. Pero limitemos esto a atributos simples. a.length es un buen ejemplo, a.size(1) no porque requiera un argumento adicional.
  • Deje . predeterminado en .. . Julia no es conocida por ser un lenguaje repetitivo. Mantengámoslo de esa manera

Deje . predeterminado en .. . Julia no es conocida por ser un lenguaje repetitivo. Mantengámoslo de esa manera

Tiendo a estar de acuerdo con esto. La sintaxis para establecer incluso una propiedad sintética sería a.property = b , no a.property() = b .

Claro, solo quería dejar en claro por qué a.property() como sintaxis en mi humilde opinión no es agradable

O más claramente: lo importante sobre la sintaxis de puntos no es que uno pueda asociar funciones con tipos / clases, sino la capacidad de escribir captadores / definidores de una manera agradable. Y los captadores / definidores son importantes para la encapsulación de datos (mantenga la interfaz estable pero cambie la implementación)

Este cambio sería genial desde la perspectiva de los diseñadores de API, pero estoy de acuerdo en que debería venir con algún tipo de guía de estilo para limitar cualquier inconsistencia futura.

Esto habilitaría Ruby como dsl ...

amt = 1.dollar + 2.dollars + 3.dollars.20.cents 

Pero prepárate para la locura de Java:

object.propert1.property2.property3 ....

Solo algunos pensamientos:

  • Lo que más quiero es la sintaxis . para dictados con símbolos como claves. Es más agradable usar d.key luego d[:key] . Pero al final no es crítico.
  • Creo que a->property lee mejor que a..property . Pero de nuevo, no es tan importante y no sé si funcionaría con la sintaxis de julia.

@BobPortmann No estoy de acuerdo. Un diccionario es un objeto contenedor, la API para objetos contenedor es obj [índice] u obj [clave]. En este momento, debido a que no tenemos propiedades en Julia, la API del contenedor está sobrecargada para proporcionar esta funcionalidad en bibliotecas como PyCall y OpenCL. Este cambio ayuda a fortalecer la distinción de la API de contenedor, ya que no se sobrecargará para proporcionar funcionalidad adicional.

Usar a->property para campos privados sería una buena manera de mantener a los piratas informáticos C alejados de Julia ;-)

Me gusta la sintaxis .. .

La sintaxis a->property ya está indicada, es una función anónima. Sin embargo, el operador a..b ha estado disponible durante un tiempo. Hay algunos casos en los que desea algo similar a un dictado pero que tenga muchos campos opcionales. Usar la sintaxis getter / setter para eso sería mejor que dictar la sintaxis de indexación.

"La sintaxis de la propiedad a-> ya está indicada, es una función anónima".

Sí, por supuesto. No lo parecía sin espacios alrededor de -> .

Como pauta de estilo, ¿qué tal si se recomienda que la propiedad (x) se use para propiedades de solo lectura y que x.property se use para propiedades de lectura / escritura?

Para las propiedades de escritura, x.foo = bar es mucho más agradable que set_foo! (X, bar).

Tener foo(x) para leer y x.foo para escribir es bastante confuso. En realidad, esto es lo que las propiedades hacen tan atractivas. Tener la misma sintaxis para el acceso de lectura y escritura, es decir, la sintaxis más simple que se puede obtener (para getters y setters)

Con respecto al estilo, existe la gran pregunta de si queremos tener tanto x.length como length(x) si esta función se implementa o si la forma posterior debería quedar obsoleta y eliminarse.

Mi opinión es que solo deberíamos tener una forma de hacerlo y solo usar x.length en el futuro. Y en cuanto al estilo, creo que es bastante sencillo. Todo lo que sea una propiedad simple de un tipo debe implementarse utilizando la sintaxis de campo. Todo lo demás con funciones. He usado muchas propiedades en C # y rara vez encontré un caso en el que no estuviera seguro de si algo debería ser una propiedad o no.

Estoy en contra de cambiar un conjunto de funciones de 1 argumento elegido aleatoriamente a la sintaxis x.f . Creo que @ mauro3 hizo un buen punto de que hacer esto oscurece la naturaleza del lenguaje.

a.b es, al menos visualmente, una especie de construcción de alcance. El b no necesita ser un identificador visible globalmente. Ésta es una diferencia crucial. Por ejemplo, las factorizaciones matriciales con una parte superior tienen una propiedad .U , pero esto no es realmente una cosa genérica --- no queremos una función global U . Por supuesto, esto es un poco subjetivo, especialmente porque puede definir fácilmente U(x) = x.U . Pero length es un tipo de cosas diferente. Es más útil que sea de primera clase (por ejemplo, map(length, lst) ).

Aquí están las pautas que sugeriría. La notación foo.bar es apropiada cuando:

  1. foo realidad tiene un campo llamado bar . Ejemplo: (1:10).start .
  2. foo es una instancia de un grupo de tipos relacionados, algunos de los cuales en realidad tienen un campo llamado .bar ; incluso si foo no tiene realmente un campo bar , el valor de ese campo está implícito en su tipo. Ejemplos: (1:10).step , (0.1:0.1:0.3).step .
  3. Aunque foo no almacena explícitamente bar , almacena información equivalente en una forma más compacta o eficiente que es menos conveniente de usar. Ejemplo: lufact(rand(5,5)).U .
  4. Estás emulando una API de otra como Python o Java.

Puede tener sentido que la propiedad bar sea ​​asignable en los casos 1 y 3 pero no 2. En el caso 2, dado que no puede cambiar el tipo de un valor, no puede mutar la propiedad bar que está implícito en ese tipo. En tales casos, probablemente desee rechazar la mutación de la propiedad bar de los otros tipos relacionados, ya sea haciéndolos inmutables o haciendo explícitamente un error foo.bar = baz .

@tknopp , no estaba sugiriendo usar x.foo para escribir y foo(x) para leer. Mi sugerencia fue que _si_ una propiedad es tanto legible como de escritura, entonces probablemente desee _ambos_ leerla y escribirla con x.foo .

@StefanKarpinski : ¿Pero no es length una caja de 3. donde los tamaños se almacenan normalmente y length es el producto de los tamaños?

Sin embargo, veo que Jeffs señala que este cambio haría que estas funciones ya no fueran de primera clase.

@stevengj : Ya veo. Perdón por confundir eso.

@tknopp : la longitud se deriva de los tamaños, pero no es equivalente a ellos. Si conoce los tamaños, puede calcular la longitud, pero no al revés. Por supuesto, esta es una línea un poco borrosa. La razón principal por la que esto es aceptable para lufact es que no hemos descubierto una API mejor que esa. Otro enfoque sería definir funciones genéricas upper y lower que den las partes triangular superior e inferior de las matrices generales. Sin embargo, este enfoque no se generaliza a las factorizaciones QR, por ejemplo.

Es revelador que solo hay unos pocos casos en los que _realmente_ parecen pedir esta sintaxis: pycall, factorizaciones y tal vez marcos de datos.
Estoy bastante preocupado por terminar con un revoltijo aleatorio de f(x) vs. x.f ; haría que el sistema fuera mucho más difícil de aprender.

¿No significa el punto 1 de la lista de @StefanKarpinski que cualquier campo de un tipo pertenece automáticamente a la API pública?

Por el momento puedo decir cuál es la API pública de un módulo: todas las funciones y tipos exportados (pero no sus campos). Después de este cambio, no sería posible saber qué campos se supone que pertenecen a la API pública y cuáles no. Podríamos comenzar a nombrar campos privados a._foo o menos, como en Python, pero eso no parece tan agradable.

Personalmente creo que el caso de DataFrames es un poco superfluo. Si hacemos esto, agregaré la funcionalidad a DataFrames, pero encuentro la pérdida de consistencia mucho más preocupante que guardar algunos caracteres.

Tampoco haría que la decisión dependa de DataFrames, PyCall (y Gtk también lo quiere). O lo queremos porque pensamos que los campos deberían ser parte de una interfaz pública (porque "se ve bien") o no lo queremos.

... pycall ...

y JavaCall

Dado que el caso de uso principal para esto parece ser interacciones con sistemas que no son de Julia, ¿qué pasa con el uso del operador .. lugar de sobrecargar . ?

Me pregunto si una solución más simple aquí es un truco más general para OO:

#we already do
A[b] => getindex(A,b)
#we could have
A.b(args...) => b(A, args...)
# while
A..b => getfield(A,::Field{:b})
# with default
getfield(A, ::Field{:b}) = getfield(A, :b)

Parece que esto permitiría a JavaCall / PyCall hacer definiciones de métodos "en" clases, mientras que también permitiría un estilo general si la gente quiere tener algún código de tipo OO, aunque es muy transparente. A.b() es solo una reescritura. Creo que esto sería muy natural para las personas que vienen de OO.
También tener el nuevo getfield con A..b para permitir la sobrecarga allí, aunque se desaconseja enfáticamente la sobrecarga aquí y solo se debe usar para propiedades / de campo (sospecho que no se usaría muy ampliamente debido al leve miedo de sobrecargar getfield(A, ::Field{:field}) .

@ mauro3 :

¿No significa el punto 1 de la lista de @StefanKarpinski que cualquier campo de un tipo pertenece automáticamente a la API pública?

Esa fue una lista de cuándo está bien usar la notación foo.bar , no cuando es necesario. Puede deshabilitar la notación foo.bar para los campos "privados", a los que solo se podrá acceder a través de foo..bar .

@karbarcca : No tengo muy claro lo que propones aquí.

fwiw, soy un fanático de adoptar el enfoque de adultos que consienten por convención y hacer que . pueda sobrecargar completamente. Creo que la propuesta del doble punto generaría más confusión en lugar de menos.

@ihnorton : ¿está en contra de usar a..b como sintaxis principal (no sobrecargable) para el acceso al campo o en contra de usar a..b para la sintaxis de sobrecarga?

Una de las mejores características de julia es su sencillez. Sobrecargar x.y siente como el primer paso en el camino hacia C ++.

@StefanKarpinski, pero esto significaría un gran cambio de paradigma de los campos privados predeterminados a los campos públicos predeterminados.

Una comprensión que acabo de tener, probablemente esto fue claro para otros todo el tiempo. Se puede realizar una programación completa al estilo OO con la sobrecarga básica de . (aunque sea fea). Definiendo

getfield(x::MyType, ::Field{:foo}) = args -> foofun(x, args...) # a method, i.e. returns a function
getfield(x::MyType, ::Field{:bar}) = x..bar+2                  # field access, i.e. returns a value

luego x.foo(a,b) y x.bar funcionan. Entonces, la discusión sobre si x.size(1) debe implementarse o solo x.size es discutible.

@StefanKarpinski contra a..b generalmente sobrecargables y tibio sobre a..b -> Core.getfield(a,b) .

Empiezo a ver la necesidad de otro operador aquí, pero a..b no es muy convincente. Necesitar dos personajes se siente muy ... de segunda clase. Tal vez a@b , a$b o a|b (los operadores bit a bit no se usan con tanta frecuencia). Una posibilidad externa también es a b`, que el analizador probablemente podría distinguir de los comandos.

Estaría bien con el uso del operador "feo" para el acceso al campo primitivo. Creo que la experiencia ha demostrado que, dado que es una operación concreta, rara vez se usa y, de hecho, es algo peligroso de usar.

Sugiero que se permita simular el envío único de OO mediante la convención / reescritura:

type Type end
# I can define methods with my Type as 1st argument
method(T, args...) = # method body
t = Type()
# then I can call that method, exactly like Java/Python methods, via:
t.method(args...)
# so
t.method(args...) 
# is just a rewrite to
method(t, args...)

La justificación aquí es que ya hacemos reescrituras de sintaxis similares para getindex / setindex !, así que permitamos la sintaxis completa de OO con esto. De esa forma, PyCall y JavaCall no tienen que hacer

my_dna[:find]("ACT")
# they can do
my_dna.find("ACT")
# by defining the appropriate find( ::PyObject, args...) method when importing modules from Python/Java

Me gusta esto porque es una transformación bastante clara, al igual que getindex / setindex, pero permite simular un único sistema OO de envío si se desea, particularmente para paquetes de idioma OO.

Entonces estaba sugiriendo el uso del operador .. para el acceso al campo, con la opción de sobrecargar. El uso aquí permitiría a PyCall / JavaCall simular el acceso al campo sobrecargando las llamadas a .. , permitiendo que DataFrames sobrecargue .. para el acceso a la columna, etc. Este también sería el nuevo acceso al campo predeterminado en general para cualquier tipo.

Tengo debilidad por las reescrituras de sintaxis pura. Podría decirse que es algo malo que pueda escribir a.f(x) ahora mismo y hacer que funcione, pero que signifique algo confusamente diferente a la mayoría de los lenguajes OO.

Por supuesto, la otra cara de esa moneda es la horrible fragmentación de estilo, y el hecho de que a.f no tiene nada en común con a.f() , lo que hace que la ilusión se rompa rápidamente.

Una de las mejores características de julia es su sencillez. Sobrecargar x.y siente como el primer paso en el camino hacia C ++.

El mismo sentimiento aquí. Estaba considerando, si la necesidad real de esto es realmente para un número limitado de tipos de interoperabilidad, ¿qué tal si solo lo hace válido si se pregunta explícitamente en la declaración de tipo? Por ejemplo, una palabra clave adicional además de type y immutable podría ser ootype o algo así.

y el hecho de que af no tiene nada en común con af (), provocando que la ilusión se rompa rápidamente.

¿Puedes aclarar qué significa esto @JeffBezanson?

Esperaría que a.f sea ​​algún tipo de objeto de método si a.f() funciona.

Ah, lo tengo. Sí, definitivamente no podrías hacer algo como map(t.method,collection) .

Estoy de acuerdo con @ mauro3 en que al permitir obj.method(...) , existe el riesgo de que los nuevos usuarios vean a julia como otro lenguaje orientado a objetos que intenta competir con python, ruby, etc., y no lo aprecien completamente. la maravilla que es el envío múltiple. El otro riesgo es que el estilo oo estándar se vuelva predominante, ya que es con lo que los usuarios están más familiarizados, a diferencia del estilo más juliano desarrollado hasta ahora.

Dado que el caso de uso, aparte de DataFrames, está restringido a la interacción con idiomas oo, ¿podría todo esto ser manejado por macros? es decir, <strong i="8">@oo</strong> obj.method(a) convierte en method(obj,a) ?

@karbarcca esto significaría que automáticamente todo se podría escribir de dos formas:

x = 3
x.sin()
sin(x)
x + 2
x.+(2) # ?!

@karbarcca https://github.com/JuliaLang/julia/issues/1974#issuecomment -38830330

t.method (argumentos ...)
# es solo una reescritura de
método (t, args ...)

Eso no sería necesario para PyCall ya que el punto sobrecargable podría usarse para llamar a pyobj[:func] por pyobj.func . Entonces pyobj.func() sería de hecho (pyobj.func)() .

Reescribir a.foo(x) como foo(a, x) no resolvería el problema de PyCall, porque foo no es y no puede ser un método de Julia, es algo que necesito buscar dinámicamente en tiempo de ejecución . Necesito reescribir a.foo(x) como getfield(a, Field{:foo})(x) o similar [o posiblemente como getfield(a, Field{:foo}, x) ] para que mi getfield{S}(::PyObject, ::Type{Field{S}}) pueda hacer lo correcto.

@JeffBezanson https://github.com/JuliaLang/julia/issues/1974#issuecomment -38837755

Empiezo a ver la necesidad de otro operador aquí, pero a..b no es muy convincente. Necesitar dos personajes se siente muy ... de segunda clase.

Yo diría que, por otro lado, .. se escribe mucho más rápido que $ , @ o | ya que no es necesario presionar la tecla Mayús , y siendo dos caracteres el dedo permanece en la misma tecla: sonrisa:

@stevengj Ah, ya veo. Pero mi punto sigue en pie, que la reescritura se podría hacer con una macro.

Para JavaCall, en realidad solo necesito esencialmente un controlador de propiedad desconocida. En realidad, no necesito reescribir o interceptar la propiedad existente de lectura o escritura. Entonces, ¿una regla de que "ax se reescribe en getfield (a,: x) sólo cuando x no es una propiedad existente" ayudaría a mantener las cosas cuerdas?

@simonbyrne , requerir una macro vencería el deseo de llamadas interlenguajes limpias y transparentes. Además, sería difícil hacer que funcione de manera confiable. Por ejemplo, suponga que tiene type Foo; p::PyObject; end , y para un objeto f::Foo desea hacer foo.p.bar donde bar es una búsqueda de propiedad de Python. Es difícil imaginar una macro que pueda distinguir de manera confiable los significados de los dos puntos en foo.p.bar .

Honestamente, no veo el gran problema con el estilo. Los paquetes de alta calidad imitarán el estilo de Base y otros paquetes cuando sea posible, y algunas personas escribirán código extraño sin importar lo que hagamos. Si incluimos la sobrecarga de puntos en una sección posterior del manual y recomendamos su uso solo en unos pocos casos cuidadosamente seleccionados (por ejemplo, interoperabilidad entre idiomas, propiedades de lectura / escritura, tal vez para evitar la contaminación del espacio de nombres para cosas como factor.U , y en general como una alternativa más limpia a foo[:bar] ), entonces no creo que seamos invadidos por paquetes que usen dot para todo. Lo principal es decidir qué usaremos y para qué lo recomendaremos, y probablemente deberíamos mantener la lista de usos recomendados muy breve y solo ampliarla cuando surjan necesidades del mundo real.

No estamos agregando una sintaxis tipo OO súper fácil como type Foo; bar(...) = ....; end por foo.bar(...) , por lo que eso también limitará la tentación para los novatos.

Básicamente estoy totalmente de acuerdo con @stevengj aquí. Me gusta a..b para el acceso al campo real porque

  1. parece similar a a.b
  2. es menos conveniente, como debería ser
  3. es solo un poco menos conveniente
  4. no tiene un significado existente y no hemos encontrado ningún uso convincente para él en más de un año
  5. no es horriblemente extraño como a b`

Con este cambio y posiblemente (https://github.com/JuliaLang/julia/issues/2403), ¿se podrá sobrecargar casi toda la sintaxis de Julia? (El operador ternario es la única excepción en la que puedo pensar) El hecho de que casi toda la sintaxis se reduzca al envío de métodos sobrecargadas me parece una característica fuertemente unificadora.

Estoy de acuerdo en que en realidad es una especie de simplificación. El operador ternario y && y || son realmente el flujo de control, así que eso es algo diferente. Por supuesto, eso es un argumento en contra de hacer que a..b el acceso al campo real desde entonces _ esa_ sería la única sintaxis no sobrecargable. Pero sigo pensando que es una buena idea. La consistencia es buena pero no primordial por sí misma.

Oh, también hay una llamada a la función que no se puede sobrecargar. Tan básico que lo olvidé.

Eso es lo que aborda el problema # 2403.

Sí. Pero esto está mucho más cerca de suceder que eso.

La única pega aquí para mí es que sería muy bueno usar el operador de acceso al campo real para los módulos, pero eso probablemente no sucederá ya que nadie quiere escribir Package..foo .

Completar tabuladores después de puntos se vuelve un poco feo; técnicamente, debe verificar qué método x. podría llamar para ver si es apropiado enumerar los nombres de campo de objeto o los nombres de módulo. Y espero que nadie intente definir getfield(::Module, ...) .

Creo que completar la pestaña se puede hacer así: foo.<tab> enumera los "campos públicos" y foo..<tab> enumera los "campos privados". Para los módulos, ¿estaría bien permitir que la implementación predeterminada de Mod.foo sea Mod..foo y decirle a la gente que no agregue métodos getfield a Module ? Quiero decir, ya puedes redefinir la suma de enteros en el lenguaje: se desata el infierno y obtienes un error de segmento, pero no intentamos evitarlo. Esto no puede ser peor que eso, ¿verdad?

De hecho, es un poco peor que eso, porque un lenguaje de programación realmente solo se preocupa por nombrar. Resolver nombres es mucho más importante que sumar números enteros.

No tenemos muchas opciones más que tener Mod.foo predeterminado en Mod..foo , pero probablemente tendremos que usar Mod..foo para el arranque en algunos lugares. El operador .. es extremadamente útil aquí, ya que sin él ni siquiera puedes llamar a Core.getfield para definir el respaldo. Con él, probablemente eliminaríamos Core.getfield y solo tendríamos .. .

Ese es un punto justo: nombrar es algo importante en la programación :-). Parece un buen camino a seguir: solo .. y no Core.getfield .

Estas dos ideas,

[...] coloque la sobrecarga de puntos en una sección posterior del manual y recomiende su uso solo en unos pocos casos cuidadosamente seleccionados @stevengj https://github.com/JuliaLang/julia/issues/1974#issuecomment -38847340

y

[...] la preferencia debe ser utilizar la sintaxis x.property tanto como sea posible @StefanKarpinski https://github.com/JuliaLang/julia/issues/1974#issuecomment -38694885

se oponen claramente.

Creo que si se elige la primera idea, entonces tiene más sentido crear un nuevo operador .. para esos "casos cuidadosamente seleccionados".
Como ventaja, usar ..name para los casos en los que actualmente se usa [:name] (DataFrames, Dict {Symbol, ...}) sería más amigable con la escritura / sintaxis al tiempo que indica claramente que algo diferente del acceso de campo estaba pasando. Además, el punto doble en ..name podría verse como dos puntos rotados, una pista a la sintaxis del símbolo :name , y tampoco habría ningún problema con la finalización de las pestañas.
Como desventaja, los usos en PyCall et al. no estaría tan cerca de las sintaxis originales (e incluso podría ser confuso para los casos en que . realmente debe usarse). Pero seamos honestos, Julia nunca será completamente compatible con la sintaxis de Python, y siempre habrá casos en los que uno tenga que escribir mucho en Julia con PyCall para realizar instrucciones que de otra manera serían simples en Python. El .. para emular . podría dar un buen saldo aquí. (Por favor, no me malinterpretes, me gusta mucho PyCall y creo que es una característica fundamental que merece un cuidado especial)

La segunda idea, que prefiero actualmente, tiene la gran decisión sobre cuándo se debe usar property(x) o x.property , lo que requiere una definición elegante, buena y clara, si tal cosa existe. .
Sin embargo, parece que si la gente quiere un . sobrecargable, es porque prefieren el estilo API x.property en primer lugar.
De todos modos, preferiría ver . no como un operador de acceso de campo sobrecargable sino como un operador de acceso de "propiedad" sobrecargable ( getprop(a, Field{:foo}) tal vez?) Que por defecto es un operador de campo no sobrecargable .. .
También deberían tomarse otras decisiones, por ejemplo, ¿cuál se utilizará en el código de implementación concreto para el acceso al campo, .. o . ? Por ejemplo, para el ejemplo del paso Rangos, ¿cuál será idiomático? step(r::Range1) = one(r..start) o step(r::Range1) = one(r.start) ? (sin mencionar la pregunta de si step debe ser un método o una propiedad).

Por eso me aparté de ese ángulo y propuse estos criterios: https://github.com/JuliaLang/julia/issues/1974#issuecomment -38812139.

Solo un pensamiento me vino a la cabeza mientras leía este interesante hilo. Exportar podría usarse para declarar campos públicos, mientras que todos los campos son visibles dentro del módulo de definición, por ejemplo:

module Foo
   type Person
     name
     age
   end
   export Person, Person.name
   <strong i="6">@property</strong> Person :age(person) = person..age + 1
end

En esta situación, la Persona exportada todavía se ve como 'nombre' y 'edad' excepto que en este caso la edad es de solo lectura a través de una función que agrega una. La exportación de todas las personas se puede realizar como exportar Person. * O similar.

[pao: citas]

@emeseles Tenga cuidado de usar comillas invertidas para citar cosas que sean como el código de Julia; esto asegura que se mantenga el formato y evita que las macros de Julia creen notificaciones de GitHub para usuarios con nombres similares.

. y .. son confusos: una sintaxis clara y fácil de recordar es algo bueno

Tengo muchas ganas de poder hacer esto. ¿Es este un cambio lo suficientemente grande como para marcarlo (o el WIP en # 5848) como un proyecto 0.4?

Sí, definitivamente es un proyecto.

Creo que la mayoría de nosotros estamos de acuerdo en que los usos recomendados deben ser limitados, al menos al principio. Mi sensación es que debería recomendarse inicialmente solo para dos usos: interoperabilidad (con otros lenguajes, como en PyCall, y más generalmente para bibliotecas externas donde la notación de puntos es natural) y quizás para objetos con estado mutable (desde get_foo(x) y set_foo!(x, val) son feos).

Incluso si lo recomendamos solo para la interoperabilidad de llamadas extranjeras, ese propósito por sí solo es suficiente para justificar esta característica en mi opinión. Para un nuevo lenguaje como Julia, hablar sin problemas con el resto del universo del software es muy importante.

Steven, no estoy 100% seguro del getter / setter porque temo que pronto dará lugar a inconsistencias, pero estoy de acuerdo con el otro caso de uso. Además de eso, tenemos en Gtk.jl propiedades dinámicas que también se beneficiarían de la sintaxis. Sin embargo, mi favorito personal es la implementación de enumeración que Stefan describió en el n. ° 5842.

Bache. ¿Qué está bloqueando el progreso en este tema? ¿Se necesita una decisión, o este problema depende de otros cambios internos, aún no se ha hecho o es solo codificación?

¿Qué está bloqueando el progreso en este tema?

Alguien haciendo el trabajo y algunas dudas sobre si es lo correcto.

Tenga en cuenta que @ihnorton ya realizó una implementación preliminar en el # 5848. Creo que el trabajo se ha estancado principalmente porque se necesita una declaración clara del equipo central de Julia sobre si esta es una característica deseada.

Estoy de acuerdo con esto. @JeffBezanson parece estar indeciso.

Para mí, tener esta función facilitaría la transición de nuestra gran base de código Python a Julia. Para explicar a los estudiantes, si usan código Python, necesitan una sintaxis bastante diferente a la que están acostumbrados, esto podría resultar difícil.

Tuvimos esta discusión arriba en este hilo y todavía no puedo ver un acuerdo completo. Actualmente, varias personas piensan que una API pública está hecha de funciones / métodos, mientras que la API privada son los campos de un tipo compuesto. Puedo ver excepciones muy raras de este esquema. ( .U en una descomposición LU?)

Esto no significa que esté en contra de esto porque el acceso a Python y las enumeraciones son casos en los que esto es útil. Aún así, uno puede preguntarse qué tan urgente es la necesidad aquí y si sería prudente impulsar esto al final de un ciclo de desarrollo.

@ ufechner7 , estoy de acuerdo en que la principal motivación es la interoperabilidad entre idiomas. @tknopp , nunca vamos a lograr un acuerdo unánime en algo como esto. En última instancia, todo se reduce a lo que decidan @JeffBezanson y @StefanKarpinski .

Creo que muchas de las dudas provienen de lo que imagino que puede ser la peor pesadilla de Jeff:

module DotOrientedProgramming
  Base.getfield(x, ::Field{:size}) = size(x)
  Base.getfield(x, ::Field{:length}) = length(x)
  ⋮
end

También me desagradaría mucho esto: cualquier paquete que decida hacer un mal uso de él de esta manera impondrá su mal uso a todos los tipos del sistema, incluido el mío. Esta función es muy poderosa y cambiará la forma en que se escribe Julia. Para bien y (quizás, pero con suerte no) para peor.

Sí, claro, Steven, es posible que no lo haya redactado correctamente. El punto que quería señalar es que este cambio puede tener una gran influencia en cómo evolucionará el idioma. Y la idea de "interfaz formal" que tenemos en otro número también influye al hacer que . pueda sobrecargar. Así que sí, deja que @JeffBezanson y @StefanKarpinski decidan. Aún así, la pregunta es si la decisión debe hacerse cumplir ahora ...

Por si sirve de algo, he llegado a favorecer hacer que casi toda la sintaxis se pueda sobrecargar y luego confiar en la cultura para resistir a volverse loco con ella.

+1 . Creo que hay un fuerte análogo filosófico (y posiblemente práctico ...) a la sobrecarga de call aquí. El manual necesita una sección titulada Don't do stupid stuff: we won't optimize that . (por supuesto, la sobrecarga de call fue en parte _por_ razones de rendimiento, pero está plagada de posibilidades de abuso)

  • : 100: a eso. Creo que la cultura debería ser suficiente motivación para no abusar de esto.

En general, estoy a favor. El potencial de abuso no es mi mayor preocupación. Para mi los grandes problemas son

  • Sintaxis aceptable para el acceso al campo "real". No me gusta mucho a..b .
  • Módulos. Los nombres calificados son extremadamente importantes. Expresarlos con método de envío es posible, pero tiene dificultades prácticas. Es decir, debe pasar por muchas fases del compilador (tal vez incluso a través de la inserción) solo para saber que tiene un nombre calificado. Esto dificulta la vida de cualquiera que escriba herramientas que consuman AST. También hace que sea muy fácil manejar este caso incorrectamente en tales herramientas.

Estos problemas podrían resolverse de un solo golpe usando la misma sintaxis para ambos, pero es casi imposible imaginar usar algo que no sea . para módulos en este momento. _Internalmente_ definitivamente habrá una sintaxis abstracta para las referencias del módulo; sería frustrante si no hubiera una buena manera de exponer eso.

Mis dos centavos en esta pregunta: ¿por qué no usar : para nombres calificados? Ya está en uso para algo similar:

import Base: call, show, size

Esto daría algo como

module Foo
    module Bar
        f(x) = 3*x
    end
    const a = 42
end

<strong i="10">@assert</strong> Foo:a == 42

Foo:Bar:f(789)

¿O ya han usado demasiado el símbolo : ? El símbolo :: (estilo C ++) parece ser demasiado detallado para mí.

El : ya es el símbolo más sobrecargado en Julia, así que me temo que eso no va a ayudar.

¿Podemos simplificar el problema de los nombres calificados haciendo que module.name no se pueda sobrecargar? Dado que los enlaces de módulo son constantes, eso nos permitiría mantener la misma semántica pero cortocircuitar toda la lógica normal para búsquedas de nombres calificados tan pronto como se sepa que el LHS de a.b es un módulo. Creo que es bastante razonable no permitir que las personas anulen lo que significa buscar un nombre en un módulo.

Prefiero la sintaxis a..b para el acceso al campo real. ¿Cuál es tu objeción?

Aparte: Me hubiera gustado haber elegido ( ) para listas de importación como algunos de los lenguajes funcionales. Es decir:

import Base (call, show, size)

Mi razón es que podríamos hacer que las comas sean opcionales y permitir las comas finales. Realmente me molesta que todos los nombres importados necesiten comas finales, excepto el último que no puede tener una.

Sí, estaba a punto de mencionar la posibilidad de hacer que a.b signifique "si a es un módulo, entonces busque el módulo primero". Eso podría ayudar, y ciertamente no queremos anular el significado de la búsqueda de módulos. Sin embargo, tiene cierto costo de complejidad, ya que entonces no podemos representar a.b como la llamada getfield(a,:b) . Debe ser un nodo AST especial con una rama implícita. Por supuesto, podríamos usar una rama _explicit_, pero me preocuparía la hinchazón de AST por eso.

No parece haber una manera fácil de salir de un conflicto tan grande entre las necesidades del front end y el back end.

Si a todos los demás les gusta a..b , supongo que puedo aprender a vivir con eso. Simplemente me parece que significa algo totalmente diferente, quizás un intervalo.

No me gusta a..b también, pero me pregunto por qué sería necesario. Al leer este hilo, uno tiene la impresión de que la sobrecarga solo se utilizará en envoltorios de idioma y casos de uso dinámicos donde no se requiere el acceso al campo real.

Porque en algún momento necesitas acceder a la representación de un objeto para poder hacer algo con él. Se podría argumentar que esto sería relativamente raro y, por lo tanto, puede ser feo como get_actual_field(a,:x) , pero parece una operación demasiado importante para no tener sintaxis.

Ya veo eso, pero parece que buscamos una sintaxis que no queremos que nadie use, ¿verdad?

No proporcionar .. sería una forma de decir sí para los casos de uso dinámicos pero no para la programación orientada a puntos

No veo cómo eso evitaría la programación orientada a puntos; aún puede hacer el ejemplo de @mbauman anterior.

Si bien la sintaxis a..b parece un intervalo (lo he usado como tal), simplemente no creo que la aritmética de intervalos necesite su propia sintaxis de entrada: escribir Interval(a,b) es solo bien y no hay mucho más para lo que nadie quiera usar esa sintaxis, ya que ha sido un operador en Julia durante años y nadie la está usando para mucho de nada. También parece un acceso de campo.

Un lado positivo de esto es que podemos reemplazar el horrible module_name con m..name . No poder acceder a los campos de los objetos del módulo ha sido una verruga.

Sí, estaba a punto de mencionar la posibilidad de hacer que a.b signifique "si a es un módulo, entonces busque el módulo primero". Eso podría ayudar, y ciertamente no queremos anular el significado de la búsqueda de módulos. Sin embargo, tiene cierto costo de complejidad, ya que entonces no podemos representar a.b como la llamada getfield(a,:b) . Debe ser un nodo AST especial con una rama implícita. Por supuesto, podríamos usar una rama _explicit_, pero me preocuparía la hinchazón de AST por eso.

¿Podríamos manejar esto haciendo que a.b signifique incondicionalmente getfield(a,:b) y luego convirtiendo en un error agregar métodos a getfield que se cruzan con el método getfield(::Module, ::Field) ? Es una forma extraña de hacer cumplir ese comportamiento, pero en última instancia tendría el mismo efecto. Luego, la reducción podría usar el hecho de que sabemos que no puede hacer eso para hacer trampa y reducir module.name para la búsqueda de nombres calificados.

Ok, lo digo al revés: ¿Alguien en este hilo usaría .. y, en caso afirmativo, cuál sería un caso de uso ejemplar? (es decir, podría estar completamente oculto el acceso al campo interno)

@StefanKarpinski Sí, eso podría funcionar. Podría ser otro caso en el que queramos algún tipo de métodos "sellados".

@tknopp Accediendo a module..name y module..parent :) Además, solo para aclarar, ¿está defendiendo la sintaxis de llamada de función como get(obj,:field) para el acceso de campo de bajo nivel?

No, no estoy defendiendo una determinada sintaxis. Simplemente creo que sería bueno asegurarse de por qué se necesita esta función y cuáles son los casos de uso. Para los casos de uso dinámicos, estaría bien que

  • a.b es un acceso de campo si Base.getfield(a, ::Field{:b}) no se ha definido
  • a.b es la versión dinámica si se define Base.getfield(a, ::Field{:b}) . En este caso, el acceso al campo real podría estar sombreado.

Mi pregunta era si hay casos de uso en los que el sombreado no está bien.

Si; es posible que desee definir pyobject.x para que siempre se busque x en el diccionario de pyobject, para todos los x . Entonces se necesita un mecanismo separado para acceder a los campos julia del pyobject.

Ahhh, ¿entonces es todo o nada? De alguna manera tuve la impresión de que uno podría tener

type A
  c
end

Base.getfield(a::A, ::Field{:b}) = 3

a = A(1)

a.c # This still calls the field access
a.b # This calls the function

Sí, puedes hacer eso, pero no todos los objetos lo harán. Algunos querrán definir getfield(a::A, ::Field) para interceptar todos los campos.

Ok, gracias ahora lo entiendo. Todos los casos de uso dinámicos querrían getfield(a::A, ::Field) y, por lo tanto, necesitarían alguna forma de llamar a los campos internos.

Entonces mi opinión es que Core.getfield es suficiente a menos que alguien encuentre un caso de uso práctico en el que esto sea molesto.

Esto probablemente sea un hecho, pero también permitiremos anular setfield! , ¿verdad? Realmente me gustaría eso para exponer vistas mutables en una base de datos en la que las filas se convierten en tipos.

Sí, esa fue mi impresión.

Ok, en mi humilde opinión, si usar .. para el acceso al campo real o Core.getfield no es tan importante. Se podría introducir la característica general como experimental y hacer que esto esté sujeto a cambios.

La pregunta es si esto encajará en el marco de tiempo de 0.4 o no. Entonces, ¿está cerca de la implementación final y el módulo tiene solución?

@johnmyleswhite : también votaría por hacer esto simétrico y también permitir setfield! . En Gtk.jl usaríamos ambos.

No parece muy claro cuál sería la regla de cuándo utilizar esta función y cuándo no. Veo el punto para PyCall, donde un método / campo debe buscarse dinámicamente y, por lo tanto, no puede ser un método / tipo compuesto de Julia (y la sintaxis resultante está más cerca de Python). Pero entonces, ¿por qué usarlo para Gtk.jl? Si comienza a hacer foo.bar = x lugar de setbar!(foo, x) , entonces el código estándar de Julia también comenzará a usar este patrón: ¿es esto lo que queremos? Quizás lo sea, pero seamos claros al respecto.

¿Sería aceptable / recomendado utilizar esta función para implementar captadores y definidores de propiedades definidos para tipos abstractos (y también concretos)?
Supongo que eso permitiría evitar el choque de nombres de métodos que se utilizan para obtener propiedades de diferentes tipos de módulos diferentes.

Ref .: https://github.com/JuliaLang/julia/issues/4345 , https://groups.google.com/forum/#!msg/julia -users / p5-lVNlDC8U / 6PYcvvsg29UJ

@nalimilan : Gtk tiene un sistema de propiedades dinámicas, no se trata de getters / setters.

@tknopp Ah, de acuerdo, de hecho. Pero para las propiedades más comunes tiene una función de captador / definidor (rápido), más la propiedad dinámica. Entonces, ¿recomendaría usar la función getter / setter cuando esté disponible, y la sintaxis de sobrecarga de campos solo para propiedades que no tienen una? Me parece bien, pero es bueno tener una política clara sobre esto en mi humilde opinión.

En mi opinión, en este punto (y creo que debemos experimentar un poco con esto para descubrir las reglas correctas), f(x) es mejor cuando f tiene sentido como un concepto general independiente como " length "mientras que x.f debe usarse cuando f no es realmente independiente de x . Para tratar de encajar mi ejemplo anterior en eso, no es realmente útil tener una función step genérica ya que la mayoría de los vectores y colecciones no tienen ningún tipo de noción de paso; _sólo_ tiene sentido cuando tienes un rango De algún tipo. Por lo tanto, está bien que x.step sea ​​la forma de obtener el paso de un rango x . Es un poco de juicio, pero supongo que la vida está llena de eso.

No me gusta .. ya que no me transmite acceso directo. ¿Qué tal foo.bar. El punto adicional al final lo fija para que sea acceso directo?

También podría elegir un símbolo Unicode: todavía nos quedan muchos ...

@GlenHertz , eso realmente no funciona si tiene que encadenar accesos de campo.

@simonbyrne , generalmente estoy en contra de tener algo en el lenguaje central o en la biblioteca estándar que requiera el uso de Unicode. Permitirlo es una cosa, obligar a las personas a usarlo es otra completamente distinta.

En mi opinión, en este punto (y creo que debemos experimentar un poco con esto para descubrir las reglas correctas), f (x) es mejor cuando f tiene sentido como un concepto general independiente como "longitud", mientras que xf debería usarse cuando f no es realmente independiente de x.

Mi regla personal para usar esta función será: use esto solo para interoperabilidad de idiomas o para cosas que sean "casi campos" o "campos aumentados". Por ejemplo, podría usar esto para actualizar una caché que depende del valor de todos los campos de un tipo.

Una gran pregunta que tengo sobre esta función: ¿cómo interactúa esto con la inferencia de tipo? Parece que está a punto de definir una función getfield(x::T, s::Symbol) que produce una salida con tipos diferentes para diferentes valores de s . ¿Eso solo funciona porque getfield es mágico? ¿Puede redefinir la salida de getfield(x, s) para x fijos y s en cualquier punto de un programa? Si es así, ¿cómo encaja eso con la incapacidad de redefinir un tipo?

Parece que está a punto de definir una función getfield(x::T, s::Symbol) que produce una salida con tipos diferentes para diferentes valores de s .

Es por eso que el plan es expresar esto como getfield{s}(x::T, f::Field{s}) donde s es un símbolo.

Me había perdido eso. Gracias por corregirme.

@nalimilan : Sí, los campos sobrecargados solo se usarían para las propiedades dinámicas. Así es como Jameson quiere abordar esto y creo que es bueno. Todos los getters y setters reales se generan automáticamente, pero siguen funcionando sin todos los nombres de get / set. La vida en el módulo GAccessor (corto G_ )

En cuanto a la sintaxis, ¿por qué no usar <- para el acceso al campo real?
Es similar a -> en c ++ que está en uso para lamdas pero <- actualmente no se usa.
Se puede leer a partir del tipo, obtener el valor directamente.

Dejaría ... sin usar para aquellos que quieran usarlo en intervalos todavía y estaría usando un par sin usar que no tiene otros usos en los que puedo pensar hasta ahora.

No usemos la notación de asignación de R para el acceso al campo.

Podríamos usar -> para reflejar C / C ++ directamente y obtener una nueva sintaxis para
funciones anónimas. Nunca me ha gustado mucho la función anónima
sintaxis ya que es un poco conciso / ilegible. Quizás podríamos hacer en su lugar
algo como

func (x) x ^ 2 final

o el más largo, más consistente

función (x) x ^ 2 end

Tal vez haya una forma de encontrar una buena sintaxis que no requiera
usando un final.

No cambiar demasiado la discusión, pero definitivamente tendría sentido.
utilizar -> para acceso real al campo.

El miércoles 28 de enero de 2015 a las 8:49 a. M., John Myles White [email protected]
escribió:

No usemos la notación de asignación de R para el acceso al campo.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -71857083.

@quinnj : func (x) x^2 end ya funciona. Pero es bueno tener una sintaxis muy concisa para funciones anónimas: map(x->x^2, 1:10)

No creo que el acceso al campo necesite una sintaxis especial (un carácter Unicode como sugirió @simonbyrne está bien como opción). No quisiera perder x -> x^2 .

Parece que este tema aún está abierto / pendiente de discusión. Ha sido interesante leer todos los comentarios aquí sobre el operador de puntos.

¿Ha habido alguna sugerencia para agregar otros tokens de operador nuevos? Usar algo como :> podría ser una buena alternativa. Tiene paralelismos con |> y podría tener un _sentimiento_ más nativo de Julia:

c = foo.a + foo.b
pyobj:>obj:>process(c)

Aunque sigue siendo mucho más fácil de escribir que:

pyobj[:obj][:procees](c)

O comparando:

someobj :> array |> length 
# vs
length(get_array(someobj)) 

Soy nuevo en Julia, pero rápidamente he ganado una gran apreciación por el enfoque de envío múltiple. La programación orientada a objetos, especialmente para la programación científica, hace que muchas tareas sean más complicadas. Me preocuparía que el paradigma / sintaxis de OO afectara negativamente el desarrollo del estilo de Julia.

O alternativamente, ya que me olvidé de la pasantía de cuerdas y / o las citas:

someobj <: field_name |> length

@elcritch , <: se usa actualmente como el operador "es subtipo de" de Julia, y si se introduce :> , es probable que queramos usarlo para algo relacionado con el tipo debido a eso patrimonio.

Si usar instance..member es un problema, aquí hay algunas posibilidades. ¡Protéjase los ojos! Es probable que cada uno de estos sea peor:

  • instance^.member (punto de zanahoria)
  • instance~.member (punto tilde)
  • instance:.member (dos puntos)
  • instance*.member (estrella punto)
  • instance-.member (punto de guión)
  • [email protected] (punto de signo arroba)
  • instance&.member (punto inicial)
  • instance$.member (punto de dólar)
  • instance<.>member (punto de nave espacial)

Honestamente, creo que (a) .. parece lo suficientemente bueno y (b) realmente no importa si se ve bien porque este siempre será un rincón oscuro del lenguaje. La mayoría de las personas usarán instance.member porque solo tendrán un campo o un método getfield , pero no ambos.

(Para el caso, la mayoría de las personas que quieran usar tanto un miembro como un método probablemente ni siquiera se molestarán en aprender acerca de .. . Simplemente definirán un método para foo.member y nombrarán el "real "campo foo._member . Podría decirse que este es un mejor estilo de todos modos; significa que cuando lea la definición de type , será inmediatamente obvio que _member no debe ser algo puede o debe acceder directamente. Esto argumentaría a favor de hacer de .. algo feo y oscuro como :. lugar de ocupar un valioso espacio de puntuación).

Echaría de menos la posibilidad de utilizar .. como operador de intervalo infijo, pero el acceso al campo sobrecargable es una compensación que vale la pena. Mientras estoy indeciso a aterrado ligeramente de añadir ningún más significados a dos puntos, :. no parece tan malo.

Tenga en cuenta que :. es en realidad una sintaxis válida para symbol(".") este momento, por lo que puede que no sea bueno usar eso. El punto de que .. es potencialmente útil está bien tomado; no deberíamos desperdiciarlo en una sintaxis que casi nadie usará. Estaría perfectamente feliz de ir con algo aún más feo como @. (ya que . no es un nombre de macro válido ni puede comenzar un identificador, esto no parece entrar en conflicto con nada ). Una vez más, este será un rincón tan oscuro de Julia que no vale la pena intentar hacerlo bonito.

+1 para hacer esto usando .. e ignorar cualquier posible fealdad

Sí, vayamos por .. todos modos, si pudiera usarse para .. entonces creo que sería un constructor _range_, pero bueno, ya está allí con dos puntos, por ejemplo. start:stop .

Una última patada: ¿qué pasa con .: ? ¿Es demasiado sutil tener ab, a. (B), a. (: B) _and_ a.:b?

@hayd , eso parece demasiado sutil y fácil de usar por accidente.

@ihnorton , ¿hay alguna posibilidad de resucitar una versión de # 5848? Podríamos apuntar a la pregunta de sintaxis y simplemente usar Core.getfield(x, Field{y}) para acceder al campo "real".

Dejando a un lado la sintaxis de Core.getfield , ¿quedan algunas preguntas importantes?

En el # 5848, @tknopp sugirió hacer que el acceso al campo "real" sea sobrecargado, contrario a la sugerencia de @JeffBezanson de que todo debería ser sobrecargado. Personalmente, me complacería hacer imposible la sobrecarga de campos reales, excepto que la naturaleza dinámica del lenguaje probablemente hará que sea mucho más complicado de implementar. por ejemplo, con el enfoque de "todo-sobrecargar", si tiene x::Vector{Any} , entonces hacer x[i].y se puede interpretar getfield(x[i], Field{:y}) y el sistema de despacho hará lo correcto independientemente de si y es un campo real, mientras que si solo desea llamar a getfield para campos "virtuales", entonces el codegen tendrá que implementar un subconjunto en miniatura del sistema de despacho para la verificación en tiempo de ejecución de x[i] tipo.

Otra pregunta fue si Module.foo debería poder sobrecargarse. Por un lado, hay cierta coherencia en el uso de getfield para todo, y el ejemplo de Vector{Any} mencionado anteriormente podría tener Module miembros de la matriz, por lo que tendríamos que manejar ese caso de todas formas. Por otro lado, @JeffBezanson señaló que esto podría dificultar la compilación y hacer que el comportamiento de declaraciones como function Base.sum(...) difícil de asimilar. Mi preferencia sería hacer que Module.foo no se pueda sobrecargar, al menos por ahora, en cualquier caso donde el compilador sepa que está trabajando con un Module (es decir, no un Vector{Any} ) ; la ligera inconsistencia parece valer la pena para ser conservadores sobre lo que se cambia.

+1 para no permitir la sobrecarga de Module.foo .

Para intervenir aquí, un área de la computación científica donde la programación y la sintaxis OO es realmente superior a la FP es el modelado basado en agentes. Aunque echo de menos la herencia concreta y múltiple para configurar jerarquías de agentes, las abstracciones ligeras y rápidas y la rápida creación de prototipos de Julia son increíbles. Ya han aparecido un par de marcos ABM.

En ABM, la notación de puntos es preferible para expresar interacciones de agentes: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Este obvio no es el caso de uso más grande, pero sería bueno mantener este azúcar sintáctico para pensar y codificar sobre ABM.

También me gustaría mucho tener esta sintaxis disponible en Julia. Como
tanto como aprecio el enfoque orientado a funciones de un diseño
perspectiva, la sintaxis de llamada al método es muy útil y legible en varios
dominios. Sería genial si Ab (C) fuera equivalente a b (A, C).
El 22 de abril de 2015 a las 8:50 a. M., "Datnamer" [email protected] escribió:

Para intervenir aquí, un área de la computación científica donde la programación OO
y la sintaxis es realmente superior a FP en el modelado basado en agentes. Aunque yo
perder herencia concreta y múltiple para establecer jerarquías de agentes, el
abstracciones ligeras y rápidas y la creación rápida de prototipos de Julia es
asombroso- Ya han aparecido un par de frameworks ABM.

En ABM, la notación de puntos es preferible para expresar interacciones de agentes:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Esto obvio no es el caso de uso más grande, pero sería bueno mantener esto
azúcar sintáctico para pensar y codificar sobre ABM.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95218555.

En ABM, la notación de puntos es preferible para expresar interacciones de agentes: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

¿Por qué es mejor? Editar: me refiero en este contexto ABM específicamente.

Por favor, no nos dejemos atrapar por guerras religiosas por la ortografía. @ dbeach24 , nadie propone que a.b(c) sea ​​equivalente en Julia a b(a,c) ; eso no va a suceder.

Los puntos sobrecargables son cruciales para la interoperabilidad natural con otros idiomas. Esa es razón suficiente.

Subject.Verb (DirectObject)

Es bastante natural en varios contextos. Muchos programadores de OO están acostumbrados a
y si bien es un mero reordenamiento de la función (A, B), ese reordenamiento
hace mucho por la legibilidad, en mi opinión.
El 22 de abril de 2015 a las 10:32 a. M., "Andy Hayden" [email protected] escribió:

En ABM, la notación de puntos es preferible para expresar interacciones de agentes:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

¿Por qué es mejor?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95256390.

Lo estaba proponiendo. (Lo siento, no quise comenzar una guerra, ni sabía
la sugerencia sería impopular.) Pensé que había visto esto antes
en los foros, pero no se dio cuenta de que ya había sido descartado como un
mala idea. ¿Puedo preguntar por qué? (¿Puedes señalarme un hilo?)

Gracias.
El 22 de abril de 2015 a las 11:09 a. M., "Steven G. Johnson" [email protected]
escribió:

Por favor, no nos dejemos atrapar por guerras religiosas por la ortografía. @ dbeach24
https://github.com/dbeach24 , nadie propone que ab (c) sea
equivalente en Julia a b (a, c); eso no va a suceder.

Los puntos sobrecargables son cruciales para una interoperabilidad fluida con otros idiomas.
Esa es razón suficiente.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95266671.

Una razón para no hacer esto es que a.b busca b en el alcance de a , mientras que b solo busca b en el alcance adjunto. Sería muy confuso si el acceso con puntos a veces no mirara hacia arriba en el objeto del lado izquierdo.

Por cierto, se considera una característica que las funciones en Julia no se buscan dentro de los objetos sino en el ámbito actual. Creo que el temor de que la gente empiece a utilizar funciones buscadas dentro de los objetos es una de las razones por las que se ha estado frenando la sobrecarga de puntos.

@toivoh , cualquier implementación de sobrecarga de puntos usaría el método de envío existente, por lo que no cambiaría el comportamiento de alcance. @ dbeach24 , la razón básica para no fomentar el uso indiscriminado de a.b(c) es que si tiene demasiadas sintaxis para hacer lo mismo, el lenguaje y las bibliotecas se vuelven un desastre. Es mejor elegir una ortografía y ceñirse a ella, y el envío múltiple de Julia favorece a b(a,c) porque es más claro que b no es "propiedad" de a - el b(a,c) método a y c .

La mayor excepción, por supuesto, es para llamar a bibliotecas externas en lenguajes como Python o C ++, donde es bueno poder reflejar la sintaxis de puntos de la biblioteca a la que está llamando. (Para que, por ejemplo, la gente pueda traducir documentación y ejemplos directamente a Julia sin cambiar mucho).

¿Estoy todo mojado, pero ab (arg) no significaría tomar una función anónima almacenada en el campo b de a, y luego evaluarla con el argumento dado?

Enviado desde mi iPhone

El 22 de abril de 2015, a las 5:06 p.m., Steven G. Johnson [email protected] escribió:

externo

@ScottPJones Eso actualmente funciona bien, pero generalmente no se considera de buen estilo.

No me preocupaba el estilo o no, solo pensé que, dado que ya tenía un significado, eso era consistente con la forma en que trabaja Julia (es decir, poder almacenar funciones anónimas en los campos), que era un buen argumento _no_ para trate de tratar ab (arg) como si fuera b (a, arg).
Sin embargo, _podría_ tener un uso para tener una estructura (tipo) con miembros que almacenan funciones anónimas, donde estoy cargando funciones escritas en Julia desde una base de datos, y luego analizándolas y almacenando las funciones en un objeto ...
¿Cuál sería un mejor estilo “juliano” para hacer algo así?

¡Gracias!

@ScottPJones Creo que hay acuerdo en que no deberían ser equivalentes *.

Puede haber excepciones a la regla de "estilo", pero tiene que haber un caso convincente para usar poner una función en el campo, al igual que para la sobrecarga de puntos. Creo que el problema es que la gente no debería hacer esto de buena gana / por el simple hecho de hacerlo / porque puede hacerlo.

Ese podría ser un ejemplo, pero también podría haber una mejor manera (ciertamente no es la única) ...

99% + de las veces es mejor enviar en typeof (a); sin campos de función, sin sobrecarga de puntos.

* _Sin embargo, creo que todo el mundo sabe que en el segundo en que esto llegue habrá un paquete que hará precisamente eso ..._

En D incluso tienen un nombre "sintaxis de llamada de función uniforme" para a.b(arg) y es bastante popular, pero creo que está bastante en desacuerdo con la función genérica, la forma de envío múltiple que trabaja Julia. Si las funciones en cuestión son anónimas o están completamente escritas en pato, entonces supongo que las cosas funcionarán, pero eso es bastante restrictivo en mi opinión. No creo que haya muchas razones para almacenar campos de función dentro de un tipo compuesto, excepto por costumbre de los lenguajes OO tradicionales basados ​​en clases. Un mejor lugar para almacenar funciones genéricas si las está cargando desde algún lugar sería en módulos.

Pero ahora nos hemos alejado bastante de las "cuestiones sustantivas". También estoy a favor de ser conservador con la forma en que permitimos sobrecargar getfield, no permitir la sobrecarga de getfield en los módulos y no preocuparnos demasiado por la sintaxis especial para "true getfield".

@stevengj : Sí, y como estaba tratando de decir, esa es una razón fundamental por la que nunca sucederá que a.b(c) convierta en b(a, c) , independientemente de lo que hagamos con la sobrecarga de puntos. .

Hubo una discusión en la lista de correo sobre esto. Desde mi perspectiva, la publicación más relevante (de @nalimilan) para este hilo es: https://groups.google.com/d/msg/julia-users/yC-sw9ykZwM/-607E_FPtl0J

Agregando a @johnmyleswhite 's comentario sobre la política de personal en el momento de utilizar esta función - se me ocurre que algunas ideas HTTP podrían ser útiles aquí, y que getfield() no deben tener efectos secundarios y setfield!() debería ser idempotente (es decir, llamarlo varias veces con el mismo valor debería tener el mismo efecto que llamarlo una vez). No necesariamente reglas estrictas que son impuestas por el compilador, sino pautas de uso para evitar que las cosas se vuelvan demasiado locas.

He publicado una solución alternativa utilizando tipos paramétricos con parámetros de puntero y convertir para llamar a un configurador personalizado al configurar un campo:
publicación: https://groups.google.com/forum/#!topic/julia -users / _I0VosEGa8o
código: https://github.com/barche/CppWrapper/blob/master/test/property.jl

Me pregunto si debería usar este enfoque en mi paquete como solución alternativa hasta setfield. ¿Hay sobrecarga disponible, o es demasiado estrés en el sistema de tipo paramétrico?

Me gustaría mencionar un beneficio adicional de la sobrecarga de getfield / setfield! , espero que este sea el lugar adecuado para esto, lo siento de otra manera. (Surgió un tema relacionado en https://groups.google.com/forum/#!topic/julia-users/ThQyCUgWb_Q)

Julia con getfield / setfield! la sobrecarga permitiría una implementación sorprendentemente elegante de la funcionalidad de recarga automática en un _paquete externo_. (Vea todo el trabajo duro que se tuvo que poner en la extensión de recarga automática de IPython https://ipython.org/ipython-doc/3/config/extensions/autoreload.html para obtener esta funcionalidad). La idea de la recarga automática es que usted puede modificar funciones y tipos en módulos externos mientras trabaja con el REPL.

TLDR: getfield / setfield! sobrecarga, diccionarios y un paquete similar a https://github.com/malmaud/Autoreload.jl deberían hacer el truco.


Para ser más específico, imagine un paquete similar a Autoreload.jl que hace lo siguiente.

Primero crea un módulo M.jl:

module M
type Foo
  field1::Int64
end
bar(x::Foo) = x.field1 + 1.0
end

En el REPL, escribe

julia> using Autoreload2
julia> arequire("M")
julia> foo = Foo(42)

Luego cambia M.jl a

module M
type Foo
  field1::Int64
  field2::Float64
end
bar(x::Foo) = x.field1+x.field2

Esto se recargaría automáticamente y se transformaría en

# type redefinition removed as already done by Autoreload.jl
const field2_dict = Dict{UInt64,Float64}()
setfield!(x::Foo, ::Field{:field2}, value) = field2_dict[object_id(x)] = value
getfield(x::Foo, ::Field{:field2}) = field2_dict[object_id(x)]
<strong i="25">@do_not_inline</strong> bar(x::Foo) = x.field1 + x.field2

y luego en el REPL podrías hacer

julia> foo.field2 = 3.14
julia> println(bar(foo)) # prints 45.14

El rendimiento no sería peor que con Python, por lo que las personas que migren su flujo de trabajo desde la recarga automática de IPython no perderían nada en términos de rendimiento. Y una vez que reinicia el REPL, vuelve a su rendimiento completo.

Me cansé de escribir a[:field][:field2][:morestuff](b[:random_stuff]) porque no es realmente legible. Así que escribí esta pequeña macro que funciona para mis casos de uso en 0.4 y 0.5
https://github.com/sneusse/DotOverload.jl

TL; DR
Una macro que transforma el AST de una expresión a.b -> getMember(a, :b)

Eliminando de 0.6, ya que no hay consenso de que esta sea una buena idea y hay una propuesta contradictoria sobre qué hacer con la sintaxis de puntos.

@Keno : ¿Tiene un enlace a la propuesta en conflicto?

No creo que @StefanKarpinski lo haya escrito todavía, pero espero que pronto haya un Julep al respecto.

Encontré object.fieldname más bonitas que las funciones getter como fieldname(object) o get_fieldname(object) . Tal vez object.fieldname (o object$fieldname ) sea una llamada a getpublicfield (tal vez con un mejor nombre) y object..fieldname sea ​​el getfield (privado) podría ser una buena opción. De esa manera, los tipos deben definir getpublicfield lugar de getters, y tratar de hacer object.fieldname debería dar un ID de error, el campo es privado (será privado si no tiene una definición para getpublicfield ).

Agregué la etiqueta de decisión. Este tema se ha debatido en profundidad y debe hacerse o no. Al leer el # 5848, parecía que @JeffBezanson @StefanKarpinski y @stevengj querían esto. En caso afirmativo, este problema debería tener un hito para que no se olvide. De lo contrario, cerca. En cualquier caso, creo que este es un cambio que debería hacerse antes de la 1.0.

@JeffBezanson y yo estábamos discutiendo esto ayer. Conclusiones provisionales: (i) sí, deberíamos tener esto; (ii) no permitir la sobrecarga de puntos para Module (que se manejará especialmente); (iii) no proporcione ninguna sintaxis especial para Core.getfield (ya que no hay necesidad urgente de que un getfield sobrecargado tenga el mismo nombre que un campo "real"; este último puede comenzar con un guión bajo).

@stevengj : Parece un plan razonable. ¿Podría indicar si esto se restringirá a un solo argumento o si la versión de múltiples argumentos a.fieldname(b) también debería ser compatible? Esto sacará una conclusión a la discusión anterior. Además, sería genial poner una etiqueta de hito adecuada a esto (¿1.0?). ¡Gracias!

Jeff y yo no discutimos el caso de múltiples argumentos. Mi sensación es que también podríamos admitirlo, ya que puede simularlo de todos modos devolviendo una función del caso sin argumentos (pero no es fundamental hacerlo de inmediato por la misma razón).

Utilizo un convertidor para emitir valores y validar datos.
Me gusta esto:

abstract AbstractAge{T}
abstract AbstractPerson
type PersonAge <: AbstractAge{AbstractPerson} 
    value::Int64
end

Base.convert(t::Type{AbstractAge{AbstractPerson}}, value::Int64) =  begin
  if value < 140 && value > 0
    PersonAge(value) 
  else
     throw(ErrorException("ValueError"))
  end
end

type Person <: AbstractPerson
  age::AbstractAge{AbstractPerson}
end 

a = Person(32)
a.age = 67

Aquí hay una implementación divertida de 3 líneas de esto:

diff --git a/base/boot.jl b/base/boot.jl
index cd3ae8b..a58bb7e 100644
--- a/base/boot.jl
+++ b/base/boot.jl
@@ -266,6 +266,9 @@ Void() = nothing

 (::Type{Tuple{}})() = ()

+struct Field{name} end
+(::Field{f})(x) where {f} = getfield(x, f)
+
 struct VecElement{T}
     value::T
     VecElement{T}(value::T) where {T} = new(value) # disable converting constructor in Core
diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm
index b4cb4b5..59c9762 100644
--- a/src/julia-syntax.scm
+++ b/src/julia-syntax.scm
@@ -1685,7 +1685,7 @@
     (if (and (pair? e) (eq? (car e) '|.|))
         (let ((f (cadr e)) (x (caddr e)))
           (if (or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
-              `(call (core getfield) ,f ,x)
+              `(call (new (call (core apply_type) (core Field) ,x)) ,f)
               (make-fuse f (cdr x))))
         (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e)))
             (make-fuse (undotop (cadr e)) (cddr e))

Mi opinión es que a.b debería llamar a una función de proyección Field{:b}() lugar de getfield , de modo que obtenga funciones como x->x.a ya de forma gratuita. Eso también permite que getfield siempre signifique acceso de campo de bajo nivel.

La implementación anterior funciona completamente, pero es bastante difícil para el compilador (sysimg + 5%, lo cual es una sorpresa agradable). Por lo tanto, esto necesitará algunas heurísticas de especialización y algunas optimizaciones tempranas deben actualizarse, pero luego, con suerte, esto será viable.

Me sorprende que las pruebas puedan pasar con esa implementación. ¿No hace esta semántica imposible que codegen optimice las referencias de los módulos? También parece que hará que las variables globales sean mucho más caras (velocidad y memoria). No parece muy probable que esto aparezca en el sysimg. Aunque la degradación de la inferencia parece que debería haber hecho que el sysimg sea más pequeño aquí, por lo que un 5% peor no parece un buen comienzo)

Sí, hay un código en jl_resolve_globals para convertirlos en GlobalRefs que necesitarían actualizarse. Aparte de eso, estos deberían estar alineados para que codegen pueda manejarlos. En inferencia, probablemente solo necesitemos un caso especial similar al de la tupla getindex .

probablemente solo necesitemos un caso especial similar al de la tupla getindex

Ese caso especial espera y asume que no se definirá ningún método que se cruce con la firma del método getindex incorporada. No veo cómo ese caso es especialmente aplicable aquí.

Agregaría un método para ::Module y diría que no está permitido sombrearlo --- posiblemente aplicado con un error real.

@JeffBezanson ¿Tiene una rama con esas tres líneas? Intenté agregarlos yo mismo a una compilación local, pero parece que Julia ya se ha movido y no pude hacer que funcione.

Debo decir que si solo tuviera un deseo para una característica en julia 1.0, sería este. Resolvería dos problemas de larga data que tengo tanto en Mimi como en Query .

El caso de Query creo que es bastante genérico. Creo que se podría escribir una versión de NamedTuples con esto que no tuviera que generar nuevos tipos en macros, y eso a su vez me permitiría escribir funciones generadas que devuelvan un NamedTuple con un conjunto de campos que son calculado en la función generada. Creo que eso me permitiría escribir una versión de tipo estable de blackrock / NamedTuples.jl # 4, que es, con mucho, mi mayor obstáculo en Query.jl en este momento.

En pocas palabras, esto sería súper, súper valioso para toda la historia del manejo de datos en julia.

¿Está disponible la siguiente sintaxis?

function (obj::MyType).plus(n::Int)
       return obj.val + n
end

¿Por qué demonios querrías esa sintaxis?

Querer escribir obj.plus(n) lugar de obj + n es un síndrome de Estocolmo grave.

Ok, el ejemplo dado fue trivial (y malo).
Era solo una idea para usar esta sintaxis en lugar de sobrecargar getfield y poder usar argumentos.

Lo que más me preocupa es la funcionalidad. Tener una sintaxis abreviada para sobrecargar getfield parece mucho menos importante y no vale la pena empantanarse discutiendo. Además, getfield y setfield! reflejan directamente getindex y setindex! y, por lo tanto, son algo naturales en Julia. Finalmente, el uso extensivo de un estilo OO no encaja realmente bien con el lenguaje de Julia, y no creo que queramos fomentarlo con una sintaxis de definición de método similar a OO (pero vea mi comentario anterior sobre argumentos).

Una cosa que se me ocurrió fue que el operador $ estaba en desuso. Ahora, esto obviamente hace que hacer algo como $(x, sym::Symbol) = ... ya esté disponible, pero también podríamos entretener una reescritura sintáctica más elegante como:

x$y          => $(x, ::Type{Val{:y}})
x$z(args...) => $(x, ::Type{Val{:z}}, args...)

Creo que ya cubre la mayoría de los casos mencionados en este número sin realizar una sobrecarga completa de getfield. Francamente, el operador . está bastante saturado semánticamente en Julia, por lo que algo como esto se siente más fácil de digerir y es lo suficientemente conveniente como para seguir siendo útil.

@quinnj , ya propuesto en # 18696.

Sin embargo, dado que ya tenemos . para el acceso al campo, parece poco elegante tener dos operadores similares al acceso al campo, uno que se puede sobrecargar y otro que no. Y sería un poco antinatural para las llamadas entre idiomas a Python y otros lenguajes OO, que casi universalmente usan . .

No veo la interoperabilidad con otros lenguajes como un argumento válido para introducir algo como esto. Es como decir: "El código Python se ve así, así que para poder fingir ser Python, también deberíamos hacer esto". Todavía tengo que ver un argumento para esto que hará que la propia Julia sea mejor y / o más consistente. Ya está bien establecido que Julia no proporciona la sintaxis x.f() estilo POO; permitir cosas así es pedir inconsistencia.

@stevengj , parte de lo que vengo es el hecho de que x.f _isn't_ acceso al campo. No hay ningún miembro de campo real f . Todo este problema se trata de sobrecargar getfield y creo que las principales preocupaciones son la posible confusión de si x.f realidad se refiere a un campo o está haciendo algo más bajo el capó.

La ventaja de la reescritura sintáctica que propuse es que cumple la historia de interoperabilidad del lenguaje sin introducir confusión adicional con getfield; eso es a lo que me refería cuando mencioné que . estaría sobresaturado. ¿ x$f sería realmente mucho más engorroso? Simplemente no veo por qué esto _tiene_ que usar el operador de punto.

Todo este problema se trata de sobrecargar getfield y creo que las principales preocupaciones son la posible confusión de si x.f realidad se refiere a un campo o está haciendo algo más bajo el capó.

No creo que haya ninguna posibilidad de confusión en la propuesta de tres líneas de a.b siempre se reduce a una llamada de función de proyección, y nunca a una llamada getfield . Y luego hay un método predeterminado / alternativo en la base para la función de proyección que llama a getfield . Los usuarios pueden agregar métodos para sus propios tipos a esa función de proyección si lo desean. getfield siempre significa acceso de campo de bajo nivel en esa propuesta, y los usuarios no agregarían métodos a getfield (supongo que eso es lo que quiso decir con "sobrecargar getfield"). Eso me parece muy limpio. Habiendo dicho eso, no me queda claro si esa propuesta todavía está sobre la mesa o no, parece haber algunas preocupaciones del compilador que no entiendo :)

Realmente me encantaría tener esta función (siempre me ha encantado en Scala). En mi humilde opinión, acceder a los campos directamente es muy natural en Julia (en comparación con, por ejemplo, la forma de Java de definir a la defensiva captadores y definidores para todo). Pero sin la capacidad de agregar campos "virtuales", se vuelve muy difícil evolucionar / refactorizar estructuras sin romper mucho código.

Sé que esto se ha etiquetado como v2.0, pero me gustaría volver a mencionarlo con la esperanza de reconsiderarlo para la v1.0. Esta característica será muy valiosa para los desarrolladores de paquetes / bibliotecas.

Al crear un paquete para los usuarios, la sobrecarga del atributo de acceso permitirá a los encargados del mantenimiento del paquete presentar una interfaz de usuario mucho más limpia. Yo discutiría eso,

complex_data_structure.attribute

es fácil de escribir que

get(complex_data_structure, :attribute)

donde get es una función necesaria para obtener datos que se describen en :attribute .

Esto también podría mejorar la capacidad de descubrimiento en el REPL y beneficiaría el código del idioma y la exploración de datos.

Además, un interceptor de atributos de setter también sería increíblemente valioso. Considere el siguiente ejemplo (que ya se encuentra en muchos de mis scripts actualmente :)),

set(complex_data_structure, :attribute, modify_attribute(get(complex_data_structure, :attribute), additional_arguments))

Si un captador y definidor de atributos se incluyeran como parte de Julia, lo anterior se convertiría en lo siguiente.

complex_data_structure.attribute = modify_attribute(complex_data_structure.attribute, additional_arguments)

Esto, en mi humilde opinión, es mucho más legible. (Nota al margen: podría usar una composición de tubería para hacerla más legible, pero el additional_arguments nuevamente lo complicaría nuevamente allí. Y el argumento aquí todavía se mantendría).

Además, restringir los valores proporcionados por un usuario es un caso de uso muy común, y será realmente genial tenerlo en la v1.0. Mejorará enormemente las interfaces de usuario para múltiples paquetes. Actualmente, según mi leal saber y entender, no parece haber forma de hacer lo siguiente (¿alguien puede corregirme si me equivoco)?

module point

mutable struct Point
    x::Int
    y::Int
    function Point(x, y)
        if x < 0 || y < 0
            throw(error("Only non-negative values allowed"))
        end
        this = new(x, y)
    end
end

end
# point

p1 = point.Point(-1, 0)
# Only non-negative values allowed

# Stacktrace:
# [1] point.Point(::Int64, ::Int64) at ./In[30]:8
# [2] include_string(::String, ::String) at ./loading.jl:515

p1 = point.Point(0, 0);
p1.x = -1;
p1
# point.Point(-1, 0)

El constructor puede restringir las entradas del dominio a una estructura inmutable, sin embargo, un usuario aún puede ingresar valores que no son válidos. Los captadores y definidores de atributos ayudarían aquí, porque harían que la retroalimentación sobre la validez de los datos aquí sea más inmediata, y esto hará que la interfaz como usuario del idioma sea mucho más limpia.

Esto también beneficiará enormemente a otros paquetes como PyCall.jl y DataFrames.jl para construir interfaces posiblemente mejores y más intuitivas para los usuarios. Los autores de estos paquetes también han expresado anteriormente su interés en tener esta característica.

Pensamientos Al leer el hilo, parece que esto ya está bastante cerca de ser final. Tengo curiosidad por saber qué piensan los autores sobre esto.

Implementado en una macro opcional estable de tipo aquí: https://github.com/bramtayl/DotOverloading.jl. Creo que tiene potencial para Base.

@bramtayl , creo que tener que poner @overload_dots antes de las expresiones a.b anula el punto aquí.

No estaba sugiriendo que la macro se agregara a la base; Estaba sugiriendo que la estrategia utilizada por la macro se incorpore al analizador. Sin embargo, es una macro que puede ejecutarse en archivos completos o incluso piratearse en un IDE.

Eso implicaría que a.b se analice a Expr(:., :a, :(Val{:b}()) y se reduzca a get_field_overloadable(a, Val{:b}())

@bramtayl , vea la implementación de Jeff

Un problema con la sobrecarga de getfield es que algunas bibliotecas no tienen una interfaz razonable con el interior de una estructura de datos. Cuando todo lo que quiero es modificar un punto de datos en una estructura para poder ejecutar mi código y hacer mi informe para mañana, no tengo muchas ganas de editar código en una biblioteca de tres niveles de profundidad en mi dependencia para poder para acceder directamente al punto de datos de una manera razonable y rápida. Quiero sentir que tengo control sobre las estructuras de datos que estoy usando.

El segundo punto es que cuando estoy usando getfield y setfield quiero una expectativa razonable de que estoy accediendo directamente a la estructura de datos en lugar de algún mecanismo de función enorme. Además, la palabra clave struct se usa para definir un tipo, recordándote C, y siento que te da la expectativa de que el acceso a getfield debería ser directo.

Entonces, creo que usar el operador $ lugar es un compromiso razonable, ya que no hay ninguna expectativa de que el acceso sea directo como en getfield, ya es un carácter especial en otros contextos y, por lo tanto, no será demasiado sorprendente cuando se usa de esta manera, será fácil de desaprobar porque pocas personas lo usan y porque ya no será una función, y se usa de manera similar en R.

Un problema con la sobrecarga de getfield es que algunas bibliotecas no tienen una interfaz razonable con el interior de una estructura de datos.

La implementación de @JeffBezanson desde arriba no sobrecarga getfield (o setfield ).

También me gustaría pedir que esto se convierta en 1.0. A menudo me encuentro dividido entre escribir getters / settings para mantener mi código algo flexible (y luego preguntarme cómo nombrarlos), o "permitir / alentar" al usuario de mi código a usar el acceso directo al campo.

Escribir defensivamente muchos getters y setters por adelantado no me parece muy juliano; después de todo, Julia no tiene campos privados / protegidos y, por lo tanto, anima al usuario a acceder a los campos directamente. Luego está la pregunta sobre cómo nombrar muchas funciones getter / setter sin arriesgarse a muchos conflictos con otros paquetes. Y la alternativa, agregar métodos para getfield y setfield! (o similar), enviados en Val{:fieldname} o menos, tampoco parece muy fácil de usar.

Por otro lado, si el acceso directo al campo básicamente bloquea todas las estructuras para siempre, claramente no debería fomentarse, especialmente para el código de larga duración. La implementación de @JeffBezanson parecería ser una forma tan ordenada de salir de este dilema.

Correcto, @davidanthoff. Debo estar combinando la sobrecarga de getfield y la sobrecarga de la sintaxis de acceso al campo . . Me refería a la sobrecarga de la sintaxis de acceso al campo.

Hay formas compatibles con versiones anteriores de agregar esta función, por lo que podría agregarse en una versión 1.x, pero no está sucediendo en 1.0. No tenemos un diseño terminado ni tiempo para implementarlo, incluso si lo tuviéramos.

Sería sumamente conveniente usar sintaxis de puntos en punteros a estructuras (C-).

Mi sintaxis preferida sería usar puntos para mover y convertir punteros solamente, y usar [] para deref.

Entonces, estructura algún par a :: Int b :: Int end, y p :: Ptr {algún par}, entonces pa es un puntero al campo a, y escribo en él con pa [] = 3, o leo con x = Pensilvania[].

A continuación, solo necesitamos declaraciones de tipos hacia adelante y posiblemente una forma de importar encabezados C, y luego ajustar C se vuelve muy fácil.

Ps, para aclarar, algo como:

function getfield(p::Ptr{T}, fn::Symbol) # dispatch on values of T and fieldname
ftype = fieldtype(T, fn)
offset = fieldoffset(T,fn)
return convert(Ptr{ftype}, p+offset)
end

getindex(p::Ptr{T}) where T = unsafe_load(p)
setindex!(p::Ptr{T}, v) where T = unsafe_store!(p, convert(T,v))

Para obtener puntos de bonificación, los tipos de la biblioteca en tiempo de ejecución de julia podrían exportarse, y pointer_from_objref podría darnos un puntero bien escrito para una introspección más conveniente.

Tengo la sensación de que los sindicatos deberían funcionar un poco automáticamente, ¿con solo tener dos campos con el mismo desplazamiento?

@chethega Creo que está buscando https://github.com/JuliaLang/julia/pull/21912 , que proporciona aproximadamente la misma función, sin los problemas que rodean la adopción del modelo de memoria de C (violaciones de juegos de palabras, fuera de -acceso de límites, aliasing de punteros interiores, etc., que limitan las optimizaciones de rendimiento posibles en ese idioma).

¿La propagación constante hace que esto sea más factible?

¡Cambie el hito a 1.0! :)

@ Liso77 ¿Qué quieres decir? Esto ya está implementado en git master, como se indica en el estado al cerrar.

@nalimilan lo siento si lo entiendo mal! Pero pensé que como 1.x se etiquetan cosas pospuestas que se resolverán después de 1.0. Y esto está resuelto ahora ...

El código abierto es una comunidad descentralizada. El hito establece lo que debería estar completo en 1.0, pero los contribuyentes y trabajan en lo que quieran. En este caso, alguien quería esto en 1.0, así que contribuyó con el código para conseguirlo.

@ Liso77 Según tengo entendido, esto no estará en la v1.0. La fecha de congelación de la función Julia v1.0 se estableció para el 15 de diciembre, pero este problema se cerró el 17 de diciembre, así que creo que podemos esperarlo en una versión 1.x. Los desarrolladores principales pueden corregirme si mi interpretación es incorrecta.

No, se fusionó con el maestro y estará en la próxima versión.

:) ¡Bien! Simplemente pensé que es bueno etiquetar 1.0 lo que está saliendo en 1.0. Si no se desea, ¡perdón por molestar! :)

Creo que el archivo NEWS es una mejor manera de ver lo que está pasando en 1.0.

El hito se agregó para significar que "se puede implementar sin romper la compatibilidad en la serie de versiones 1.x", pero luego, dado que el código estaba listo, se fusionó de todos modos antes de la congelación de la característica 1.0. Eliminé el hito para mayor claridad, pero en este punto todo lo que se fusiona en el maestro estará en 1.0.

¡Gracias por la aclaración! ¡Esto es emocionante! El archivo NEWS también fue particularmente esclarecedor.

¡Gracias por eliminar! ¡También estoy muy contento de que vendrá en 1.0! :)

Me pregunto si hay una manera de admitir estos nuevos "campos definidos dinámicamente" en el autocompletado, por ejemplo, permitiendo sobrecargar fieldnames ?. Esto podría ser muy poderoso, para uso interactivo, por ejemplo, cuando se trata de DataFrame s (asumiendo que admitirán df.column_name en el futuro) con muchas columnas y / o nombres de columna largos.

Supongo que en este momento REPL (expansión de pestañas), IJulia, etc., miran la definición de tipo, no la instancia. Pero tal vez eso podría cambiarse, para uso interactivo. Sin embargo, probablemente sea imposible admitirlo en un IDE como Juno, ya que las instancias no están disponibles durante la codificación.

@oschulz , ya puedes sobrecargar fieldnames :


julia> struct Foo; foo; end

julia> fieldnames(Foo)
1-element Array{Symbol,1}:
 :foo

julia> Base.fieldnames(::Type{Foo}) = [:bar, :baz]

julia> fieldnames(Foo)
2-element Array{Symbol,1}:
 :bar
 :baz

Y parece que fieldnames es lo que mira el REPL:

julia> x = Foo(3)
Foo(3)

julia> x.ba<tab>
bar baz

@yurivish correcto, pero ¿es "seguro" hacerlo, actualmente? No estoy seguro de qué más se basa en fieldnames .

Si no es seguro, debería ser posible definir un complete_fieldnames(x) = fieldnames(x) , usar complete_fieldnames para las terminaciones de REPL y sobrecargarlo para las terminaciones personalizadas.

Sí, es por eso que mencioné esto, en caso de que sea necesario cambiar / agregar algo en Base, para que REPL y sus amigos puedan aprovecharlo más tarde. En vista de la característica 0.7 congelar ...

Por eso me alegro de que llamemos a la función getproperty ; ayuda a aclarar que no se refiere a lo mismo que fieldnames . Interferir con fieldnames definitivamente podría causar serios problemas.

Podríamos necesitar un propertynames para dar los valores de propiedad válidos. Por supuesto, puede que no sea un concepto bien definido, por lo que tal vez queramos algo más específico para completar.

Tenía la sensación de que esto podría requerir un análisis un poco más profundo y aportes de los expertos (¡gracias!). ¿Deberíamos abrir una nueva edición para esto?

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

Temas relacionados

ararslan picture ararslan  ·  3Comentarios

StefanKarpinski picture StefanKarpinski  ·  3Comentarios

yurivish picture yurivish  ·  3Comentarios

StefanKarpinski picture StefanKarpinski  ·  3Comentarios

dpsanders picture dpsanders  ·  3Comentarios