Julia: permitir a sobrecarga da sintaxe de acesso ao campo ab

Criado em 10 jan. 2013  ·  249Comentários  ·  Fonte: JuliaLang/julia

Comentários muito úteis

Aqui está uma implementação divertida de 3 linhas disso:

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))

Meu pensamento é que a.b deve realmente chamar uma função de projeção Field{:b}() vez de getfield , de modo que você obtenha funções como x->x.a já de graça. Isso também permite que getfield sempre signifique acesso de campo de baixo nível.

A implementação acima funciona completamente, mas é muito difícil para o compilador (sysimg + 5%, o que é uma surpresa agradável, na verdade). Portanto, isso vai precisar de algumas heurísticas de especialização e algumas otimizações iniciais precisam ser atualizadas, mas espero que isso seja viável.

Todos 249 comentários

A capacidade de usar pontos como açúcar sintático para métodos mutadores / acessadores seria boa para muitas coisas. Sempre gostei disso em linguagens que o fornecem, de modo que você pode transformar campos de estrutura em abstrações mais complicadas sem quebrar a API.

+1

Eu tenho uma maneira absolutamente incrível de implementar isso.

Interessado em falar sobre isso? Eu sei que Tom Short está realmente interessado em ter isso para DataFrames, embora eu esteja cada vez mais cético sobre a sabedoria de usar esse recurso.

Isso tornaria a chamada do código Python (via PyCall) significativamente mais agradável, já que atualmente sou forçado a fazer a[:b] vez de a.b .

@JeffBezanson , alguma chance de ter isso por 0,3? Seria ótimo para interoperabilidade entre linguagens, tanto para PyCall quanto para JavaCall (cc @aviks).

@JeffBezanson if I have an absolutely awesome way to implement this. )

Na minha experiência, não há maneira mais rápida nem mais segura de fazer Jeff implementar algo do que implementar uma versão que ele não gosta ;-)

A ideia básica é que você implemente

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

para que você possa sobrecarregá-lo por campo. Isso permite acesso a campos "reais" para continuar trabalhando de forma transparente. Com alternativas adequadas, getfield(::MyType, ::Symbol) também funciona.

O maior problema é que os módulos têm um comportamento especial em relação a . . Em teoria, este seria apenas outro método de getfield , mas o problema é que precisamos resolver as referências do módulo _anterior_, pois basicamente se comportam como variáveis ​​globais. Acho que teremos que manter este um caso especial no comportamento de . . Também existe um pouco de preocupação com a eficiência do compilador, devido à análise de (# tipos) * (# campos) definições de funções extras. Mas para isso veremos apenas o que acontece.

@JeffBezanson Você também se refere ao comportamento de const em módulos? Seria útil ter um tipo de usuário emulando um módulo e ser capaz de dizer ao compilador quando o resultado de uma pesquisa de campo dinâmico é de fato constante. (outra abordagem seria começar com um módulo real e ser capaz de "capturar" um jl_get_global falha e injetar novas ligações sob demanda)

Eu acharia isso muito útil em combinação com o # 5395. Então, pode-se interceptar uma chamada para uma função ou método indefinido MyMod.newfunction(new signature) e gerar ligações para uma API (possivelmente grande) sob demanda. Isso seria então armazenado em cache como ligações constantes habituais, eu acho.

Deixe-me, um simples novato em Julia, apresentar uma pequena preocupação: acho que a possibilidade de sobrecarregar o operador ponto pode implicar que a "pureza" do acesso ao campo seja de alguma forma perdida.

O usuário geralmente perderia o conhecimento se fazer ab é apenas um acesso a uma referência / valor ou se pode haver uma grande máquina de função sendo chamada por trás. Não tenho certeza de como isso pode ser ruim, é apenas um sentimento ...

Por outro lado, vejo que de fato este é um grande desejo de açúcar de sintaxe para muitos casos (PyCall, Dataframes ...), o que é perfeitamente compreensível.
Talvez seja a hora de .. # 2614?

Eu apóio fazer isso.

Mas a pureza tem algo a dizer sobre ela, mesmo se alguém puder usar names(Foo) para descobrir quais são os componentes reais de Foo .

O argumento de pureza está intimamente relacionado à principal preocupação prática que tenho, que é como lidar com conflitos de nomes quando os campos do tipo interferem nos nomes que você espera usar. Em DataFrames, acho que resolveríamos isso banindo o uso de columns e colindex como nomes de coluna, mas queríamos saber qual era o plano das pessoas para isso.

Eu acho que getfield(x::MyType, ::Field{:foo}) = ... teria que ser proibido quando MyType tem um campo foo , caso contrário, o acesso ao campo real seria perdido (ou uma forma de forçar o acesso ao campo teria de estar disponível).
Mas então getfield só poderia ser definido para tipos concretos, uma vez que os abstratos não sabem nada sobre campos.

(Enquanto isso, descobri isso sobre C ++ .)

Não é um grande problema. Podemos fornecer algo como Core.getfield(x, :f) para forçar o acesso aos campos reais.

Ok, talvez eu esteja convencido. Mas então definir um atalho para Core.getfield(x, :f) (por exemplo, x..f ) será bom, caso contrário, o código interno de tipos que sobrecarregam o . para todos os símbolos (dataframes, provavelmente dicionários) tem que estar lotado com Core.getfield s.

Não estou preocupado com o aspecto da pureza - até termos este, o único código
que deveria estar usando o acesso de campo é um código que pertence ao
implementação de um determinado tipo. Quando o acesso de campo faz parte de uma API, você
tem que documentá-lo, como acontece com qualquer api. Eu concordo que pode ser útil com
alguma sintaxe de atalho para core.getfield, ao escrever aqueles
implementações.

Já havia sido apontado no # 4935, mas vamos puxá-lo para aqui: a sobrecarga de pontos pode se sobrepor um pouco ao despacho múltiplo Juliano clássico se não for usado corretamente, pois podemos começar a fazer

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

ao invés de

size (x :: MyType) = ..........
para i = 1: tamanho (y) ....

Embora o ponto seja ótimo para acessar itens em coleções (Dataframes, Dicts, PyObjects), ele pode de alguma forma alterar a maneira como as propriedades do objeto (não os campos) são acessadas.

Acho que uma coisa a se considerar é que se você pode sobrecarregar o campo de acesso, também deve ser capaz de sobrecarregar _setting_ um campo. Caso contrário, isso será inconsistente e frustrante. Você está bem para ir tão longe?

@nalimilan , é absolutamente necessário um setfield! além de getfield . (Semelhante a setindex! vs. getindex para [] ). Não acho isso controverso.

Concordo com @stevengj : DataFrames certamente implementará setfield! para colunas.

Eu apoio isso.

A experiência com outras linguagens (por exemplo, C # e Python) mostra que a sintaxe de ponto tem muito valor prático. A maneira como é implementado por meio de métodos especializados aborda amplamente a preocupação com a regressão do desempenho.

É, no entanto, importante garantir que a _inlineabilidade_ de um método não seja seriamente afetada por essa mudança. Por exemplo, algo como f(x) = g(x.a) + h(x.b) não se tornará subitamente não alinhado depois que isso cair.

Se decidirmos fazer isso acontecer, é útil também fornecer macros para facilitar a definição da propriedade, que pode ser semelhante a:

# 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

Nos bastidores, tudo isso pode ser traduzido em definições de método.

Não estou convencido de que <strong i="5">@property</strong> (a::A).foo = seja muito mais fácil do que getproperty(a::A, ::Field{foo}) = ...

Em qualquer caso, melhor açúcar sintático é algo que pode ser adicionado depois que a funcionalidade básica chegar.

Com relação ao inline, contanto que o acesso de campo seja inline antes de ser tomada a decisão de inline da função circundante, não vejo por que isso seria afetado. Mas talvez esta não seja a ordem em que o inlining é feito atualmente?

getproperty(a::A, ::Field{:foo}) = me impressiona porque há muitos dois pontos :-) Concordo que isso é uma coisa pequena e provavelmente não precisamos nos preocupar com isso agora.

Minha preocupação é se isso causaria uma regressão de desempenho. Não estou muito claro sobre o mecanismo interno de geração de código. @JeffBezanson provavelmente pode dizer algo sobre isso?

O acesso de campo é de nível muito baixo, então não farei isso sem garantir que o desempenho seja preservado.

Afinal, não estou convencido de que sobrecarregar campos seja uma boa ideia. Com esta proposta, sempre haveria duas maneiras de definir uma propriedade: x.property = value e property!(x, value) . Se a sobrecarga de campo for implementada, precisaremos de um guia de estilo muito forte para evitar terminar em uma confusão total onde você nunca sabe com antecedência qual solução o autor escolheu para um determinado tipo.

E então haveria a questão de saber se os campos são públicos ou privados. Não permitir a sobrecarga de campo tornaria o sistema de tipo mais claro: os campos sempre seriam privados. Os métodos seriam públicos e os tipos seriam capazes de declarar que implementam interfaces / protocolo / características, ou seja, que fornecem um determinado conjunto de métodos. Isso iria contra @stevengj 's https://github.com/JuliaLang/julia/issues/1974#issuecomment -12083268 sobre sobrecarregando campos com métodos para evitar a quebra de uma API: só oferecem métodos como parte da API, e nunca campos .

O único lugar onde eu lamentaria a sobrecarga de campo é em DataFrames , já que df[:a] não é tão bom quanto df.a . Mas isso não parece que deva exigir sozinho uma mudança tão importante. O outro caso de uso parece ser PyCall, o que pode indicar que a sobrecarga de campo deve ser permitida, mas apenas para casos de uso não Julianos altamente específicos. Mas como evitar que as pessoas usem indevidamente um recurso quando ele estiver disponível? Esconder em um módulo especial?

@nalimilan , eu diria que a preferência deve ser usar a sintaxe x.property máximo possível. O fato é que as pessoas _realmente_ gostam dessa sintaxe - é muito agradável. Pegar uma sintaxe tão boa e dizer especificamente que ela só deve ser usada para acesso interno a objetos parece totalmente perverso - "hah, essa sintaxe legal existe; não a use!" Parece muito mais razoável tornar a sintaxe para acessar coisas privadas menos conveniente e bonita, em vez de forçar as APIs a usarem a sintaxe mais feia. Talvez este seja um bom caso de uso para o operador .. : o operador de acesso a campo _real_ privado.

Na verdade, acho que essa mudança pode tornar as coisas mais claras e consistentes, em vez de menos. Considere os intervalos - atualmente existe uma espécie de mistura horrível de estilos de step(r) versus r.step por aí agora. Especialmente porque eu introduzi FloatRange isso é perigoso porque apenas o código que usa step(r) está correto. A razão para a combinação é que algumas propriedades de intervalos são armazenadas e algumas são calculadas - mas aquelas mudaram com o tempo e são de fato diferentes para diferentes tipos de intervalos. Seria um estilo melhor se cada acesso fosse do estilo step(r) , exceto a definição do próprio step(r) . Mas existem algumas barreiras psicológicas íngremes contra isso. Se fizermos de r.step uma chamada de método cujo padrão é r..step , então as pessoas podem simplesmente fazer o que estão naturalmente inclinadas a fazer.

Para bancar o advogado do diabo (comigo mesmo), devemos escrever r.length ou length(r) ? A inconsistência entre funções e métodos genéricos é um problema que aflige o Python, enquanto o Ruby se compromete totalmente com o estilo r.length .

+1 para .. as Core.getfield !

@StefanKarpinski Faz sentido, mas então você precisará adicionar sintaxe para campos privados, e as interfaces terão que especificar métodos e campos públicos. E, de fato, você precisa de um guia de estilo para garantir alguma consistência; o caso de length é difícil, mas também existe, por exemplo, size , que é muito semelhante, mas precisa de um índice de dimensão. Esta decisão abre uma lata de vermes ...

Nesse caso, também apóio .. para acessar campos reais e . para acessar campos, sejam eles métodos ou valores reais.

Para bancar o advogado do diabo (comigo mesmo), devemos escrever r.length ou length(r) ? A inconsistência entre funções e métodos genéricos é um problema que aflige o Python, enquanto o Ruby se compromete totalmente com o estilo r.length .

O fator-chave que pode eliminar a ambigüidade desse problema é se você deseja ser capaz de usar algo como uma função de ordem superior ou não. Ou seja, f em f(x) é algo que você pode map em uma coleção, enquanto f em x.f não é (exceto por escrito x -> x.f ) - que é a mesma situação para todos os métodos em linguagens de despacho único.

Por que parar no acesso ao campo? Que tal ter x.foo(args...) equivalente a getfield(x::MyType, ::Field{:foo}, args...) = ... ? Então poderíamos ter x.size(1) para o tamanho ao longo da primeira dimensão. (não tenho certeza se gosto da minha sugestão, mas talvez algo a considerar. Ou provavelmente não, já que as pessoas irão apenas escrever código OO parecido?)

Isso seria possível com esta funcionalidade. O que é uma das coisas que me fazem pensar. Não tenho problemas com códigos de estilo oo como esse - como eu disse, é bastante agradável e as pessoas realmente gostam - mas apresenta opções suficientes de maneiras de escrever coisas que _realmente_ precisamos de uma política forte sobre o que você deve fazer já que você estará muito livre com o que você pode fazer.

Quando comecei a aprender Julia, a sintaxe no-dot me ajudou muito a abandonar mentalmente o estilo de programação OO. Então, só por esse motivo, acho que minha sugestão é ruim.

Além disso, para sobrecarga simples (ou seja, apenas a.b sans (args...) ), concordo com o comentário de @nalimilan acima. Na edição nº 4935, o consenso parece ser que os campos não devem fazer parte da API, mas apenas métodos; conseqüentemente, parece que essa questão será encerrada. Ter a sintaxe . -overloading tornará muito menos claro que os campos normais não devem fazer parte da API e provavelmente incentivará os campos a fazerem parte da API.

Mas sim, a sintaxe . é conveniente ...

Que tal: o único . deve _somente_ ser açúcar sintático para getfield(x::MyType, ::Field{:name}) = ... e o acesso ao campo é _somente_ por meio de .. (ou seja, o que . é agora).

Isso permitiria fazer uma distinção clara:

  • o . é para a API pública acessar coisas semelhantes a valor de instâncias do tipo
  • o .. é para acesso de campo e geralmente não deve ser usado na API pública

Claro, esta seria uma mudança significativa.

Isso é basicamente o que eu estava propondo, exceto que . padrão .. então não está quebrando.

Desculpe, eu deveria ter relido!

Mas eu acho que . não padronizar para .. pode realmente ser bom (além de estar quebrando), pois forçaria uma decisão do desenvolvedor sobre o que é API pública e o que não é. Além disso, se o usuário usar um .. ele pode esperar que seu código seja corrompido, enquanto . não deveria.

Este é um bom ponto. Podemos seguir esse caminho tendo a.b padrão a..b com um aviso de depreciação.

De uma perspectiva de estilo, acho que prefiro muito mais ver

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

do que

a.length
a.size

Acho que ajuda a preservar a ideia de que uma função está sendo chamada em vez de apenas uma propriedade sendo recuperada que de alguma forma está armazenada no tipo (de volta à preocupação de "pureza" acima). Eu me pergunto se há uma maneira de ajudar a garantir esse tipo de estilo para que as coisas não fiquem tão bagunçadas como em outras línguas.

Eu realmente não gosto

a.length()

desde então não consigo dizer se havia um campo de função no tipo original. Se . nunca acessa campos, isso obviamente não é um problema. Caso contrário, parece confuso para mim.

A priori, acho que não devemos fazer a.length() ou a.length . Mas a pergunta é porque? O que torna r.step diferente de r.length ? É diferente? Se eles não forem diferentes, devemos usar step(r) e length(r) ou r.step e r.length ?

Com a semântica sugerida por Stefan e a adição minha, ficaria claro que . é sempre uma chamada de função (assim como + também), enquanto .. é sempre um campo Acesso.

Sobre a questão de se a.length , etc, é uma boa ideia: que tal . acesso deve ser usado apenas para acessar dados reais no tipo, mais ou menos como se usaria as entradas de um dicionário . Considerando que continuamos com funções para as propriedades sem dados como size , length , step etc. Porque alguns deles precisarão de parâmetros extras e, eu acho, o a.size(1) tipo de sintaxe é ruim.

Aqui está minha opinião sobre este tópico:

  • A sintaxe de ponto deve ser usada apenas para atributos de um tipo / classe. Lembre-se de que não se trata apenas de getters, mas também de setters e algo como a.property() = ... parece completamente errado.
  • Embora eu goste da situação atual em que a função define a API pública e os campos são privados, eu compartilho a opinião de Stefan de que a sintaxe de ponto é boa demais para ser proibida em APIs públicas. Mas, por favor, vamos restringir isso a atributos simples. a.length é um bom exemplo, a.size(1) não porque requer um argumento adicional.
  • Por favor, deixe . padrão para .. . Julia não é conhecida por ser uma linguagem clichê. Vamos manter assim

Por favor, deixe . padrão para .. . Julia não é conhecida por ser uma linguagem clichê. Vamos manter assim

Eu tendo a concordar com isso. A sintaxe para definir até mesmo uma propriedade sintética seria apenas a.property = b , não a.property() = b .

Claro, eu só queria deixar claro por que a.property() como uma sintaxe IMHO não é legal

Ou mais claramente: o importante sobre a sintaxe de ponto não é que se possa associar funções a tipos / classes, mas a capacidade de escrever getters / setters de uma maneira agradável. E getters / setters são importantes para o encapsulamento de dados (mantenha a interface estável, mas altere a implementação)

Essa mudança seria ótima do ponto de vista dos designers de API, mas concordo que deveria vir com algum tipo de guia de estilo para limitar qualquer inconsistência futura.

Isso habilitaria Ruby como dsl's ...

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

Mas esteja preparado para um java como a loucura:

object.propert1.property2.property3 ....

Apenas alguns pensamentos:

  • Eu mais quero a sintaxe . para Dictos com Símbolos como chaves. É melhor usar d.key que d[:key] . Mas no final não é crítico.
  • Acho que a->property lê melhor do que a..property . Mas, novamente, não é grande coisa e não sei se funcionaria com a sintaxe julia.

@BobPortmann eu discordo. Um dicionário é um objeto container, a API para objetos container é obj [index] ou obj [key]. No momento, como não temos propriedades em Julia, a API do contêiner está sobrecarregada para fornecer essa funcionalidade em bibliotecas como PyCall e em OpenCL. Essa alteração ajuda a fortalecer a distinção da API do contêiner, pois ela não será sobrecarregada para fornecer funcionalidade adicional.

Usar a->property para campos privados seria uma boa maneira de manter os hackers C longe de Julia ;-)

Eu meio que gosto da sintaxe .. .

A sintaxe a->property já é falada - essa é uma função anônima. O operador a..b está em jogo há algum tempo, entretanto. Existem alguns casos em que você deseja algo semelhante a um dicionário, mas com muitos campos opcionais. Usar a sintaxe getter / setter para isso seria melhor do que a sintaxe de indexação dict.

"A sintaxe da propriedade a-> já foi falada - essa é uma função anônima."

Sim, claro. Não parecia sem espaços em torno de -> .

Como orientação de estilo, que tal recomendar que a propriedade (x) seja usada para propriedades somente leitura e que x.property seja usada para propriedades de leitura / gravação?

Para propriedades graváveis, x.foo = bar é realmente muito melhor do que set_foo! (X, bar).

Ter foo(x) para ler e x.foo para escrever é bastante confuso. Na verdade, é isso que as propriedades tornam tão atraentes. Ter a mesma sintaxe para acesso de leitura e gravação, ou seja, a sintaxe mais simples que se pode obter (para getters e setters)

Com relação ao estilo, existe a grande questão se queremos ter x.length e length(x) se este recurso for implementado ou se a forma posterior deve ser descontinuada e removida.

Minha opinião é que devemos ter apenas uma maneira de fazer isso e usar apenas x.length no futuro. E em relação ao estilo, acho bastante simples. Tudo o que é uma propriedade simples de um tipo deve ser implementado usando a sintaxe de campo. Tudo o mais com funções. Usei muito as propriedades em C # e raramente encontrei um caso em que não tivesse certeza se algo deveria ser uma propriedade ou não.

Sou contra a alteração de um conjunto de funções de 1 argumento escolhido aleatoriamente para a sintaxe x.f . Acho que @ mauro3 disse que fazer isso obscurece a natureza da linguagem.

a.b é, pelo menos visualmente, uma espécie de construção de escopo. O b não precisa ser um identificador visível globalmente. Essa é uma diferença crucial. Por exemplo, fatorações de matriz com uma parte superior têm uma propriedade .U , mas isso não é realmente uma coisa genérica --- não queremos uma função global U . Claro que isso é um pouco subjetivo, especialmente porque você pode definir facilmente U(x) = x.U . Mas length é um tipo diferente de coisa. É mais útil ser de primeira classe (por exemplo, map(length, lst) ).

Aqui estão as diretrizes que eu sugeriria. A notação foo.bar é apropriada quando:

  1. foo na verdade tem um campo chamado bar . Exemplo: (1:10).start .
  2. foo é uma instância de um grupo de tipos relacionados, alguns dos quais realmente possuem um campo denominado .bar ; mesmo que foo não tenha realmente um campo bar , o valor desse campo está implícito em seu tipo. Exemplos: (1:10).step , (0.1:0.1:0.3).step .
  3. Embora foo não armazene explicitamente bar , ele armazena informações equivalentes em uma forma mais compacta ou eficiente que é menos conveniente de usar. Exemplo: lufact(rand(5,5)).U .
  4. Você está emulando uma API de outra como Python ou Java.

Pode fazer sentido que a propriedade bar seja atribuível nos casos 1 e 3, mas não 2. No caso 2, uma vez que você não pode alterar o tipo de um valor, você não pode alterar a propriedade bar isso está implícito nesse tipo. Nesses casos, você provavelmente deseja impedir a mutação da propriedade bar dos outros tipos relacionados, tornando-os imutáveis ​​ou explicitamente tornando foo.bar = baz um erro.

@tknopp , eu não estava sugerindo usar x.foo para escrever e foo(x) para ler. Minha sugestão foi que _se_ uma propriedade pode ser lida e gravada, então provavelmente você deseja _tanto_ ler e escrever com x.foo .

@StefanKarpinski : Mas length uma caixa de 3. onde os tamanhos são normalmente armazenados e length é o produto dos tamanhos?

Vejo que Jeffs aponta, porém, que essa mudança tornaria essas funções não mais de primeira classe.

@stevengj : Entendo. Desculpe por confundir isso.

@tknopp - o comprimento é derivado dos tamanhos, mas não equivalente a eles. Se você souber os tamanhos, poderá calcular o comprimento, mas não vice-versa. Claro, esta é uma linha um pouco borrada. A principal razão pela qual isso é aceitável para lufact é que não descobrimos uma API melhor do que essa. Outra abordagem seria definir upper e lower funções genéricas que fornecem as partes triangular superior e triangular inferior de matrizes gerais. No entanto, essa abordagem não generaliza para fatorações QR, por exemplo.

É revelador que existem apenas alguns casos que _realmente_ parecem pedir por esta sintaxe: pycall, fatorações e talvez dataframes.
Estou bastante preocupado em acabar com uma confusão aleatória de f(x) vs. x.f ; tornaria o sistema muito mais difícil de aprender.

O ponto 1 da lista de

No momento, posso dizer o que é a API pública de um módulo: todas as funções e tipos exportados (mas não seus campos). Após essa alteração, não seria possível dizer quais campos deveriam pertencer à API pública e quais não. Poderíamos começar nomeando campos privados a._foo ou algo assim, como em python, mas isso não parece tão bom.

Pessoalmente, acho que o caso DataFrames é um pouco supérfluo. Se fizermos isso, adicionarei a funcionalidade a DataFrames, mas acho a perda de consistência muito mais problemática do que salvar alguns caracteres.

Eu também não faria a decisão dependente de DataFrames, PyCall (e Gtk também quer). Queremos porque pensamos que os campos devem fazer parte de uma interface pública (porque "parece bom") ou não o queremos.

... pycall ...

e JavaCall

Visto que o principal caso de uso para isso parece ser interações com sistemas não Julia, que tal usar o operador .. proposto em vez de sobrecarregar . ?

Eu me pergunto se uma solução mais simples aqui é uma gorjeta mais geral 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 isso permitiria que JavaCall / PyCall fizesse definições de método "em" classes, enquanto também permitia um estilo geral se as pessoas quiserem ter algum código do tipo OO, embora seja muito transparente A.b() é apenas uma reescrita. Acho que isso seria muito natural para pessoas que vêm de OO.
Também ter o novo getfield com A..b para permitir a sobrecarga lá, embora sobrecarregar aqui seja fortemente desencorajado e só deve ser usado para propriedades / semelhantes a campo (eu suspeito que não seria usado muito amplamente devido ao leve medo de sobrecarregar getfield(A, ::Field{:field}) .

@ mauro3 :

O ponto 1 da lista de

Essa foi uma lista de quando é permitido usar a notação foo.bar , não quando é necessário. Você pode desabilitar a notação foo.bar para campos "privados", que então só seriam acessíveis via foo..bar .

@karbarcca : Não estou muito claro sobre o que você está propondo aqui.

Fwiw, adoro adotar a abordagem de adultos consentidos por convenção e tornar . totalmente sobrecarregável. Acho que a proposta de ponto duplo levaria a mais confusão, e não a menos.

@ihnorton - como se você fosse contra o uso de a..b como a sintaxe principal (descarregável) para acesso de campo ou contra o uso de a..b para a sintaxe sobrecarregável?

Uma das melhores características do julia é a sua simplicidade. Sobrecarregar x.y parece o primeiro passo na estrada para C ++.

@StefanKarpinski, mas isso significaria uma grande mudança no paradigma de campos privados padrão para campos públicos padrão.

Uma constatação que acabei de ter, provavelmente isso ficou claro para os outros o tempo todo. A programação completa no estilo OO pode ser feita com a sobrecarga básica de . (embora seja feia). Definindo

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

então x.foo(a,b) e x.bar trabalham. Portanto, a discussão sobre se x.size(1) deve ser implementado ou apenas x.size é discutível.

@StefanKarpinski contra a..b geralmente sobrecarregável e morno sobre a..b -> Core.getfield(a,b) .

Eu começo a ver a necessidade de outro operador aqui, mas a..b não é muito convincente. Precisando de dois personagens parece muito ... segunda classe. Talvez a@b , a$b , ou a|b (operadores bit a bit não são usados ​​com freqüência). Uma possibilidade externa também é a b`, que o analisador provavelmente poderia distinguir dos comandos.

Eu ficaria bem em usar o operador "feio" para acesso ao campo primitivo. Acho que a experiência mostrou que, por se tratar de uma operação concreta, raramente é usada e, na verdade, é um tanto perigosa de usar.

Estou sugerindo permitir a simulação de despacho único OO pela convenção / reescrita:

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...)

A justificativa aqui é que já fazemos reescritas de sintaxe semelhantes para getindex / setindex !, então vamos permitir a sintaxe OO completa com isso. Dessa forma, PyCall e JavaCall não precisam fazer

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

Eu gosto disso porque é uma transformação bastante clara, assim como getindex / setindex, mas permite simular um único sistema OO de despacho se desejado, particularmente para pacotes de linguagem OO.

Eu estava sugerindo então o uso do operador .. para acesso de campo, com a opção de sobrecarregar. O uso aqui seria permitir que PyCall / JavaCall simule o acesso de campo, sobrecarregando chamadas para .. , permitindo que DataFrames sobrecarreguem .. para acesso de coluna, etc. Este também seria o novo acesso de campo padrão em geral para qualquer tipo.

Eu tenho um fraco por reescritas de sintaxe pura. É indiscutivelmente uma coisa ruim que você possa escrever a.f(x) agora e fazê-lo funcionar, mas significar algo confusamente diferente da maioria das linguagens OO.

É claro que o outro lado da moeda é a fragmentação de estilo horrível e o fato de que a.f não tem nada em comum com a.f() , fazendo com que a ilusão se desfaça rapidamente.

Uma das melhores características do julia é a sua simplicidade. Sobrecarregar x.y parece o primeiro passo na estrada para C ++.

O mesmo sentimento aqui. Eu estava considerando, se a necessidade real disso é realmente para um número limitado de tipos de interoperabilidade, que tal torná-lo válido apenas se explicitamente solicitado na declaração de tipo? Por exemplo, uma palavra-chave adicional além de type e immutable pode ser ootype ou algo assim.

e o fato de que af não tem nada em comum com af (), fazendo com que a ilusão se desfaça rapidamente.

Você pode esclarecer o que isso significa @JeffBezanson?

Eu esperaria que a.f seja algum tipo de objeto de método se a.f() funcionar.

Ah, entendi. Sim, você definitivamente não seria capaz de fazer algo como map(t.method,collection) .

Vou concordar com @ mauro3 que, ao permitir obj.method(...) , há o risco de que novos usuários vejam julia como outra linguagem orientada a objetos tentando competir com python, ruby ​​etc., e não apreciem totalmente a grandiosidade que é múltipla. O outro risco é que o estilo oo padrão então se torne predominante, pois é com isso que os usuários estão mais familiarizados, ao contrário do estilo mais juliano desenvolvido até agora.

Visto que o caso de uso, diferente de DataFrames, é restrito à interoperabilidade com linguagens oo, isso poderia ser manipulado por macros? ou seja, <strong i="8">@oo</strong> obj.method(a) torna-se method(obj,a) ?

@karbarcca isso significaria que automaticamente tudo poderia ser escrito de duas maneiras:

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

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

t.method (args ...)
# é apenas uma reescrita para
método (t, args ...)

Isso não seria necessário para PyCall, pois o ponto sobrecarregável poderia apenas ser usado para chamar pyobj[:func] por pyobj.func . Então pyobj.func() seria de fato (pyobj.func)() .

Reescrever a.foo(x) como foo(a, x) não resolveria o problema para PyCall, porque foo não é e não pode ser um método Julia, é algo que preciso pesquisar dinamicamente em tempo de execução . Eu preciso reescrever a.foo(x) como getfield(a, Field{:foo})(x) ou similar [ou possivelmente como getfield(a, Field{:foo}, x) ] para que meu getfield{S}(::PyObject, ::Type{Field{S}}) possa fazer a coisa certa.

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

Eu começo a ver a necessidade de outro operador aqui, mas a..b não é muito convincente. Precisar de dois personagens parece muito ... segunda classe

Eu diria que, por outro lado, .. é digitado muito mais rapidamente do que $ , @ ou | pois nenhuma tecla shift precisa ser pressionada , e sendo dois caracteres, o dedo permanece na mesma tecla: sorriso:

@stevengj Ah, entendo. Mas meu ponto ainda é válido, que a reescrita poderia ser feita com uma macro.

Para JavaCall, na verdade, só preciso essencialmente de um manipulador de unknownProperty. Na verdade, não preciso reescrever ou interceptar a leitura ou gravação de propriedades existentes. Então, uma regra de que "ax é reescrito para getfield (a,: x) somente quando x não é uma propriedade existente" ajudaria a manter as coisas sãs?

@simonbyrne , exigir uma macro derrotaria o desejo de uma chamada interlíngua limpa e transparente. Além disso, seria difícil fazer com que funcionasse de forma confiável. Por exemplo, suponha que você tenha type Foo; p::PyObject; end e, para um objeto f::Foo queira fazer foo.p.bar onde bar é uma pesquisa de propriedade Python. É difícil imaginar uma macro que pudesse distinguir com segurança os significados dos dois pontos em foo.p.bar .

Honestamente, não vejo grande problema com o estilo. Pacotes de alta qualidade imitarão o estilo de Base e outros pacotes onde possível, e algumas pessoas escreverão códigos estranhos, não importa o que façamos. Se colocarmos a sobrecarga de pontos em uma seção posterior do manual e recomendarmos seu uso apenas em alguns casos cuidadosamente selecionados (por exemplo, interoperabilidade entre linguagens, propriedades de leitura / gravação, talvez para evitar poluição do namespace para coisas como factor.U , e em geral como uma alternativa mais limpa para foo[:bar] ), então não acho que seremos sobrecarregados com pacotes usando ponto para tudo. O principal é decidir o que _nós_ usaremos e recomendaremos, e provavelmente devemos manter a lista de usos recomendados muito curta e apenas estendê-la quando surgirem necessidades reais.

Não estamos adicionando sintaxe semelhante a OO super fácil, como type Foo; bar(...) = ....; end para foo.bar(...) , então isso limitará a tentação de novatos também.

Estou basicamente de acordo com @stevengj aqui. Eu gosto de a..b para acesso de campo real porque

  1. é semelhante a a.b
  2. é menos conveniente, como deveria ser
  3. é apenas _ um pouco_ menos conveniente
  4. não tem significado existente e não encontramos nenhum uso atraente para ele em mais de um ano
  5. não é terrivelmente estranho como a b`

Com essa mudança e possivelmente (https://github.com/JuliaLang/julia/issues/2403), quase toda a sintaxe de Julia ficará sobrecarregada? (O operador ternário é a única exceção em que consigo pensar). O fato de quase toda a sintaxe ser reduzida a despacho de método sobrecarregável parece ser um recurso fortemente unificador para mim.

Eu concordo que na verdade é uma espécie de simplificação. O operador ternário e && e || são realmente o fluxo de controle, então isso é um pouco diferente. É claro que esse tipo de argumento é contra fazer de a..b o acesso de campo real desde então _que_ seria a única sintaxe não sobrecarregável. Mas ainda acho que é uma boa ideia. A consistência é boa, mas não fundamental por si só.

Oh, também há chamada de função que não é sobrecarregável. Tão básico que me esqueci disso.

É isso que o problema # 2403 aborda.

Sim. Mas isso está muito mais perto de acontecer do que isso.

O único problema para mim aqui é que seria muito bom usar o operador de acesso de campo real para módulos, mas isso provavelmente não acontecerá, pois ninguém quer escrever Package..foo .

O preenchimento com tabulação após os pontos fica um pouco feio; tecnicamente, você tem que verificar qual método x. pode chamar para ver se é apropriado listar nomes de campos de objetos ou nomes de módulos. E espero que ninguém tente definir getfield(::Module, ...) .

Acho que o preenchimento da guia pode ser feito assim: foo.<tab> lista os "campos públicos" e foo..<tab> lista os "campos privados". Para os módulos, estaria tudo bem permitir apenas que a implementação padrão de Mod.foo fosse Mod..foo e apenas dizer às pessoas para não adicionar métodos getfield a Module ? Quer dizer, você já pode redefinir a adição de inteiros na linguagem - o inferno se abre e você obtém um segfault, mas não tentamos evitá-lo. Isso não pode ser pior do que isso, pode?

Na verdade, é um pouco pior do que isso, porque uma linguagem de programação realmente só se preocupa com nomes. Resolver nomes é muito mais importante do que adicionar inteiros.

Não temos muita escolha a não ser Mod.foo padrão para Mod..foo , mas provavelmente teremos que usar Mod..foo para inicialização em alguns lugares. O operador .. é extremamente útil aqui, pois sem ele você não pode nem chamar Core.getfield para definir o fallback. Com ele, provavelmente apenas removeríamos Core.getfield e teríamos apenas .. .

Esse é um ponto justo - nomear é uma coisa importante na programação :-). Parece um bom caminho a seguir - apenas .. e não Core.getfield .

Estas duas ideias,

[...] coloque a sobrecarga de pontos em uma seção posterior do manual e recomende seu uso apenas em alguns casos cuidadosamente selecionados @stevengj https://github.com/JuliaLang/julia/issues/1974#issuecomment -38847340

e

[...] a preferência deve ser usar a sintaxe x.property tanto quanto possível @StefanKarpinski https://github.com/JuliaLang/julia/issues/1974#issuecomment -38694885

são claramente opostos.

Eu acho que se a primeira ideia for escolhida, então apenas criar um novo operador .. para aqueles "casos cuidadosamente selecionados" faz mais sentido.
Como vantagem, usar ..name para casos em que atualmente [:name] é usado (DataFrames, Dict {Symbol, ...}) seria mais amigável de digitação / sintaxe, embora afirmando claramente que algo diferente do acesso ao campo estava acontecendo. Além disso, o ponto duplo em ..name poderia ser visto como dois pontos girados, uma dica para a sintaxe do símbolo :name , e também não haveria problemas com o preenchimento da guia.
Como desvantagem, os usos em PyCall et al. não seria tão parecido com as sintaxes originais (e poderia até ser confuso para os casos em que . realmente deve ser usado). Mas vamos ser honestos, Julia nunca será totalmente compatível com a sintaxe do Python, e sempre haverá casos em que é preciso digitar muito em Julia com PyCall para executar instruções simples em Python. O .. para emular . pode dar um bom equilíbrio aqui. (Por favor, não me interpretem mal, eu realmente gosto do PyCall e acho que é um recurso crítico que merece um cuidado especial)

A segunda ideia, que atualmente prefiro, é a grande decisão sobre quando property(x) ou x.property deve ser usado, o que requer uma definição elegante, bem pensada e clara, se tal existir .
Parece que se as pessoas querem um . sobrecarregável, é porque preferem o estilo de API x.property em primeiro lugar.
De qualquer forma, eu preferiria ver . não como um operador de acesso de campo sobrecarregável, mas como um operador de acesso de "propriedade" sobrecarregável ( getprop(a, Field{:foo}) talvez?), Cujo padrão é um operador de campo não sobrecarregável .. .
Outras decisões também teriam que ser tomadas, por exemplo, qual será usada no código de implementação concreto para acesso ao campo, .. ou . ? Por exemplo, para o exemplo da etapa Ranges, qual será idiomática? step(r::Range1) = one(r..start) ou step(r::Range1) = one(r.start) ? (sem mencionar a questão de se step deve ser um método ou uma propriedade).

É por isso que recuei desse ângulo e propus estes critérios: https://github.com/JuliaLang/julia/issues/1974#issuecomment -38812139.

Apenas um pensamento me veio à cabeça enquanto lia este tópico interessante. A exportação pode ser usada para declarar campos públicos, enquanto todos os campos são visíveis dentro do módulo de definição, por exemplo:

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

Nesta situação, a Pessoa exportada ainda se parece com 'nome' e 'idade', exceto neste caso idade é somente leitura por meio de uma função que adiciona um. Exportar tudo de Person pode ser feito como export Person. * Ou semelhante.

[pao: aspas]

@emeseles Por favor,

. e .. são confusos: uma sintaxe clara e fácil de lembrar é algo bom

Estou realmente ansioso para poder fazer isso. Esta é uma mudança grande o suficiente para que ele (ou o WIP em # 5848) seja sinalizado como um projeto de 0,4?

Sim, é definitivamente um projeto.

Acho que a maioria de nós concorda que seus usos recomendados devem ser limitados, pelo menos para começar. Meu sentimento é que ele deve ser recomendado inicialmente para apenas dois usos: interoperabilidade (com outras linguagens, como em PyCall, e mais geralmente para bibliotecas externas onde a notação de ponto é natural), e talvez para objetos com estado mutável (desde get_foo(x) e set_foo!(x, val) são feios).

Mesmo que o recomendemos apenas para interoperabilidade de chamadas estrangeiras, esse propósito por si só é suficiente para justificar esse recurso em minha opinião. Para uma nova linguagem como Julia, falar sem problemas com o resto do universo do software é extremamente importante.

Steven, não tenho 100% de certeza sobre o getter / setter porque temo que isso logo leve a inconsistências, mas concordo com o outro caso de uso. Além disso, temos em Gtk.jl propriedades dinâmicas que também se beneficiariam da sintaxe. Meu favorito pessoal é a implementação de enum que Stefan descreveu em # 5842.

Colisão. O que está bloqueando o progresso desse problema? É necessária uma decisão ou este problema depende de outras mudanças internas, ainda não feitas, ou é apenas codificação?

O que está bloqueando o progresso desse problema?

Alguém fazendo o trabalho e alguma hesitação sobre se é a coisa certa a fazer.

Observe que @ihnorton já fez um primeiro rascunho de implementação em # 5848. Acho que o trabalho está paralisado principalmente porque é necessária uma declaração clara da equipe principal de Julia sobre se esse é um recurso desejado.

Estou de acordo com isso. @JeffBezanson parece estar em cima do muro.

Para mim, ter esse recurso tornaria a transição de nossa grande base de código Python para Julia mais fácil. Para explicar aos alunos, se eles usarem o código Python, eles precisam de uma sintaxe bem diferente da que estão acostumados, isso pode se tornar difícil.

Tivemos essa discussão acima neste tópico e ainda não consigo ver um acordo completo. Atualmente, várias pessoas pensam que uma API pública é feita de funções / métodos, enquanto a API privada são os campos de um tipo composto. Eu posso ver raras exceções neste esquema. ( .U em uma decomposição LU?)

Isso não significa que eu seja contra, porque o acesso Python e enums são casos em que isso é útil. Ainda assim, pode-se questionar o quão urgente é a necessidade aqui e se seria sensato empurrar isso no final de um ciclo de desenvolvimento.

@ ufechner7 , concordo que a principal motivação é a interoperabilidade entre idiomas. @tknopp , nunca chegaremos a um acordo unânime sobre algo como isso. Em última análise, tudo se resume ao que @JeffBezanson e @StefanKarpinski decidem.

Acho que muito da hesitação vem do que imagino ser o pior pesadelo de Jeff:

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

Eu também não gostaria muito disso - qualquer pacote que decida usá-lo indevidamente dessa forma irá impor seu uso indevido a _todos_ os tipos do sistema, incluindo o meu próprio. Este recurso é muito poderoso e mudará a forma como Julia é escrita. Para melhor e (talvez, mas espero que não) pior.

Sim, claro Steven, isso pode não ter sido dito corretamente de mim. O que quero dizer é que essa mudança pode ter uma grande influência na evolução da linguagem. E a ideia de "interface formal" que temos em outra edição também é influenciada por tornar . sobrecarregável. Então sim vamos @JeffBezanson e @StefanKarpinski decidir. Ainda assim, a questão é se a decisão deve ser executada agora ...

Pelo que vale a pena, passei a favorecer a sobrecarga de quase todas as sintaxes e, em seguida, confiar na cultura para resistir a ir muito longe com ela.

+1 . Acho que há um forte análogo filosófico (e possivelmente prático ...) à sobrecarga de call aqui. O manual precisa de uma seção intitulada Don't do stupid stuff: we won't optimize that . (é claro, a sobrecarga de call foi parcialmente _por_ motivos de desempenho - mas é repleta de potencial para abuso)

  • : 100: para isso. Acho que a cultura deve ser motivação suficiente para não abusar disso

No geral, sou a favor. O potencial para abuso não é minha maior preocupação. Para mim, os grandes problemas são

  • Sintaxe aceitável para acesso ao campo "real". Não gosto muito de a..b .
  • Módulos. Nomes qualificados são extremamente importantes. Expressá-los com envio de método é possível, mas apresenta dificuldades práticas. Ou seja, você precisa passar por muitas fases do compilador (talvez até por inlining) apenas para saber que tem um nome qualificado. Isso torna a vida mais difícil para qualquer pessoa que esteja escrevendo ferramentas que consumam ASTs. Também torna muito fácil lidar incorretamente com este caso em tais ferramentas.

Esses problemas poderiam ser resolvidos de uma só vez usando a mesma sintaxe para ambos, mas é quase impossível imaginar o uso de qualquer coisa além de . para módulos neste ponto. _Internamente_ definitivamente haverá sintaxe abstrata para referências de módulo; seria frustrante se não houvesse uma maneira legal de expor isso.

Meus dois centavos nesta questão: por que não usar : para nomes qualificados? Já está em uso para algo semelhante:

import Base: call, show, size

Isso daria 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)

Ou eles já usam demais o símbolo : ? O símbolo :: (estilo C ++) parece ser muito prolixo para mim.

O : já é o símbolo mais sobrecarregado em Julia, então isso não vai ajudar, infelizmente.

Podemos simplificar o problema de nomenclatura qualificada tornando module.name não sobrecarregável? Uma vez que as ligações de módulo são constantes, isso nos permitiria manter a mesma semântica, mas curto-circuitar toda a lógica normal para pesquisas de nomes qualificados assim que soubermos que o LHS de a.b é um módulo. Acho que é bastante razoável não permitir que as pessoas substituam o que significa procurar um nome em um módulo.

Eu prefiro a sintaxe a..b para acesso de campo real. Qual é a sua objeção a isso?

À parte: eu meio que gostaria que tivéssemos optado por ( ) para listas de importação como algumas das linguagens funcionais. Ie:

import Base (call, show, size)

Meu motivo é que poderíamos tornar as vírgulas opcionais e permitir vírgulas no final. Realmente me irrita que todos os nomes importados precisem de vírgulas à direita, exceto o último que não pode ter uma.

Sim, eu estava prestes a mencionar a possibilidade de fazer a.b significar "se a for um módulo, faça a pesquisa de módulo primeiro". Isso pode ajudar, e certamente não queremos substituir o significado da pesquisa de módulo. Porém, tem algum custo de complexidade, já que não podemos representar a.b como a chamada getfield(a,:b) . Precisa ser um nó AST especial com uma ramificação implícita. Claro que poderíamos usar um branch _explicit_, mas eu me preocuparia com o inchaço do AST com isso.

Não parece haver uma saída fácil para um conflito tão grande entre as necessidades do front-end e do back-end.

Se todo mundo gosta de a..b , acho que posso aprender a conviver com isso. Só me parece que significa algo totalmente diferente, talvez um intervalo.

Eu também não gosto de a..b , mas me pergunto por que seria necessário. Ao ler este tópico, tem-se a impressão de que a sobrecarga só será usada em wrappers de linguagem e casos de uso dinâmicos onde o acesso ao campo real não é necessário.

Porque em algum momento você precisa acessar a representação de um objeto para fazer algo com ele. Alguém poderia argumentar que isso seria relativamente raro e, portanto, feio como get_actual_field(a,:x) , mas parece uma operação muito importante para não ter sintaxe.

Eu vejo isso, mas parece que estamos procurando uma sintaxe que não queremos que ninguém use, certo?

Não fornecer .. seria uma maneira de dizer sim para casos de uso dinâmico, mas não para programação orientada a pontos

Não vejo como isso impediria a programação orientada a pontos; você ainda pode fazer o exemplo de @mbauman acima.

Embora a sintaxe a..b pareça com um intervalo (eu a usei como tal), eu simplesmente não acho que a aritmética de intervalo precisa de sua própria sintaxe de entrada - escrever Interval(a,b) é apenas bem e não há muitas outras coisas para as quais alguém queira usar essa sintaxe, já que é um operador em Julia há anos e ninguém o usa para quase nada. Também se parece com acesso de campo.

Um forro de prata para isso é que podemos substituir o horrível module_name por m..name . Não ser capaz de acessar os campos dos objetos do Módulo foi uma verruga.

Sim, eu estava prestes a mencionar a possibilidade de fazer a.b significar "se a for um módulo, faça a pesquisa de módulo primeiro". Isso pode ajudar, e certamente não queremos substituir o significado da pesquisa de módulo. Porém, tem algum custo de complexidade, já que não podemos representar a.b como a chamada getfield(a,:b) . Precisa ser um nó AST especial com uma ramificação implícita. Claro que poderíamos usar um branch _explicit_, mas eu me preocuparia com o inchaço do AST com isso.

Poderíamos lidar com isso fazendo a.b significar incondicionalmente getfield(a,:b) e, em seguida, tornando um erro adicionar métodos a getfield que cruzam o método getfield(::Module, ::Field) ? É uma forma estranha de impor esse comportamento, mas acabaria tendo o mesmo efeito. Então, rebaixar poderia apenas usar o fato de que sabemos que você não pode fazer isso para trapacear e diminuir module.name para pesquisar nomes qualificados.

Ok, eu afirmo o contrário: alguém neste tópico usaria .. e, se sim, qual seria um caso de uso exemplar? (ou seja, pode obscurecer inteiramente o acesso de campo interno estar ok)

@StefanKarpinski Sim, pode funcionar. Pode ser outro caso em que queremos algum tipo de método "selado".

@tknopp Acessando module..name e module..parent :) Além disso, só para esclarecer, você está defendendo a sintaxe de chamada de função como get(obj,:field) para acesso a campos de baixo nível?

Não, não estou defendendo uma determinada sintaxe. Só acho que seria bom ter certeza de por que esse recurso é necessário e quais são os casos de uso. Para os casos de uso dinâmico, estaria tudo bem

  • a.b é um acesso de campo se Base.getfield(a, ::Field{:b}) não foi definido
  • a.b é a versão dinâmica se Base.getfield(a, ::Field{:b}) for definido. Neste caso, o acesso ao campo real pode ser obscurecido

Minha dúvida era se há casos de uso em que o sombreamento não está ok.

Sim; você pode querer definir pyobject.x para que x seja sempre procurado no dicionário do pyobjeto, para todos os x . Em seguida, um mecanismo separado é necessário para acessar os campos julia do pyobject.

Ahhh, então é tudo ou nada? De alguma forma, tive a impressão de que alguém poderia ter

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

Sim, você _pode_ fazer isso, mas nem todos os objetos o farão. Alguns vão querer definir getfield(a::A, ::Field) para interceptar todos os campos.

Ok, obrigado, agora entendi. Todos os casos de uso dinâmicos desejariam getfield(a::A, ::Field) e, portanto, precisariam de qualquer maneira de chamar os campos internos.

Então, minha opinião é que Core.getfield é suficiente, a menos que alguém encontre um caso de uso prático em que isso seja irritante.

Provavelmente, isso é um dado adquirido, mas também permitiremos a substituição de setfield! , certo? Eu realmente gostaria disso para expor exibições mutáveis ​​em um banco de dados no qual as linhas se tornam tipos.

Sim, essa foi minha impressão.

Ok, IMHO se usar .. para acesso de campo real ou Core.getfield não é um grande negócio. Pode-se apresentar o recurso geral como experimental e fazer com que este assunto seja alterado.

A questão é se isso se encaixará no prazo de 0,4 ou não. Então é # 5848 perto da implementação final e do módulo que pode ser resolvido?

@johnmyleswhite : Eu também votaria por fazer isso simétrico e também permitir setfield! . Em Gtk.jl usaríamos ambos.

Não parece muito claro qual seria a regra quando usar esse recurso e quando não usá-lo. Vejo o ponto para PyCall, onde um método / campo deve ser pesquisado dinamicamente e, portanto, não pode ser um método / tipo composto Julia (e a sintaxe resultante é mais próxima do Python). Mas então, por que usá-lo para Gtk.jl? Se começar a fazer foo.bar = x vez de setbar!(foo, x) , o código padrão de Julia também começará a usar este padrão: é isso que queremos? Talvez seja, mas sejamos claros sobre isso.

Seria aceitável / recomendado usar esse recurso para implementar getters e setters de propriedade definidos para tipos abstratos (e concretos também)?
Acho que isso permitiria evitar o conflito de nomes de métodos que são usados ​​para obter propriedades 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 tem um sistema de propriedade dinâmica que não trata de getters / setters.

@tknopp Ah, OK, de fato. Mas para a maioria das propriedades comuns, você tem uma função getter / setter (rápida), além da propriedade dinâmica. Portanto, você recomendaria usar a função getter / setter quando disponível e a sintaxe de sobrecarga de campo apenas para propriedades que não têm uma? Parece bom para mim - mas é bom ter uma política clara sobre este IMHO.

Na minha opinião, neste ponto (e acho que precisamos experimentar um pouco para descobrir as regras certas), f(x) é melhor quando f faz sentido como um conceito autônomo geral como " comprimento "enquanto x.f deve ser usado quando f não é realmente independente de x . Para tentar encaixar meu exemplo anterior nisso, não é realmente útil ter uma função step genérica, já que a maioria dos vetores e coleções não tem nenhum tipo de noção de passo - ela _apenas_ faz sentido quando você tem um intervalo De algum tipo. Portanto, não há problema em ter x.step como a maneira de obter o passo de um intervalo x . É um tipo de julgamento, mas acho que a vida está cheia disso.

Não gosto de .. , pois não transmite acesso direto a mim. Que tal foo.bar. O ponto extra no final indica que é o acesso direto.

Também pode escolher um símbolo Unicode: ainda temos muitos desses restantes ...

@GlenHertz , isso realmente não funciona se você tiver que encadear acessos de campo.

@simonbyrne , Geralmente sou contra qualquer coisa na linguagem central ou biblioteca padrão que requeira o uso de Unicode. Permitir é uma coisa, forçar as pessoas a usá-lo é outra totalmente diferente.

Na minha opinião, neste ponto (e acho que precisamos experimentar isso um pouco para descobrir as regras certas), f (x) é melhor quando f faz sentido como um conceito autônomo geral como "comprimento", enquanto xf deve ser usado quando f não é realmente independente de x.

Minha regra pessoal para usar esse recurso será: use-o apenas para interoperabilidade de linguagem ou para coisas que são "quase campos" ou "campos aumentados". Por exemplo, posso usar isso para atualizar um cache que depende do valor de todos os campos em um tipo.

Uma grande dúvida que tenho sobre esse recurso: como isso interage com a inferência de tipo? Parece que você está prestes a definir uma função getfield(x::T, s::Symbol) que produz uma saída com tipos diferentes para valores diferentes de s . Isso só funciona porque getfield é mágico? Você pode redefinir a saída de getfield(x, s) para x e s fixos em qualquer ponto de um programa? Em caso afirmativo, como isso se confunde com a incapacidade de redefinir um tipo?

Parece que você está prestes a definir uma função getfield(x::T, s::Symbol) que produz uma saída com tipos diferentes para valores diferentes de s .

É por isso que o plano é expressar isso como getfield{s}(x::T, f::Field{s}) onde s é um símbolo.

Eu tinha sentido falta disso. Obrigado por me esclarecer.

@nalimilan : Sim, os campos sobrecarregados seriam usados ​​apenas para as propriedades dinâmicas. É assim que Jameson quer lidar com isso e eu acho que isso é bom. Todos os getters e setters reais são gerados automaticamente, mas ainda funcionam sem toda a nomenclatura get / set. Ao vivo no módulo GAccessor (curto G_ )

Sobre a sintaxe, por que não usar <- para acesso de campo real?
É semelhante a -> em c ++ que está em uso para lamdas, mas <- está sendo usado no momento.
Pode ser lido a partir do tipo, obtenha o valor diretamente.

Deixaria .. sem uso para aqueles que querem usá-lo em intervalos ainda e estaria usando um par não usado que não tem nenhum outro uso que eu possa pensar até agora.

Não vamos usar a notação de atribuição de R para acesso ao campo.

Poderíamos possivelmente usar -> para espelhar C / C ++ diretamente e obter uma nova sintaxe para
funções anônimas. Nunca me importei muito com a função anônima
sintaxe, pois é um pouco concisa / ilegível. Talvez pudéssemos fazer
algo como

função (x) x ^ 2 end

ou o mais longo, mais consistente

função (x) x ^ 2 fim

Talvez houvesse uma maneira de criar uma boa sintaxe que não requeira
usando um fim.

Para não mudar muito a discussão, mas definitivamente faria sentido
para usar -> para acesso de campo real.

Na quarta-feira, 28 de janeiro de 2015 às 8h49, John Myles White [email protected]
escrevi:

Não vamos usar a notação de atribuição de R para acesso ao campo.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -71857083.

@quinnj : func (x) x^2 end já funciona. Mas é bom ter uma sintaxe muito concisa para funções anônimas: map(x->x^2, 1:10)

Eu não acho que o acesso ao campo precisa de sintaxe especial (um caractere Unicode como @simonbyrne sugeriu está bem como opção). Eu não gostaria de perder x -> x^2 .

Parece que este problema ainda está em aberto / discussão pendente? Foi interessante ler todos os vários comentários aqui sobre o operador de ponto.

Houve alguma sugestão para adicionar outros novos tokens de operador? Usar algo como :> pode ser uma boa alternativa. Tem paralelos com |> e pode ter uma _feel_ mais nativa de Julia:

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

Embora ainda seja muito mais fácil de escrever do que:

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

Ou comparando:

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

Sou novo para Julia, mas rapidamente adquiri um grande apreço pela abordagem de envio múltiplo. A programação orientada a objetos - particularmente para programação científica - torna muitas tarefas mais complicadas. Eu ficaria preocupado se o paradigma / sintaxe OO afetaria negativamente o desenvolvimento do estilo de Julia.

Ou, alternativamente, já que esqueci de estagiar e / ou citar string:

someobj <: field_name |> length

@elcritch , <: é usado atualmente como o operador "é subtipo de" de Julia, e se :> for introduzido, é provável que queiramos usá-lo para algo relacionado ao tipo devido a isso herança.

Se usar instance..member for um problema, aqui estão algumas possibilidades. Proteja seus olhos! É provável que cada um destes seja pior:

  • instance^.member (ponto de cenoura)
  • instance~.member (til ponto)
  • instance:.member (dois pontos)
  • instance*.member (ponto estrela)
  • instance-.member (traço ponto)
  • [email protected] (ponto de arroba)
  • instance&.member (amper ponto)
  • instance$.member (ponto em dólar)
  • instance<.>member (ponto da nave espacial)

Sinceramente, acho que (a) .. parece bom o suficiente e (b) realmente não importa se parece bom, porque esse sempre será um canto obscuro da linguagem. A maioria das pessoas usará instance.member porque elas terão apenas um campo ou um método getfield , mas não ambos.

(Por falar nisso, a maioria das pessoas que desejam usar um membro e um método provavelmente nem se importarão em aprender sobre .. . Eles apenas definirão um método para foo.member e nomearão o "real "campo foo._member . Indiscutivelmente, este é um estilo melhor de qualquer maneira - significa que quando você ler a type definição, ficará imediatamente óbvio que _member não deveria ser algo você pode ou deve acessar diretamente. Isso seria um argumento para tornar .. algo feio e obscuro como :. vez de ocupar um valioso espaço de pontuação.)

Eu sentiria falta da capacidade de usar .. como um operador de intervalo infixo, mas o acesso a campo sobrecarregável é uma compensação que vale a pena. Embora eu esteja hesitante em estar ligeiramente apavorado em adicionar mais significados aos dois pontos, :. não parece tão ruim.

Observe que :. é realmente uma sintaxe válida para symbol(".") agora, então pode não ser bom usá-la. O ponto de que .. é potencialmente útil é bem entendido; não devemos desperdiçá-lo com uma sintaxe que dificilmente alguém usará. Eu ficaria perfeitamente feliz em ir com algo ainda mais feio como @. (uma vez que . não é um nome de macro válido nem pode iniciar um identificador, isso não parece entrar em conflito com nada ) Novamente, este será um canto tão obscuro de Julia que não vale a pena tentar torná-lo bonito.

+1 para apenas fazer isso usando .. e ignorando qualquer potencial feiúra

Sim, vamos para .. qualquer maneira, se pudesse ser usado para .. então eu acho que seria um construtor _range_, mas hey já está lá com dois pontos, por exemplo. start:stop .

Um último golpe: e quanto a .: ? É muito sutil ter ab, a. (B), a. (: B) _e_ a.:b?

@hayd , isso parece muito sutil e fácil de usar por acidente.

@ihnorton , há alguma chance de ressuscitar uma versão do # 5848? Poderíamos dar uma olhada na questão de sintaxe e apenas usar Core.getfield(x, Field{y}) para acessar o campo "real".

Deixando de lado a sintaxe de Core.getfield , ainda há alguma questão substantiva restante?

Em # 5848, @tknopp sugeriu tornar apenas o acesso de campo "real" sobrecarregável, ao contrário da sugestão de x::Vector{Any} , fazer x[i].y pode ser interpretado getfield(x[i], Field{:y}) e o sistema de despacho fará a coisa certa independentemente de y é um campo real, enquanto se você quiser chamar apenas getfield para campos "virtuais", o codegen terá que implementar um subconjunto em miniatura do sistema de despacho para verificação em tempo de execução de x[i] type.

Outra questão era se Module.foo deveria ser sobrecarregável. Por um lado, há uma certa consistência em usar getfield para tudo, e o exemplo acima mencionado de Vector{Any} poderia ter Module membros da matriz, então teríamos que lidar com esse caso de qualquer forma. Por outro lado, @JeffBezanson apontou que isso poderia tornar a compilação mais difícil e tornar o comportamento de declarações como function Base.sum(...) difíceis de entender. Minha preferência seria tornar Module.foo não sobrecarregável, pelo menos por agora, em qualquer caso onde o compilador saiba que está trabalhando com Module (ou seja, não Vector{Any} ) ; a ligeira inconsistência parece valer a pena para ser conservador sobre o que é alterado.

+1 para não permitir a sobrecarga de Module.foo .

Para falar aqui, uma área da computação científica onde a programação e sintaxe OO é realmente superior ao FP é a modelagem baseada em agente. Embora eu sinta falta da herança concreta e múltipla para configurar hierarquias de agentes, as abstrações leves e rápidas e a rápida prototipagem de Julia são incríveis - já alguns frameworks ABM surgiram.

No ABM, a notação de ponto é preferível para expressar as interações do agente: Agente1.dosomething (Agente2) vs dosomething (Agente1, Agente2).

Este não é o maior caso de uso óbvio, mas seria bom manter este açúcar sintático para pensar e codificar sobre ABMs.

Eu também gostaria muito de ter essa sintaxe disponível no Julia. Como
tanto quanto eu aprecio a abordagem orientada à função de um design
perspectiva, a sintaxe de chamada de método é muito útil e legível em vários
domínios. Seria ótimo se Ab (C) fosse equivalente a b (A, C).
Em 22 de abril de 2015, 8h50, "datnamer" [email protected] escreveu:

Para falar aqui, uma área da computação científica onde a programação OO
e a sintaxe é realmente superior ao FP é a modelagem baseada em agente. embora eu
perder herança concreta e múltipla para configurar hierarquias de agentes, o
abstrações leves e rápidas e prototipagem rápida de Julia é
incrível - já surgiram algumas estruturas ABM.

No ABM, a notação de ponto é preferível para expressar as interações do agente:
Agente1.dosomething (Agente2) vs Dosomething (Agente1, Agente2).

Este óbvio não é o maior caso de uso, mas seria bom manter este
açúcar sintático para pensar e codificar sobre ABMs.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95218555.

No ABM, a notação de ponto é preferível para expressar as interações do agente: Agente1.dosomething (Agente2) vs dosomething (Agente1, Agente2).

Por que isso é melhor? Edit: Quero dizer neste contexto ABM especificamente.

Por favor, não vamos cair em guerras religiosas por causa da ortografia. @ dbeach24 , ninguém está propondo que a.b(c) seja equivalente em Julia a b(a,c) ; isso não vai acontecer.

Pontos sobrecarregáveis ​​são cruciais para a interoperabilidade natural com outras linguagens. Isso é motivo suficiente.

Subject.Verb (DirectObject)

É bastante natural em vários contextos. Muitos programadores OO estão acostumados a
e, embora seja um mero reordenamento de função (A, B), esse reordenamento
faz muito para legibilidade, IMO.
Em 22 de abril de 2015 10:32, "Andy Hayden" [email protected] escreveu:

No ABM, a notação de ponto é preferível para expressar as interações do agente:
Agente1.dosomething (Agente2) vs Dosomething (Agente1, Agente2).

Por que isso é melhor?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95256390.

Eu estava propondo isso. (Desculpe, eu não queria começar uma guerra, nem sabia
a sugestão seria impopular.) Achei que já tivesse visto isso antes
nos fóruns, mas não percebeu que já havia sido descartado como um
péssima ideia. Posso perguntar por que? (Você pode me apontar um tópico?)

Obrigado.
Em 22 de abril de 2015 11:09, "Steven G. Johnson" [email protected]
escrevi:

Por favor, não vamos cair em guerras religiosas por causa da ortografia. @ dbeach24
https://github.com/dbeach24 , ninguém está propondo que ab (c) seja
equivalente em Julia a b (a, c); isso não vai acontecer.

Pontos sobrecarregáveis ​​são cruciais para uma interoperabilidade suave com outras línguas.
Isso é motivo suficiente.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95266671.

Uma razão para não fazer isso é que a.b procura b no escopo de a , enquanto b sozinho procura b em o escopo envolvente. Seria muito confuso se o acesso com pontos às vezes não fosse exibido no objeto do lado esquerdo.

A propósito, é considerado um recurso que as funções em Julia não são pesquisadas dentro dos objetos, mas no escopo atual. Acredito que o medo de que as pessoas comecem a usar funções examinadas dentro dos objetos seja uma das razões que tem impedido a sobrecarga de pontos.

@toivoh , qualquer implementação de sobrecarga de pontos usaria o método de despacho existente, portanto, não mudaria o comportamento do escopo. @ dbeach24 , a razão básica para não encorajar o uso indiscriminado de a.b(c) é que se você tiver muitas sintaxes para fazer a mesma coisa, a linguagem e as bibliotecas se transformam em uma bagunça. É melhor escolher uma grafia e ficar com ela, e o despacho múltiplo de Julia favorece b(a,c) porque é mais claro que b não é "propriedade" de a - o b(a,c) método a e c .

A maior exceção, é claro, é para chamar bibliotecas externas em linguagens como Python ou C ++, onde é bom ser capaz de espelhar a sintaxe de ponto da biblioteca que você está chamando. (Para que, por exemplo, as pessoas possam traduzir a documentação e os exemplos diretamente para o Julia sem mudar muito.)

Estou todo molhado, mas ab (arg) não significaria pegar uma função anônima armazenada no campo b de a e então avaliá-la com o argumento fornecido?

Enviado do meu iPhone

Em 22 de abril de 2015, às 17h06, Steven G. Johnson [email protected] escreveu:

externo

@ScottPJones Isso atualmente funciona bem, mas geralmente não é considerado um bom estilo.

Eu não estava preocupado com estilo ou não, só pensei que, uma vez que já tinha um significado, que era consistente com a forma como Julia trabalha (ou seja, ser capaz de armazenar funções anônimas em campos), que era um bom argumento _não_ para tente tratar ab (arg) como se fosse b (a, arg).
Eu _poderia_ ter uma utilidade para ter um struct (tipo) com membros armazenando funções anônimas, no entanto, onde estou carregando funções escritas em Julia a partir de um banco de dados e, em seguida, analisando-as e armazenando as funções em um objeto ...
Qual seria o melhor estilo “Juliano” para fazer algo assim?

Obrigado!

@ScottPJones Acho que há um consenso de que eles não deveriam ser equivalentes *.

Pode haver exceções à regra de "estilo", mas deve haver um caso convincente para usar colocar uma função no campo, o mesmo que para a sobrecarga de pontos. Acho que a questão é que as pessoas não deveriam fazer isso quer queira quer não / só por causa disso / porque podem.

Isso pode ser um exemplo, mas também pode haver uma maneira melhor (certamente não é a única maneira) ...

99% + das vezes é melhor despachar no typeof (a); sem campos de função, sem sobrecarga de pontos.

* _No entanto, acho que todo mundo sabe que no segundo que isso pousar haverá um pacote que faz exatamente isso ..._

Em D, eles até têm um nome "sintaxe de chamada de função uniforme" para a.b(arg) e é bastante popular, mas acho que está profundamente em desacordo com a função genérica, modo de despacho múltiplo de Julia funciona. Se as funções em questão forem anônimas ou completamente digitadas, suponho que as coisas funcionarão, mas isso é bastante restritivo, IMO. Não acho que haja muitos motivos para armazenar campos de função dentro de um tipo composto, exceto por hábito das linguagens OO tradicionais baseadas em classes. Um lugar melhor para armazenar funções genéricas se você estiver carregando-as de algum lugar seria em módulos.

Mas chegamos muito longe de "questões substantivas" agora. Também sou a favor de ser conservador com a forma como permitimos a sobrecarga do getfield, não permitindo a sobrecarga do getfield nos módulos e não me preocupando muito com a sintaxe especial para "true getfield."

@stevengj : Sim, e como eu estava tentando dizer, essa é uma razão fundamental pela qual nunca vai acontecer que a.b(c) se torne igual a b(a, c) , independentemente do que fazemos com a sobrecarga de pontos. .

Houve alguma discussão na lista de discussão sobre isso. Da minha perspectiva, a postagem mais relevante (por @nalimilan) para este tópico é: https://groups.google.com/d/msg/julia-users/yC-sw9ykZwM/-607E_FPtl0J

Adicionando ao comentário de @johnmyleswhite sobre a política pessoal sobre quando usar este recurso - me getfield() não deve ter efeitos colaterais e setfield!() deve ser idempotente (ou seja, chamá-lo várias vezes com o mesmo valor deve ter o mesmo efeito que chamá-lo uma vez). Não necessariamente regras rígidas que são aplicadas pelo compilador, mas diretrizes de uso para evitar que as coisas fiquem muito malucas.

Publiquei uma solução alternativa usando tipos paramétricos com parâmetros de ponteiro e convertidos para chamar um configurador personalizado ao definir um campo:
postagem: https://groups.google.com/forum/#!topic/julia -users / _I0VosEGa8o
código: https://github.com/barche/CppWrapper/blob/master/test/property.jl

Estou me perguntando se devo usar essa abordagem em meu pacote como uma solução alternativa até setfield! sobrecarga está disponível ou é muito estresse no sistema de tipo paramétrico?

Gostaria de mencionar um benefício adicional de sobrecarga de getfield / setfield! , espero que este seja o lugar certo para isso, desculpe o contrário. (Um tópico relacionado surgiu em https://groups.google.com/forum/#!topic/julia-users/ThQyCUgWb_Q)

Julia com getfield / setfield! a sobrecarga permitiria uma implementação surpreendentemente elegante da funcionalidade autoreload em um _pacote externo_. (Veja todo o trabalho árduo que teve que ser colocado na extensão autoreload IPython https://ipython.org/ipython-doc/3/config/extensions/autoreload.html para obter esta funcionalidade.) A ideia do autoreload é que você pode modificar funções e tipos em módulos externos enquanto trabalha com o REPL.

TLDR: sobrecarga de getfield / setfield! , dicionários e um pacote semelhante a https://github.com/malmaud/Autoreload.jl deve resolver o problema.


Para ser mais específico, imagine um pacote semelhante ao Autoreload.jl que faz o seguinte.

Você primeiro cria um módulo M.jl:

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

No REPL, você digita

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

Então você muda M.jl para

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

Isso seria carregado automaticamente e transformado em

# 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

e então no REPL você poderia fazer

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

O desempenho não seria pior do que com Python, portanto, as pessoas que migram seu fluxo de trabalho do autoreload IPython não perderiam nada em termos de desempenho. E depois de reiniciar o REPL, você está de volta ao desempenho total.

Cansei de escrever a[:field][:field2][:morestuff](b[:random_stuff]) porque não é realmente legível. Então eu escrevi esta pequena macro que funciona para meus casos de uso em 0.4 e 0.5
https://github.com/sneusse/DotOverload.jl

TL; DR
Uma macro que transforma o AST de uma expressão a.b -> getMember(a, :b)

Removendo de 0,6, já que não há consenso de que isso é uma boa ideia e há uma proposta conflitante sobre o que fazer com a sintaxe de pontos.

@Keno : Você tem um link para a proposta conflitante?

Não acho que @StefanKarpinski tenha escrito isso ainda, mas espero que haja um Julep sobre isso em breve.

Achei object.fieldname mais agradável do que funções getter como fieldname(object) ou get_fieldname(object) . Talvez object.fieldname (ou object$fieldname ) seja uma chamada para getpublicfield (talvez com um nome melhor) e object..fieldname sendo o getfield real (privado) pode ser uma boa opção. Dessa forma, os tipos devem definir getpublicfield vez de getters, e tentar fazer object.fieldname deve fornecer um id de erro o campo é privado (será privado se não tiver uma definição para getpublicfield ).

Eu adicionei o rótulo de decisão. Este assunto foi discutido longamente e deve ser feito ou não. Ao ler para # 5848, parecia que @JeffBezanson @StefanKarpinski e @stevengj querem isso. Em caso afirmativo, esse problema deve ter um marco para que não seja esquecido. Caso contrário, feche. Em qualquer caso, acho que essa é uma mudança que deve ser feita antes da 1.0.

@JeffBezanson e eu estávamos discutindo isso ontem. Conclusões provisórias: (i) sim, devemos ter isso; (ii) não permite a sobrecarga de pontos para Module (o que será especialmente tratado); (iii) não forneça nenhuma sintaxe especial para Core.getfield (uma vez que não há necessidade urgente de um getfield sobrecarregado ter o mesmo nome de um campo "real"; o último pode apenas começar com um sublinhado).

@stevengj : Parece um plano razoável. Você poderia indicar se isso será restrito a um único argumento ou se a versão de vários argumentos a.fieldname(b) também deve ser suportada? Isso levará a uma conclusão para a discussão acima. Além disso, seria ótimo colocar um rótulo de marco apropriado para isso (1.0?). Obrigado!

Jeff e eu não discutimos o caso de vários argumentos. Minha sensação é que podemos também apoiá-lo, já que você pode simular de qualquer maneira, retornando uma função do caso sem arg (mas não é crítico fazer imediatamente pelo mesmo motivo).

Eu uso um conversor para lançar valores e validar dados.
como isso:

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

Aqui está uma implementação divertida de 3 linhas disso:

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))

Meu pensamento é que a.b deve realmente chamar uma função de projeção Field{:b}() vez de getfield , de modo que você obtenha funções como x->x.a já de graça. Isso também permite que getfield sempre signifique acesso de campo de baixo nível.

A implementação acima funciona completamente, mas é muito difícil para o compilador (sysimg + 5%, o que é uma surpresa agradável, na verdade). Portanto, isso vai precisar de algumas heurísticas de especialização e algumas otimizações iniciais precisam ser atualizadas, mas espero que isso seja viável.

Estou surpreso que os testes possam passar com essa implementação. Essa semântica não torna impossível para o codegen otimizar as referências do módulo? Também parece que tornará as variáveis ​​globais muito mais caras (velocidade e memória). Não parece muito provável que isso apareça no sysimg. Embora a degradação de inferência pareça que deveria ter deixado o sysimg menor aqui, então 5% pior não parece um bom começo)

Sim, existe um código em jl_resolve_globals para convertê-los em GlobalRefs que precisariam ser atualizados. Fora isso, eles devem ser incorporados para que seja possível para o codegen lidar com eles. Na inferência, provavelmente precisamos apenas de um caso especial semelhante ao da tupla getindex .

provavelmente só precisamos de um caso especial semelhante ao de tupla getindex

Esse caso especial espera e assume que nenhum método será definido que cruze com a assinatura de método getindex embutida. Não vejo como esse caso é especialmente aplicável aqui.

Eu adicionaria um método para ::Module e diria que você não tem permissão para ocultá-lo --- possivelmente aplicado com um erro real.

@JeffBezanson Você tem uma filial com essas três linhas? Eu mesmo tentei adicioná-los a uma construção local, mas julia parece ter mudado agora e eu não consegui fazer funcionar.

Devo dizer que, se eu tivesse apenas um desejo para um recurso em julia 1.0, seria esse. Isso resolveria dois problemas antigos que tenho no Mimi e no Query .

Acho que o caso de Query é bastante genérico. Eu acredito que alguém poderia escrever uma versão de NamedTuples com isso que não tivesse que gerar novos tipos em macros, e que por sua vez me permitiria escrever funções geradas que retornam NamedTuple com um conjunto de campos que são calculado na função gerada. Acho que isso me permitiria escrever uma versão estável de tipo de blackrock / NamedTuples.jl # 4, que é de longe meu maior obstáculo no Query.jl agora.

Resumindo, isso seria super, super valioso para toda a história de tratamento de dados em Julia.

A sintaxe abaixo está disponível?

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

Por que diabos você iria querer essa sintaxe?

Querer escrever obj.plus(n) vez de obj + n é uma séria síndrome de Estocolmo.

Ok, o exemplo dado era trivial (e ruim).
Foi apenas uma ideia para usar esta sintaxe em vez de sobrecarregar getfield e poder usar argumentos.

Estou mais preocupado com a funcionalidade. Ter uma sintaxe abreviada para sobrecarregar getfield parece muito menos importante e não vale a pena se prender a discutir sobre isso. Além disso, getfield e setfield! espelham diretamente getindex e setindex! e, portanto, são um tanto naturais em Julia. Finalmente, o uso extensivo de um estilo OO não é realmente um bom ajuste para o idioma de Julia, e não acho que queremos encorajá-lo por uma sintaxe de definição de método semelhante a OO (mas veja meu comentário acima sobre os argumentos).

Uma coisa que me ocorreu foi que o operador $ estava obsoleto. Agora, isso obviamente torna algo como $(x, sym::Symbol) = ... já disponível, mas também podemos entreter uma reescrita sintática mais elaborada como:

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

Acho que isso cobre a maioria dos casos mencionados nesta edição, sem sobrecarregar totalmente o getfield. Francamente, o operador . é bastante saturado semanticamente em Julia, então algo como isso parece mais fácil de digerir e é conveniente o suficiente para ainda ser útil.

@quinnj , já proposto em # 18696.

Como temos . para acesso ao campo, no entanto, parece deselegante ter dois operadores do tipo acesso ao campo, um sobrecarregável e outro não. E seria um pouco anormal para chamadas entre linguagens para Python e outras linguagens OO, que quase universalmente usam . .

Não vejo a interoperabilidade com outras linguagens como um argumento válido para a introdução de algo assim. É como dizer: "O código Python se parece com isso, portanto, para fingir ser Python, devemos fazer isso também." Ainda estou para ver um argumento para isso que torne a própria Julia melhor e / ou mais consistente. Já está bem estabelecido que Julia não fornece a sintaxe do estilo OOP x.f() ; permitir coisas assim é pedir inconsistência.

@stevengj , parte de onde venho é o fato de que x.f _não_ é acesso ao campo. Não há nenhum membro de campo real f . Toda essa questão é sobre sobrecarregar getfield e eu acho que as principais preocupações são a confusão potencial de x.f realmente se referir a um campo ou se está fazendo alguma outra coisa nos bastidores.

A vantagem da reescrita sintática que propus é que ela cumpre a história de interoperabilidade de linguagem sem introduzir confusão adicional com getfield; é a isso que me referia quando mencionei que . seria supersaturado. Seria x$f realmente muito mais pesado? Eu simplesmente não vejo por que isso _tem_ para usar o operador ponto.

Toda essa questão é sobre sobrecarregar getfield e eu acho que as principais preocupações são a confusão potencial de se x.f realmente se refere a um campo ou está fazendo outra coisa nos bastidores.

Não acho que haja qualquer potencial para confusão na proposta de três linhas de @JeffBezanson : a.b é sempre reduzido para uma chamada de função de projeção, e nunca para uma chamada getfield . E então há um método padrão / fallback na base para a função de projeção que chama getfield . Os usuários podem adicionar métodos para seus próprios tipos a essa função de projeção, se desejarem. getfield sempre significa acesso de campo de baixo nível nessa proposta, e os usuários não adicionariam métodos a getfield (presumo que seja isso o que você quis dizer com "sobrecarregar getfield"). Isso parece muito claro para mim. Dito isso, não está claro para mim se essa proposta ainda está em cima da mesa ou não, parece que há algumas preocupações do compilador que eu não entendo :)

Eu realmente adoraria ter esse recurso (sempre adorei no Scala). IMHO acessar campos diretamente é muito natural em Julia (em comparação, por exemplo, com a maneira Java de definir getters e setters defensivamente para tudo). Mas sem a capacidade de adicionar campos "virtuais", torna-se muito difícil evoluir / refatorar estruturas sem quebrar muito código.

Eu sei que isso foi marcado como v2.0, mas gostaria de trazer isso à tona novamente na esperança de reconsiderar isso para a v1.0. Este recurso será muito valioso para desenvolvedores de pacotes / bibliotecas.

Ao construir um pacote para usuários, sobrecarregar o acessador de atributo permitirá que os mantenedores do pacote apresentem uma interface de usuário muito mais limpa. Eu argumentaria que,

complex_data_structure.attribute

é mais fácil de digitar do que

get(complex_data_structure, :attribute)

onde get é uma função necessária para obter dados que são descritos por :attribute .

Isso também poderia melhorar potencialmente a descoberta no REPL e beneficiaria o código de linguagem e a exploração de dados.

Além disso, um interceptor de atributo setter também seria incrivelmente valioso. Considere o seguinte exemplo (já encontrado em muitos dos meus scripts atualmente :)),

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

Se um getter e um setter de atributo fossem incluídos como parte de Julia, o acima se tornaria o seguinte.

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

Na minha humilde opinião, isso é muito mais legível. (Nota secundária: Eu poderia usar uma composição de tubo para torná-la mais legível, mas additional_arguments novamente complicaria isso novamente. E o argumento aqui ainda permaneceria.)

Além disso, restringir os valores fornecidos por um usuário é um caso de uso muito comum, e será ótimo tê-lo na v1.0. Vai melhorar muito as interfaces de usuário para vários pacotes. Atualmente, até onde sei, não parece haver maneira de fazer o seguinte (alguém pode me corrigir se eu estiver errado)?

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)

O construtor pode restringir as entradas do domínio a uma estrutura imutável, no entanto, um usuário ainda pode inserir valores que não são válidos. Os getters e setters de atributos ajudariam aqui, porque tornariam o feedback sobre a validade dos dados mais imediato, e isso tornará a interface muito mais limpa como um usuário da linguagem.

Isso também beneficiará muito outros pacotes, como PyCall.jl e DataFrames.jl, para construir interfaces indiscutivelmente melhores e mais intuitivas para os usuários. Os autores desses pacotes também expressaram interesse em ter esse recurso.

Pensamentos? Lendo o tópico, parece que já está perto do final. Estou curioso para saber o que os autores pensam sobre isso?

Implementado em uma macro opcional estável de tipo aqui: https://github.com/bramtayl/DotOverloading.jl. Acho que tem potencial para o Base.

@bramtayl , acho que colocar @overload_dots antes de a.b expressões acaba com o ponto aqui.

Eu não estava sugerindo que a macro fosse adicionada à base; Eu estava sugerindo que a estratégia usada pela macro fosse incorporada ao analisador. No entanto, é uma macro que pode ser executada em arquivos inteiros ou mesmo hackeada em um IDE.

Isso implicaria em a.b sendo analisado para Expr(:., :a, :(Val{:b}()) e reduzido para get_field_overloadable(a, Val{:b}())

@bramtayl , veja a implementação de Jeff

Um problema com a sobrecarga de getfield é que algumas bibliotecas não têm uma interface razoável para o interior de uma estrutura de dados. Quando tudo que eu quero é modificar um ponto de dados em uma estrutura para que possa executar meu código e fazer meu relatório para amanhã, não estou particularmente ansioso para editar o código em uma biblioteca de três níveis de profundidade em minha dependência para que eu seja capaz de para acessar diretamente o ponto de dados de uma maneira razoável e rápida. Quero sentir que tenho controle sobre as estruturas de dados que estou usando.

O segundo ponto é que, quando estou usando getfield e setfield, desejo uma expectativa razoável de que estou acessando diretamente a estrutura de dados em vez de algum mecanismo de função enorme. Além disso, a palavra-chave struct é usada para definir um tipo, lembrando você de C, e eu sinto que ela dá a você a expectativa de que o acesso getfield deve ser direto.

Então, eu acho que usar o operador $ é um meio-termo razoável, uma vez que não há expectativa de que o acesso seja direto como em getfield, ele já é um caractere especial em outros contextos e, portanto, não será muito surpreendente quando for usado dessa maneira, será fácil descontinuar porque poucas pessoas o usam e porque não será mais uma função, e é usado de forma semelhante em R.

Um problema com a sobrecarga de getfield é que algumas bibliotecas não têm uma interface razoável para o interior de uma estrutura de dados.

A implementação de @JeffBezanson acima não sobrecarrega getfield (ou setfield ).

Eu também gostaria de implorar para que isso chegasse ao 1.0. Freqüentemente, fico dividido entre escrever getters / settings para manter meu código um tanto flexível (e depois me perguntar como nomeá-los) ou "permitir / encorajar" o usuário do meu código a usar o acesso direto ao campo.

Escrever de forma defensiva muitos getters e setters antecipadamente não parece muito Juliano para mim - afinal, Julia não tem campos privados / protegidos e, portanto, incentiva o usuário a acessar os campos diretamente. Então há a questão de como nomear várias funções getter- / setter sem arriscar muitos conflitos com outros pacotes. E a alternativa, adicionar métodos para getfield e setfield! (ou similar), despachados em Val{:fieldname} ou assim, também não parece muito amigável.

Por outro lado, se o acesso direto ao campo basicamente bloqueia todos os structs para sempre, isso claramente não deve ser encorajado - especialmente para código de longa duração. A implementação de @JeffBezanson parece ser uma maneira simples de sair desse dilema.

Certo, @davidanthoff. Devo estar confundindo sobrecarga de getfield e sobrecarga da sintaxe de acesso de campo . . Eu quis dizer sobrecarregar a sintaxe de acesso ao campo.

Existem maneiras compatíveis com versões anteriores de adicionar esse recurso, então ele poderia ser adicionado em uma versão 1.x, mas não está acontecendo na 1.0. Não temos um design finalizado ou tempo para implementá-lo, mesmo que o tivéssemos.

Seria extremamente conveniente usar a sintaxe de pontos em ponteiros para estruturas (C-).

Minha sintaxe preferida seria usar pontos apenas para mover e converter ponteiros e usar [] para remover o código.

Então, struct somepair a :: Int b :: Int end e p :: Ptr {somepair}, então pa é um ponteiro para o campo a, e escrevo nele com pa [] = 3, ou leio com x = pa [].

Em seguida, só precisamos encaminhar declarações de tipos e, possivelmente, uma maneira de importar cabeçalhos C, e então agrupar C se torna uma brisa.

Ps, para esclarecer, 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 obter pontos de bônus, os tipos da biblioteca de tempo de execução do julia podem ser exportados, e o pointer_from_objref pode nos dar um ponteiro bem digitado para uma introspecção mais conveniente.

Tenho a sensação de que os sindicatos devem funcionar automaticamente, apenas por ter dois campos com o mesmo deslocamento.

@chethega Acho que você está procurando https://github.com/JuliaLang/julia/pull/21912 , que fornece aproximadamente o mesmo recurso, sem os problemas que cercam a adoção do modelo de memória C (violações de trocadilhos, fora de -bounds access, aliasing de interiores ponteiros, etc, que limitam as otimizações de desempenho possíveis nessa linguagem).

A propagação constante torna isso mais factível?

Pls altere o marco para 1.0! :)

@ Liso77 O que você quer dizer? Isso já está implementado no git master, conforme observado no status ao fechar.

@nalimilan desculpe se entendi errado! Mas eu pensei que como 1.x são rotulados coisas adiadas que serão resolvidas após 1.0. E isso está resolvido agora ...

O código aberto é uma comunidade descentralizada. O marco define o que deve ser concluído em 1.0, mas contribuidores e trabalhar no que quiserem. Nesse caso, alguém queria isso no 1.0, então contribuiu com o código para chegar lá.

@ Liso77 Pelo que entendi, isso não será na v1.0. A data de congelamento do recurso Julia v1.0 foi definida para 15 de dezembro, mas esse problema foi encerrado em 17 de dezembro, então acho que podemos esperar um lançamento 1.x. Os desenvolvedores principais podem me corrigir se minha interpretação estiver incorreta.

Não, ele está mesclado com o mestre e estará na próxima versão.

:) Bem! Eu apenas pensei que é bom rotular 1.0 o que está saindo em 1.0. Se não for desejado, desculpe por incomodar! :)

Acho que o arquivo NEWS é a melhor maneira de ver o que está acontecendo no 1.0.

O marco foi adicionado para significar "pode ​​ser implementado sem quebrar a compatibilidade na série de lançamento 1.x", mas desde que o código estava pronto, ele foi mesclado de qualquer maneira antes do congelamento do recurso 1.0. Removi o marco para maior clareza, mas neste ponto tudo o que é mesclado no mestre estará em 1.0.

Obrigado pelo esclarecimento! Isso é emocionante! O arquivo NEWS também foi particularmente esclarecedor.

Obrigado por remover! Também estou muito feliz que virá em 1.0! :)

Eu me pergunto se há uma maneira de suportar esses novos "campos definidos dinamicamente" no preenchimento automático, por exemplo, permitindo sobrecarregar fieldnames ?. Isso pode ser muito poderoso, para uso interativo, por exemplo, ao lidar com DataFrame s (assumindo que eles irão suportar df.column_name no futuro) com muitas colunas e / ou nomes de coluna longos.

Eu acho que no momento o REPL (expansão de guia), IJulia, etc. olhe para a definição de tipo, não a instância? Mas talvez isso pudesse ser mudado, para uso interativo. Provavelmente, é impossível oferecer suporte em um IDE como o Juno, pois as instâncias não estão disponíveis durante a codificação.

@oschulz , você já pode sobrecarregar 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

E parece que fieldnames é o que o REPL analisa:

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

julia> x.ba<tab>
bar baz

@yurivish certo - mas é "seguro" fazê-lo atualmente? Não tenho certeza do que mais depende de fieldnames .

Se não for seguro, deve ser possível definir um complete_fieldnames(x) = fieldnames(x) , usar complete_fieldnames para as conclusões REPL e sobrecarregá-lo para as conclusões personalizadas.

Sim, é por isso que eu estava trazendo isso - no caso de algo precisar ser alterado / adicionado no Base, para que REPL e amigos possam tirar proveito disso mais tarde. Em vista do congelamento de recursos de 0,7 ...

É por isso que estou feliz por termos chamado a função getproperty ; ajuda a esclarecer que não se refere à mesma coisa que fieldnames . Interferir em fieldnames definitivamente pode causar sérios problemas.

Podemos precisar de um propertynames para fornecer os valores de propriedade válidos. Claro, esse pode não ser um conceito bem definido, então talvez queiramos algo mais específico para ser concluído.

Tive a sensação de que isso pode exigir um pouco mais de análise e sugestões dos especialistas (obrigado!) - devemos abrir uma nova edição para isso?

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