Julia: разрешить перегрузку синтаксиса доступа к полю ab

Созданный на 10 янв. 2013  ·  249Комментарии  ·  Источник: JuliaLang/julia

Воспитано здесь: https://github.com/JuliaLang/julia/issues/1263.

decision parser

Самый полезный комментарий

Вот забавная трехстрочная реализация этого:

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

Я считаю, что a.b должен фактически вызывать функцию проекции Field{:b}() вместо getfield , чтобы вы получали такие функции, как x->x.a уже бесплатно. Это также позволяет getfield всегда означать низкоуровневый доступ к полю.

Вышеупомянутая реализация полностью работает, но довольно сложна для компилятора (sysimg + 5%, что действительно является приятным сюрпризом). Так что для этого потребуется некоторая эвристика специализации и некоторые ранние оптимизации, которые необходимо обновить, но, надеюсь, это будет жизнеспособным.

Все 249 Комментарий

Возможность использовать точки в качестве синтаксического сахара для методов мутатора / доступа была бы полезна для многих вещей. Я всегда ценил это в языках, которые его предоставляют, так что вы можете превращать поля структуры в более сложные абстракции, не нарушая API.

+1

У меня есть отличный способ реализовать это.

Хотите поговорить об этом? Я знаю, что Том Шорт действительно заинтересован в наличии этого для DataFrames, хотя я все больше скептически отношусь к целесообразности использования этой функции.

Это сделало бы вызов кода Python (через PyCall) значительно лучше, поскольку в настоящее время я вынужден делать a[:b] вместо a.b .

@JeffBezanson , есть ли шанс получить это за 0.3? Отлично подходит для межъязыкового взаимодействия как для PyCall, так и для JavaCall (cc @aviks).

@JeffBezanson, если I have an absolutely awesome way to implement this. )

По моему опыту, нет более быстрого и надежного способа заставить Джеффа что-то реализовать, чем реализовать версию, которая ему не нравится ;-)

Основная идея заключается в том, что вы реализуете

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

так что вы можете перегрузить его для каждого поля. Это позволяет получить доступ к «реальным» полям, чтобы работать прозрачно. С подходящими запасными вариантами getfield(::MyType, ::Symbol) тоже работает.

Самая большая проблема заключается в том, что модули имеют особое поведение по отношению к . . Теоретически это был бы просто еще один метод getfield , но проблема в том, что нам нужно разрешать ссылки на модули _earlier_, поскольку они в основном ведут себя как глобальные переменные. Я думаю, нам нужно будет оставить это частным случаем в поведении . . Существует также небольшая проблема эффективности компилятора из-за анализа (# типов) * (# полей) определений дополнительных функций. Но для этого мы просто посмотрим, что произойдет.

@JeffBezanson Вы также имеете в виду поведение const в модулях? Было бы полезно иметь пользовательский тип, имитирующий модуль, и уметь сообщать компилятору, когда результат динамического поиска поля фактически постоянен. (другой подход - начать с реального модуля и иметь возможность «перехватить» отказавший jl_get_global и ввести новые привязки по запросу)

Я считаю, что это очень полезно в сочетании с # 5395. Затем можно будет перехватить вызов неопределенной функции или метода MyMod.newfunction(new signature) и по запросу сгенерировать привязки к (возможно, большому) API. Я думаю, это будет кешироваться как обычные привязки const.

Позвольте мне, простому новичку Джулии, высказать небольшое беспокойство: я думаю, что возможность перегрузки оператора точки может означать, что «чистота» доступа к полю каким-то образом потеряна.

Пользователь обычно теряет знания, если выполнение ab является просто доступом к ссылке / значению или если за ним может быть вызван огромный функциональный механизм. Я не знаю, как это могло быть плохо, это просто чувство ...

С другой стороны, я вижу, что это действительно большое желание синтаксического сахара для многих случаев (PyCall, Dataframes ...), что совершенно понятно.
Может пора .. # 2614?

Я поддерживаю это.

Но чистоте есть что сказать, даже если можно использовать names(Foo) чтобы выяснить, каковы настоящие компоненты Foo .

Аргумент чистоты тесно связан с основной практической проблемой, которая у меня есть, а именно с тем, как обрабатывать конфликты имен, когда поля типа мешают именам, которые вы, возможно, надеетесь использовать. В DataFrames, я думаю, мы могли бы решить эту проблему, запретив использование columns и colindex качестве имен столбцов, но хотели знать, каковы были планы людей для этого.

Я предполагаю, что getfield(x::MyType, ::Field{:foo}) = ... должно быть запрещено, когда MyType имеет поле foo , иначе доступ к реальному полю будет потерян (или способ принудительного доступа к полю должен быть доступен).
Но тогда getfield можно определить только для конкретных типов, поскольку абстрактные ничего не знают о полях.

(Между тем я наткнулся на это насчет C ++ .)

Это не большая проблема. Мы можем предоставить что-то вроде Core.getfield(x, :f) для принудительного доступа к реальным полям.

Хорошо, может я продался. Но тогда определение ярлыка для Core.getfield(x, :f) (например, x..f ) будет приятным, в противном случае внутренний код типов, перегружающий . для всех символов (фреймы данных, возможно, словари), должен быть переполненным Core.getfield s.

Меня не беспокоит аспект чистоты - пока у нас не будет этого, единственный код
который должен вообще использовать доступ к полю - это код, принадлежащий
реализация данного типа. Когда доступ к полю является частью API, вы
должны задокументировать это, как и любой api. Я согласен, что это может быть удобно с
некоторый сокращенный синтаксис для core.getfield, хотя при написании тех
реализации.

На это уже указывалось в # 4935, но давайте остановимся здесь: перегрузка точек может немного перекрываться с классической множественной отправкой по Джулиану, если не используется должным образом, поскольку мы можем начать делать

getfield (x :: MyType, :: Field {: size}) = .........
для i = 1: размер y .....

вместо

size (x :: MyType) = ..........
для i = 1: size (y) ....

Хотя точка была бы удобна для доступа к элементам в коллекциях (Dataframes, Dicts, PyObjects), она может каким-то образом изменить способ доступа к свойствам объекта (а не к полям).

Я думаю, что нужно учитывать одну вещь: если вы можете перегрузить поле доступа, вы также сможете перегрузить _setting_ поле. Иначе это будет непоследовательно и неприятно. Вы готовы зайти так далеко?

@nalimilan , в дополнение к getfield нужен setfield! getfield . (Аналогично setindex! против getindex для [] ). Я не думаю, что это спорно.

Согласитесь с @stevengj : DataFrames определенно будет реализовывать setfield! для столбцов.

Я поддерживаю это.

Опыт работы с другими языками (например, C # и Python) показывает, что синтаксис с точкой действительно имеет большое практическое значение. То, как это реализовано с помощью специализированных методов, в значительной степени решает проблему снижения производительности.

Тем не менее, важно убедиться, что это изменение не повлияет серьезно на _inlineability_ метода. Например, что-то вроде f(x) = g(x.a) + h(x.b) не станет внезапно отключенным после этого.

Если мы решим это сделать, полезно также предоставить макросы, чтобы упростить определение свойства, которые могут выглядеть так:

# 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

За кулисами все это можно перевести в определения методов.

Я не уверен, что <strong i="5">@property</strong> (a::A).foo = намного проще, чем getproperty(a::A, ::Field{foo}) = ...

В любом случае, лучший синтаксический сахар - это то, что может быть добавлено после того, как будут реализованы основные функции.

Что касается встраивания, если доступ к полю встроен до того, как будет принято решение, встраивать ли окружающую функцию, то я не понимаю, почему это может повлиять. Но, может быть, это не тот порядок, в котором сейчас выполняется встраивание?

getproperty(a::A, ::Field{:foo}) = поразил меня, так как здесь слишком много двоеточий :-) Я согласен, что это мелочь, и, вероятно, нам не нужно сейчас об этом беспокоиться.

Меня беспокоит, приведет ли это к снижению производительности. Я не очень понимаю механизм генерации внутреннего кода. @JeffBezanson может что-то сказать по этому поводу?

Доступ к полям очень низкоуровневый, поэтому я не буду этого делать, не убедившись, что производительность сохраняется.

В конце концов, я не уверен, что перегрузка полей - хорошая идея. В этом предложении всегда будет два способа установки свойства: x.property = value и property!(x, value) . Если реализована перегрузка полей, нам понадобится очень сильное руководство по стилю, чтобы избежать полного беспорядка, когда вы никогда заранее не знаете, какое решение автор выбрал для данного типа.

И тогда возникает вопрос, являются ли поля общедоступными или частными. Запрет на перегрузку полей сделает систему типов более понятной: поля всегда будут закрытыми. Методы будут общедоступными, а типы смогут объявлять, что они реализуют интерфейсы / протокол / признаки, т.е. что они предоставляют заданный набор методов. Это будет противоречить @stevengj https://github.com/JuliaLang/julia/issues/1974#issuecomment -12083268 о перегрузке полей методами, чтобы избежать нарушения API: предлагайте методы только как часть API, а не поля .

Единственное место, где я бы пожалел о перегрузке полей, - это DataFrames , поскольку df[:a] не так хорошо, как df.a . Но это не похоже на то, что для этого потребуются такие серьезные изменения. Другим вариантом использования, похоже, является PyCall, который может указывать на то, что перегрузка поля должна быть разрешена, но только для очень конкретных, не юлианских вариантов использования. Но как предотвратить злоупотребление функцией, когда она станет доступна? Спрятать в специальный модуль?

@nalimilan , я бы сказал, что предпочтительнее использовать синтаксис x.property как можно больше. Дело в том, что людям этот синтаксис очень нравится - он очень приятный. Такой красивый синтаксис и конкретное заявление о том, что его следует использовать только для внутреннего доступа к объектам, кажутся совершенно извращенными - «ха, этот красивый синтаксис существует; не используйте его!» Кажется гораздо более разумным сделать синтаксис для доступа к частным вещам менее удобным и красивым, вместо того, чтобы заставлять API использовать более уродливый синтаксис. Возможно, это хороший вариант использования оператора .. : оператора доступа к частному полю _real_.

Я действительно думаю, что это изменение может сделать вещи более ясными и последовательными, а не менее четкими. Рассмотрим диапазоны - в настоящее время существует какая-то отвратительная смесь стилей step(r) и r.step . Особенно с тех пор, как я представил FloatRange это опасно, потому что правильный только код, использующий step(r) . Причина такого сочетания в том, что некоторые свойства диапазонов сохраняются, а некоторые вычисляются, но со временем они изменились и фактически различаются для разных типов диапазонов. Было бы лучше, если бы каждый доступ был в стиле step(r) за исключением определения самого step(r) . Но этому препятствуют серьезные психологические барьеры. Если мы сделаем r.step вызовом метода, который по умолчанию равен r..step , тогда люди смогут просто делать то, что они от природы склонны делать.

Чтобы сыграть в адвоката дьявола (с собой), следует ли писать r.length или length(r) ? Несоответствие между универсальными функциями и методами - это проблема, с которой сталкивался Python, в то время как Ruby полностью r.length стиля

+1 за .. as Core.getfield !

@StefanKarpinski Имеет смысл, но тогда вам нужно будет добавить синтаксис для частных полей, а интерфейсы должны будут указывать как методы, так и общедоступные поля. И действительно, вам нужно руководство по стилю, чтобы обеспечить некоторую последовательность; случай length сложен, но есть еще, например, size , который очень похож, но требует индекса измерения. Это решение открывает банку червей ...

В этом случае я также поддерживаю .. для доступа к фактическим полям и . для доступа к полям, будь то методы или реальные значения.

Чтобы сыграть в адвоката дьявола (с собой), следует ли писать r.length или length(r) ? Несоответствие между универсальными функциями и методами - это проблема, с которой сталкивался Python, в то время как Ruby полностью r.length стиля

Ключевым фактором, который может устранить неоднозначность этой проблемы, является то, хотите ли вы использовать что-то в качестве функции более высокого порядка или нет. Т.е. f в f(x) - это то, что вы можете map над коллекцией, тогда как f в x.f - нет (если не писать x -> x.f ) - такая же ситуация для всех методов в языках единой диспетчеризации.

Зачем останавливаться на полях? Как насчет того, чтобы x.foo(args...) эквивалентно getfield(x::MyType, ::Field{:foo}, args...) = ... ? Тогда у нас может быть x.size(1) для размера по первому измерению. (не уверен, нравится ли мне мое предложение, но, возможно, стоит подумать. Или, вероятно, нет, поскольку люди будут просто писать OO-подобный код?)

Это было бы возможно с этой функцией. Это одна из вещей, которая заставляет меня задуматься. У меня нет проблем с таким кодом в стиле oo - как я уже сказал, он довольно приятный и людям он действительно нравится - но он дает достаточно возможностей для выбора способов написания вещей, которые нам действительно_ нужна сильная политика в отношении того, что вы должны делать. так как вы будете очень свободны в том, что можете делать.

Когда я начал изучать Джулию, синтаксис без точек очень помог мне мысленно отказаться от стиля объектно-ориентированного программирования. Только по этой причине я считаю, что мое предложение плохое.

Кроме того, для простой перегрузки (т.е. просто a.b sans (args...) ) я согласен с комментарием @nalimilan выше. В выпуске № 4935, похоже, все согласны с тем, что поля не должны быть частью API, а должны быть только методами; следовательно, похоже, что этот вопрос будет закрыт. Наличие синтаксиса . -overloading сделает гораздо менее ясным, что обычные поля не должны быть частью API, и, вероятно, побудит сделать поля частью API.

Но да, синтаксис . удобен ...

Как насчет: одиночный . должен _только_ быть синтаксическим сахаром для getfield(x::MyType, ::Field{:name}) = ... а доступ к полю осуществляется _только_ через .. (то есть то, что сейчас . ).

Это позволило бы провести четкое различие:

  • . предназначен для публичного API для доступа к ценностным вещам типов-экземпляров
  • .. предназначен для доступа к полю и обычно не должен использоваться в общедоступном API

Конечно, это было бы переломным моментом.

В основном это то, что я предлагал, за исключением того, что . умолчанию равен .. так что это не ломается.

Извини, надо было перечитать!

Но я думаю, что . не по умолчанию равное .. самом деле может быть приятным (кроме того, что он ломается), поскольку это заставит разработчика принять решение о том, что такое публичный API, а что нет. Кроме того, если пользователь использует .. он может ожидать, что его код может сломаться, тогда как . нет.

Неплохо подмечено. Мы можем пойти по этому пути, установив для a.b default значение a..b с предупреждением об устаревании.

С точки зрения стиля, я бы предпочел увидеть

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

чем

a.length
a.size

Я думаю, что это помогает сохранить идею о том, что функция вызывается, а не просто извлекается свойство, которое каким-то образом хранится в типе (возвращаясь к проблеме «чистоты» выше). Интересно, есть ли способ помочь обеспечить такой стиль, чтобы все не стало таким беспорядочным, как в некоторых других языках.

Мне не очень нравится

a.length()

с тех пор я не могу сказать, было ли поле функции в исходном типе. Если . никогда не обращается к полям, это, очевидно, не проблема. В остальном это меня сбивает с толку.

Я априори считаю, что мы не должны делать ни a.length() и a.length . Но вот почему? Чем r.step отличается от r.length ? Это другое? Если они не отличаются, следует ли использовать step(r) и length(r) или r.step и r.length ?

С семантикой, предложенной Стефаном, и добавлением мной было бы ясно, что . всегда является вызовом функции (как и + ), тогда как .. всегда является полем доступ.

По вопросу о том, является ли a.length и т. Д. Хорошей идеей: как насчет . доступ должен использоваться только для доступа к фактическим данным в типе, более или менее, как если бы можно было использовать записи dict . Принимая во внимание, что мы придерживаемся функций для свойств без данных, таких как, size , length , step и т. Д. Поскольку для некоторых из них потребуются дополнительные параметры и, я думаю, a.size(1) тип синтаксиса плохой.

Вот мой взгляд на эту тему:

  • Точечный синтаксис следует использовать только для атрибутов типа / класса. Имейте в виду, что речь идет не только о геттерах, но и о сеттерах, и что-то вроде a.property() = ... кажется совершенно неправильным.
  • Хотя мне отчасти нравится текущая ситуация, когда функции определяют общедоступный API, а поля являются частными, я разделяю мнение Стефанса о том, что синтаксис с точкой слишком хорош, чтобы его нельзя было запретить для общедоступных API. Но давайте ограничимся простыми атрибутами. a.length - хороший пример, a.size(1) не потому, что требует дополнительного аргумента.
  • Пожалуйста, позвольте . умолчанию .. . Джулия, как известно, не является стандартным языком. Давай так и оставим

Пожалуйста, позвольте . умолчанию .. . Джулия, как известно, не является стандартным языком. Давай так и оставим

Я склонен с этим согласиться. Синтаксис для установки даже синтетического свойства будет просто a.property = b , а не a.property() = b .

Конечно, я просто хотел прояснить, почему a.property() в качестве синтаксиса ИМХО нехорошо

Или более ясно: в синтаксисе точек важно не то, что можно связывать функции с типами / классами, а его способность писать геттеры / сеттеры в приятной форме. И геттеры / сеттеры важны для инкапсуляции данных (сохраняйте стабильность интерфейса, но меняйте реализацию)

Это изменение было бы замечательно с точки зрения разработчиков API, но я согласен, что оно должно сопровождаться каким-то руководством по стилю, чтобы ограничить любую будущую несогласованность.

Это позволит Ruby как dsl ...

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

Но будьте готовы к безумию java:

object.propert1.property2.property3 ....

Несколько мыслей:

  • Мне больше всего нужен синтаксис . для Dicts с символами в качестве ключей. Просто лучше использовать d.key чем d[:key] . Но в конце концов это не критично.
  • Я думаю, что a->property читается лучше, чем a..property . Но опять же, это не так уж важно, и я не знаю, будет ли это работать с синтаксисом julia.

@BobPortmann Я не согласен. Словарь - это объект-контейнер, API для объектов-контейнеров - это obj [index] или obj [key]. Прямо сейчас, поскольку у нас нет свойств в Julia, API контейнера перегружен, чтобы обеспечить эту функциональность в таких библиотеках, как PyCall и OpenCL. Это изменение помогает усилить отличия API контейнера, поскольку он не будет перегружен для обеспечения дополнительных функций.

Использование a->property для закрытых полей было бы хорошим способом уберечь C-хакеров от Джулии ;-)

Мне нравится синтаксис .. .

Синтаксис a->property уже используется - это анонимная функция. Однако оператор a..b уже давно доступен. В некоторых случаях вам нужно что-то похожее на диктовку, но с множеством необязательных полей. Использование синтаксиса getter / setter для этого было бы лучше, чем синтаксис индексации dict.

«О синтаксисе свойства a-> уже говорилось - это анонимная функция».

Да, конечно. Без пробелов вокруг -> это не выглядело.

В качестве руководства по стилю, как насчет рекомендации, чтобы свойство (x) использовалось для свойств только для чтения, а x.property - для свойств чтения / записи?

Для записываемых свойств x.foo = bar действительно намного лучше, чем set_foo! (X, bar).

Наличие foo(x) для чтения и x.foo для записи довольно сбивает с толку. Собственно, это то, что делает его привлекательным. Имея одинаковый синтаксис для доступа для чтения и записи, то есть самый простой синтаксис, который можно получить (для геттеров и сеттеров)

Что касается стиля, возникает большой вопрос, хотим ли мы иметь одновременно x.length и length(x) если эта функция будет реализована, или более поздняя форма должна быть устаревшей и удаленной.

Я считаю, что у нас должен быть только один способ сделать это и в будущем использовать только x.length . Что касается стиля, я считаю, что это довольно просто. Все, что является простым свойством типа, должно быть реализовано с использованием синтаксиса поля. Все остальное с функциями. Я много использовал свойства в C # и редко встречал случаи, когда я не был уверен, должно ли что-то быть свойством или нет.

Я против изменения случайно выбранного набора функций с одним аргументом на синтаксис x.f . Я думаю, что @ mauro3 хорошо

a.b , по крайней мере, визуально, является своего рода конструкцией для определения области видимости. b не обязательно должен быть глобально видимым идентификатором. Это принципиальная разница. Например, матричные факторизации с верхней частью имеют свойство .U , но на самом деле это не универсальная вещь - нам не нужна глобальная функция U . Конечно, это немного субъективно, тем более что вы можете легко определить U(x) = x.U . Но length - это совсем другое дело. Более полезно, чтобы он был первоклассным (например, map(length, lst) ).

Вот рекомендации, которые я бы предложил. Обозначение foo.bar подходит, когда:

  1. foo самом деле имеет поле с именем bar . Пример: (1:10).start .
  2. foo - это экземпляр группы связанных типов, некоторые из которых фактически имеют поле с именем .bar ; даже если foo самом деле не имеет поля bar , значение этого поля подразумевается его типом. Примеры: (1:10).step , (0.1:0.1:0.3).step .
  3. Хотя foo явно не хранит bar , он хранит эквивалентную информацию в более компактной или эффективной форме, которая менее удобна в использовании. Пример: lufact(rand(5,5)).U .
  4. Вы эмулируете API другого типа, например Python или Java.

Может иметь смысл присваивать свойство bar в случаях 1 и 3, но не 2. В случае 2, поскольку вы не можете изменить тип значения, вы не можете изменить свойство bar что подразумевается этим типом. В таких случаях вы, вероятно, захотите запретить изменение свойства bar других связанных типов, либо сделав их неизменяемыми, либо явно сделав foo.bar = baz ошибкой.

@tknopp , я не предлагал использовать x.foo для записи и foo(x) для чтения. Мое предложение заключалось в том, что _ если_ свойство доступно и для чтения, и для записи, тогда, вероятно, вы захотите как читать, так и записать его с помощью x.foo .

@StefanKarpinski : Но разве length является случаем 3. где размеры обычно хранятся, а length - произведение размеров?

Я вижу, что Джефф указывает на то, что это изменение сделает эти функции больше не первоклассными.

@stevengj :

@tknopp - длина выводится из размеров, но не эквивалентна им. Если вы знаете размеры, вы можете вычислить длину, но не наоборот. Конечно, это немного размытая линия. Основная причина, по которой это приемлемо для lufact заключается в том, что мы не придумали лучшего API, чем этот. Другой подход заключался бы в определении общих функций upper и lower которые дают верхнетреугольные и нижнетреугольные части общих матриц. Однако этот подход не распространяется, например, на QR-факторизации.

Это говорит о том, что есть только несколько случаев, в которых действительно_ требуется этот синтаксис: pycall, факторизации и, возможно, фреймы данных.
Меня очень беспокоит, что в итоге получится случайная путаница из f(x) против x.f ; это сделало бы систему намного более сложной для изучения.

Разве пункт 1 списка @StefanKarpinski не означает, что любое поле типа автоматически принадлежит общедоступному API?

На данный момент я могу сказать, что такое публичный API модуля: все экспортируемые функции и типы (но не их поля). После этого изменения будет невозможно определить, какие поля должны принадлежать общедоступному API, а какие нет. Мы могли бы начать называть частные поля a._foo или около того, как в python, но это кажется не очень приятным.

Лично я считаю, что случай DataFrames немного лишний. Если мы это сделаем, я добавлю функциональность к DataFrames, но я считаю, что потеря согласованности намного более проблематична, чем сохранение нескольких символов.

Я бы также не делал решение зависимым от DataFrames, PyCall (и Gtk тоже этого хочет). Либо мы хотим этого, потому что считаем, что поля должны быть частью общедоступного интерфейса (потому что он «красиво выглядит»), либо мы этого не хотим.

... пикал ...

и JavaCall

Поскольку основным вариантом использования для этого, по-видимому, является взаимодействие с системами, отличными от Julia, как насчет использования предложенного оператора .. вместо перегрузки . ?

Интересно, является ли более простое решение более общей подсказкой для 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)

Похоже, что это позволит JavaCall / PyCall выполнять определения методов «внутри» классов, а также позволяет использовать общий стиль, если люди хотят иметь некоторый код типа OO, хотя он очень прозрачен. A.b() - это просто переписывание. Я думаю, это было бы очень естественно для людей, пришедших из ОО.
Также наличие нового getfield с A..b чтобы разрешить перегрузку там, хотя перегрузка здесь настоятельно не рекомендуется и должна использоваться только для полевых / свойств (я подозреваю, что это не будет использоваться очень широко из-за небольшой пугливости перегрузки getfield(A, ::Field{:field}) .

@ mauro3 :

Разве пункт 1 списка @StefanKarpinski не означает, что любое поле типа автоматически принадлежит общедоступному API?

Это был список тех случаев, когда можно использовать нотацию foo.bar , а не тогда, когда это необходимо. Вы можете отключить нотацию foo.bar для «частных» полей, которые тогда будут доступны только через foo..bar .

@karbarcca : Я не совсем понимаю, что вы здесь предлагаете.

fwiw, я поклонник подхода, основанного на согласии взрослых по соглашению и сделавшего . полностью перегружаемым. Я думаю, что предложение с двумя точками приведет к большей путанице, чем к уменьшению.

@ihnorton - например, вы против использования a..b в качестве (не перегружаемого) основного синтаксиса для доступа к полю или против использования a..b для перегружаемого синтаксиса?

Одна из лучших особенностей julia - его простота. Перегрузка x.y кажется первым шагом на пути к C ++.

@StefanKarpinski, но тогда это будет означать существенный сдвиг парадигмы с частных полей по умолчанию на публичные поля по умолчанию.

Осознание, которое у меня только что появилось, вероятно, было ясно другим с самого начала. Полное программирование в стиле объектно-ориентированного программирования может быть выполнено с помощью базовой . -перегрузки (хотя это и уродливо). Определение

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

затем x.foo(a,b) и x.bar работают. Поэтому обсуждение того, следует ли реализовать x.size(1) или только x.size является спорным.

@StefanKarpinski против обычно перегружаемых a..b и равнодушен к a..b -> Core.getfield(a,b) .

Я действительно начинаю видеть необходимость в другом операторе здесь, но a..b не совсем убедительно. Необходимость в двух персонажах кажется очень ... второсортной. Может быть, a@b , a$b или a|b (просто побитовые операторы используются не так часто). Внешняя возможность - также a b`, которую синтаксический анализатор, вероятно, мог бы отличить от команд.

Я бы согласился использовать "уродливый" оператор для доступа к примитивным полям. Я думаю, что опыт показал, что, поскольку это конкретная операция, она используется редко и действительно несколько опасна в использовании.

Я предлагаю разрешить моделирование одиночной отправки OO путем соглашения / перезаписи:

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

Оправданием здесь является то, что мы уже выполняем аналогичные переписывания синтаксиса для getindex / setindex !, поэтому давайте разрешим полный OO-синтаксис с этим. Таким образом, PyCall и JavaCall не должны делать

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

Мне это нравится, потому что это довольно четкое преобразование, как и getindex / setindex, но позволяет при желании моделировать единую диспетчерскую OO-систему, особенно для языковых пакетов OO.

Затем я предлагал использовать оператор .. для доступа к полю с возможностью перегрузки. Использование здесь могло бы позволить PyCall / JavaCall имитировать доступ к полям путем перегрузки вызовов .. , позволяя DataFrames перегрузить .. для доступа к столбцам и т. Д. Это также будет новым доступом к полю по умолчанию в вообще для любого типа.

У меня есть слабость к переписыванию чистого синтаксиса. Возможно, это плохо, что вы можете прямо сейчас написать a.f(x) и заставить его работать, но означать что-то сбивающее с толку, отличное от большинства объектно-ориентированных языков.

Конечно, обратная сторона медали - ужасная фрагментация стилей и тот факт, что a.f имеет ничего общего с a.f() , что приводит к быстрому разрушению иллюзии.

Одна из лучших особенностей julia - его простота. Перегрузка x.y кажется первым шагом на пути к C ++.

То же чувство здесь. Я думал, если реальная потребность в этом действительно для ограниченного числа типов взаимодействия, как насчет того, чтобы сделать его действительным только в том случае, если он явно задан в объявлении типа? Например, дополнительное ключевое слово помимо type и immutable может быть ootype или что-то в этом роде.

и тот факт, что af не имеет ничего общего с af (), что приводит к быстрому разрушению иллюзии.

Не могли бы вы пояснить, что это значит @JeffBezanson?

Я ожидал, что a.f - это своего рода объект метода, если a.f() работает.

Ах, понял. Да, вы точно не сможете сделать что-то вроде map(t.method,collection) .

Я собираюсь согласиться с @ mauro3, что, разрешая obj.method(...) , есть риск того, что новые пользователи могут просто увидеть julia как еще один объектно-ориентированный язык, пытающийся конкурировать с python, ruby ​​и т. Д., И не полностью оценить удивительность, которая заключается в многократной отправке. Другой риск состоит в том, что стандартный стиль oo затем станет преобладающим, поскольку это то, с чем пользователи более знакомы, в отличие от более юлианского стиля, разработанного до сих пор.

Поскольку вариант использования, отличный от DataFrames, ограничен взаимодействием с oo-языками, можно ли все это обрабатывать с помощью макросов? т.е. <strong i="8">@oo</strong> obj.method(a) становится method(obj,a) ?

@karbarcca это будет означать, что автоматически все можно записать двумя способами:

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

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

t.method (аргументы ...)
# просто переписать
метод (t, аргументы ...)

Для PyCall в этом не было бы необходимости, поскольку перегружаемую точку можно было бы просто использовать для вызова pyobj[:func] помощью pyobj.func . Тогда pyobj.func() будет фактически (pyobj.func)() .

Переписывание a.foo(x) как foo(a, x) не решит проблему для PyCall, потому что foo не является и не может быть методом Джулии, это то, что мне нужно динамически искать во время выполнения . Мне нужно переписать a.foo(x) как getfield(a, Field{:foo})(x) или аналогичный [или, возможно, как getfield(a, Field{:foo}, x) ], чтобы мой getfield{S}(::PyObject, ::Type{Field{S}}) мог поступать правильно.

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

Я действительно начинаю видеть необходимость в другом операторе здесь, но a..b не совсем убедительно. Необходимость в двух персонажах кажется очень ... второсортной.

Я бы сказал, что, с другой стороны, .. набирается намного быстрее, чем $ , @ или | поскольку нажимать клавишу Shift не нужно. , и, будучи двумя символами, палец остается на одной и той же клавише: smile:

@stevengj А, понятно. Но моя точка зрения по-прежнему остается в силе, что переписывание может быть выполнено с помощью макроса.

Для JavaCall мне действительно нужен только обработчик unknownProperty. На самом деле мне не нужно переписывать или перехватывать чтение или запись существующего свойства. Так может ли правило, что «ax перезаписывается в getfield (a,: x) только тогда, когда x не является существующим свойством», поможет сохранить разум?

@simonbyrne , требующий макроса, победит стремление к чистому и прозрачному межъязыковому вызову. Кроме того, было бы сложно заставить его работать надежно. Например, предположим, что у вас есть type Foo; p::PyObject; end , а для объекта f::Foo вы хотите выполнить foo.p.bar где bar - это поиск свойств Python. Трудно представить макрос, который мог бы надежно различать значения двух точек в foo.p.bar .

Честно говоря, я не вижу большого дела в стиле. Высококачественные пакеты будут имитировать стиль Base и других пакетов, где это возможно, и некоторые люди будут писать странный код, что бы мы ни делали. Если мы добавим перегрузку через точку в следующем разделе руководства и рекомендуем ее использовать только в нескольких тщательно отобранных случаях (например, межъязыковая совместимость, свойства чтения / записи, возможно, для предотвращения загрязнения пространства имен такими вещами, как factor.U , и в целом в качестве более чистой альтернативы foo[:bar] ), я не думаю, что мы будем переполнены пакетами, использующими точку для всего. Главное - решить, для чего _we_ будем использовать и порекомендовать это, и, вероятно, нам следует сделать список рекомендуемых применений очень коротким и расширять его только по мере возникновения реальных потребностей.

Мы не добавляем сверхлегкий объектно-ориентированный синтаксис вроде type Foo; bar(...) = ....; end для foo.bar(...) , так что это также ограничит соблазн новичков.

Я в основном полностью согласен с @stevengj здесь. Мне нравится a..b для реального доступа к полю, потому что он

  1. похож на a.b
  2. менее удобно, как и должно быть
  3. только _ немного_ менее удобно
  4. не имеет существующего значения, и мы не нашли ему убедительного применения более года
  5. не ужасно странно, как a b`

С этим изменением и, возможно, (https://github.com/JuliaLang/julia/issues/2403) почти весь синтаксис Джулии будет перегружен? (Тернарный оператор - единственное исключение, о котором я могу думать.) То, что почти весь синтаксис понижен до перегрузки метода отправки, кажется мне сильно объединяющей функцией.

Я согласен, что на самом деле это своего рода упрощение. Тернарный оператор и && и || на самом деле являются потоком управления, так что это немного другое. Конечно, такого рода аргументы против превращения a..b в реальный доступ к полю, поскольку тогда _that_ будет единственным неперегрузочным синтаксисом. Но я все равно считаю это хорошей идеей. Последовательность - это хорошо, но не само по себе.

О, есть еще вызов функции, который нельзя перегрузить. Так принципиально, что я забыл об этом.

На это и обращается номер 2403.

Ага. Но это гораздо ближе к тому, чем это происходит.

Единственная ложка дегтя для меня здесь заключается в том, что было бы действительно неплохо использовать настоящий оператор доступа к полям для модулей, но этого, вероятно, не произойдет, поскольку никто не хочет писать Package..foo .

Завершение табуляции после точек становится немного некрасивым; технически вы должны проверить, какой метод x. может вызвать, чтобы увидеть, уместно ли перечислять имена полей объектов или имена модулей. И я надеюсь, что никто не пытается определить getfield(::Module, ...) .

Я думаю, что заполнение вкладки может быть выполнено следующим образом: foo.<tab> перечисляет «общедоступные поля», а foo..<tab> перечисляет «частные поля». Для модулей, можно ли просто разрешить реализацию по умолчанию Mod.foo быть Mod..foo и просто сказать людям, чтобы они не добавляли методы getfield в Module ? Я имею в виду, что вы уже можете переопределить сложение целых чисел в языке - все вырвется наружу, и вы получите segfault, но мы не пытаемся предотвратить это. Это не может быть хуже, не так ли?

На самом деле это немного хуже, потому что язык программирования действительно заботится только об именах. Разрешение имен намного важнее добавления целых чисел.

У нас нет особого выбора, кроме как установить Mod.foo умолчанию на Mod..foo , но нам, вероятно, придется использовать Mod..foo для начальной загрузки в некоторых местах. Оператор .. здесь чрезвычайно полезен, поскольку без него вы даже не сможете вызвать Core.getfield , чтобы определить откат. С его помощью мы, вероятно, просто удалим Core.getfield и оставим только .. .

Это справедливо - именование - это большая проблема в программировании :-). Кажется, неплохой вариант - только .. и никаких Core.getfield .

Это две идеи,

[...] поместите перегрузку точек в более поздний раздел руководства и рекомендуйте ее использование только в нескольких тщательно отобранных случаях @stevengj https://github.com/JuliaLang/julia/issues/1974#issuecomment -38847340

и

[...] предпочтительно использовать синтаксис x.property в максимально возможной степени @StefanKarpinski https://github.com/JuliaLang/julia/issues/1974#issuecomment -38694885

явно против.

Я думаю, что если нужно выбрать первую идею, то простое создание нового оператора .. для этих «тщательно отобранных случаев» имеет больше смысла.
В качестве преимущества использование ..name для случаев, когда в настоящее время используется [:name] (DataFrames, Dict {Symbol, ...}), будет более удобным для набора текста / синтаксиса, при этом четко указано, что что-то отличается от доступа к полю происходило. Более того, двойная точка в ..name может рассматриваться как повернутое двоеточие, подсказка к синтаксису символа :name , а также не будет проблем с завершением табуляции.
В качестве недостатка использование PyCall et al. будет не так близок к исходному синтаксису (и может даже сбивать с толку в тех случаях, когда действительно необходимо использовать . ). Но давайте будем честными, Julia никогда не будет полностью совместима с синтаксисом Python, и всегда будут случаи, когда нужно много печатать в Julia с помощью PyCall, чтобы выполнить простые инструкции на Python. .. для подражания . может дать здесь хороший баланс. (Не поймите меня неправильно, мне очень нравится PyCall, и я считаю, что это важная функция, заслуживающая особого внимания)

Вторая идея, которую я сейчас предпочитаю, имеет важное решение о том, когда следует использовать property(x) или x.property , что требует элегантного, хорошо продуманного и четкого определения, если такое существует ... .
Кажется, что если людям нужен перегружаемый . , это потому, что они предпочитают стиль x.property API в первую очередь.
В любом случае, я бы предпочел видеть . не как перегружаемый оператор доступа к полю, а как оператор доступа к перегружаемому "свойству" (возможно, getprop(a, Field{:foo}) ?), Который по умолчанию является оператором неперегружаемого поля .. .
Также должны быть приняты другие решения, например, что будет использоваться в конкретном коде реализации для доступа к полю, .. или . ? Например, что будет идиоматическим для примера шага «Диапазоны»? step(r::Range1) = one(r..start) или step(r::Range1) = one(r.start) ? (не говоря уже о том, должно ли step быть методом или свойством).

Вот почему я отказался от этого угла и предложил следующие критерии: https://github.com/JuliaLang/julia/issues/1974#issuecomment -38812139.

Только одна мысль пришла мне в голову при чтении этой интересной ветки. Экспорт можно использовать для объявления общедоступных полей, в то время как все поля видны внутри определяющего модуля, например:

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

В этой ситуации экспортированное лицо по-прежнему выглядит как «имя» и «возраст», за исключением того, что в этом случае возраст читается только с помощью функции, которая его добавляет. Экспорт всего Person может быть выполнен как экспорт Person. * Или аналогичный.

[пао: цитаты]

@emeseles Пожалуйста, будьте осторожны при использовании обратных кавычек для цитирования вещей, похожих на код Julia - это обеспечивает сохранение форматирования и предотвращает создание макросами Julia уведомлений GitHub для пользователей с похожими именами.

. и .. сбивают с толку: ясный и легко запоминающийся синтаксис - это что-то хорошее

Я действительно с нетерпением жду возможности сделать это. Это достаточно большое изменение, чтобы его (или WIP в # 5848) пометили как проект 0.4?

Да, это определенно проект.

Я думаю, что большинство из нас согласны с тем, что рекомендуемые способы использования должны быть ограничены, по крайней мере, для начала. Я считаю, что изначально его следует рекомендовать только для двух применений: взаимодействия (с другими языками, как в PyCall, и в более общем плане для внешних библиотек, где точечная нотация естественна) и, возможно, для объектов с изменяемым состоянием (поскольку get_foo(x) и set_foo!(x, val) уродливы).

Даже если мы рекомендуем его только для взаимодействия с иностранными телефонами, одной этой цели, на мой взгляд, достаточно, чтобы оправдать эту функцию. Для нового языка, такого как Джулия, очень важно гладкое общение с остальной частью вселенной программного обеспечения.

Стивен, я не уверен на 100% по поводу геттера / сеттера, потому что боюсь, что это скоро приведет к несоответствиям, но я согласен с другим вариантом использования. Кроме того, у нас есть динамические свойства Gtk.jl, которые также выиграют от синтаксиса. Лично мне больше всего нравится реализация enum, которую Стефан описал в # 5842.

Удар. Что мешает продвижению по этому вопросу? Требуется ли решение, или эта проблема зависит от других внутренних изменений, которые еще не сделаны, или это просто код?

Что мешает продвижению по этому вопросу?

Кто-то делает работу, и некоторые сомневаются, правильно ли это делать.

Обратите внимание, что @ihnorton уже сделал ранний черновой вариант реализации на # 5848. Я думаю, что работа застопорилась в первую очередь из-за того, что от основной команды Julia требуется четкое заявление о том, является ли это желаемой функцией.

Я согласен с этим. @JeffBezanson, кажется, стоит на заборе.

Для меня наличие этой функции упростило бы переход от нашей большой базы кода Python к Julia. Чтобы объяснить студентам, что если они используют код Python, им нужен совершенно другой синтаксис, чем они привыкли, это может стать трудным.

У нас было это обсуждение выше в этой ветке, и я все еще не вижу полного согласия. В настоящее время некоторые люди думают, что публичный API состоит из функций / методов, в то время как частный API - это поля составного типа. Я вижу очень редкие исключения из этой схемы. ( .U в разложении LU?)

Это не значит, что я против этого, потому что доступ Python и перечисления - это случаи, когда это полезно. Тем не менее, можно задаться вопросом, насколько острая необходимость здесь, и было бы разумно продвигать ее в конце цикла разработки.

@ ufechner7 , я согласен, что основная мотивация - межъязыковое взаимодействие. @tknopp , мы никогда не поводу чего-то подобного. В конечном итоге все сводится к тому, что решат @JeffBezanson и @StefanKarpinski .

Я думаю, что многие колебания проистекают из того, что я считаю худшим кошмаром Джеффа:

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

Мне бы это тоже очень не понравилось - любой пакет, который решит использовать его не по назначению, навлечет их неправильное использование на _все_ типы в системе, включая мой собственный. Это очень мощная функция, которая изменит способ написания Джулии. К лучшему и (возможно, но, надеюсь, нет) к худшему.

Да, конечно, Стивен, возможно, я неправильно сформулировал это. Я хотел сказать, что это изменение может существенно повлиять на развитие языка. И идея «формального интерфейса», которая есть у нас в другом выпуске, также влияет, делая . перегружаемым. Так что да, пусть решают @StefanKarpinski . Тем не менее, вопрос в том, нужно ли приводить решение в исполнение сейчас ...

Что бы это ни стоило, я предпочел сделать почти весь синтаксис перегружаемым, а затем полагаться на культуру, чтобы сопротивляться чрезмерному увлечению ею.

+1 . Я думаю, что здесь есть сильный философский (и, возможно, практический ...) аналог call перегрузки. В руководстве нужен раздел под названием Don't do stupid stuff: we won't optimize that . (конечно, call перегрузка была частично _по_ соображениям производительности, но она изобилует потенциальными злоупотреблениями)

  • : 100: к тому. Я думаю, что культура должна быть достаточной мотивацией, чтобы не злоупотреблять этим

В целом я поддерживаю. Возможность жестокого обращения - не самое большое мое беспокойство. Для меня большие проблемы

  • Допустимый синтаксис для "реального" доступа к полю. Мне не очень нравятся a..b .
  • Модули. Полные имена чрезвычайно важны. Выражение их с помощью метода отправки возможно, но имеет практические трудности. А именно, вам нужно пройти много этапов компиляции (возможно, даже через встраивание), чтобы знать, что у вас есть квалифицированное имя. Это усложняет жизнь любому, кто пишет инструменты, использующие AST. Это также позволяет очень легко справиться с этим некорректно в таких инструментах.

Эти проблемы могут быть решены одним махом, используя один и тот же синтаксис для обоих, но на данном этапе почти невозможно представить себе использование чего-либо, кроме . для модулей. _ Внутри_ определенно будет абстрактный синтаксис для ссылок на модули; было бы неприятно, если бы не было хорошего способа разоблачить это.

Мои два цента за этот вопрос: почему бы не использовать : для полных имен? Он уже используется для чего-то подобного:

import Base: call, show, size

Это даст что-то вроде

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)

Или они уже слишком часто используют символ : ? Символ :: (стиль C ++) мне кажется слишком многословным.

: уже является самым перегруженным символом в Julia, так что, боюсь, это не поможет.

Можем ли мы упростить проблему квалифицированного именования, сделав module.name не перегружаемым? Поскольку привязки модулей являются постоянными, это позволило бы нам сохранить ту же семантику, но сократить всю обычную логику для поиска квалифицированных имен, как только станет известно, что LHS a.b является модулем. Я думаю, что вполне разумно не позволять людям переопределять, что означает искать имя в модуле.

Мне больше нравится синтаксис a..b для реального доступа к полю. Что вы против этого возражаете?

Кроме того: мне бы хотелось, чтобы мы не использовали ( ) для списков импорта, таких как некоторые из функциональных языков. Т.е.:

import Base (call, show, size)

Моя причина в том, что мы могли сделать запятые необязательными и разрешить запятые в конце. Меня действительно раздражает, что всем импортированным именам нужны конечные запятые, кроме последнего, у которого их не может быть.

Да, я как раз собирался упомянуть возможность использования a.b значении «если a - это модуль, то сначала выполните поиск модуля». Это может помочь, и мы, конечно же, не хотим переопределять значение поиска модуля. Однако это имеет некоторую сложность, поскольку тогда мы не можем представить a.b как вызов getfield(a,:b) . Это должен быть специальный узел AST с неявной ветвью. Конечно, мы могли бы использовать _explicit_ ветку, но я бы побеспокоился о раздувании AST из-за этого.

Кажется, нет простого выхода из такого огромного конфликта между потребностями передней и задней части.

Если всем нравится a..b думаю, я могу научиться жить с этим. Мне просто кажется, что это означает что-то совершенно другое, возможно, интервал.

Мне тоже не нравится a..b но я не понимаю, зачем он вообще нужен. При чтении этого потока создается впечатление, что перегрузка будет использоваться только в языковых оболочках и динамических вариантах использования, где реальный доступ к полю не требуется.

Потому что в какой-то момент вам нужно получить доступ к представлению объекта, чтобы что-то с ним сделать. Можно было бы возразить, что это будет относительно редко и поэтому может быть уродливым, как get_actual_field(a,:x) , но это кажется слишком важной операцией, чтобы не иметь синтаксиса.

Я понимаю, но это звучит так, будто мы ищем синтаксис, которым никто не хочет пользоваться, верно?

Отсутствие .. было бы способом сказать "да" для динамических вариантов использования, но "нет" для точечно-ориентированного программирования.

Я не понимаю, как это помешает точечно-ориентированному программированию; вы все равно можете выполнить приведенный выше пример

Хотя синтаксис a..b действительно выглядит как интервал (я использовал его как таковой), я просто не думаю, что для интервальной арифметики нужен собственный синтаксис ввода - написание Interval(a,b) - это просто хорошо, и больше никто не хочет использовать этот синтаксис, так как он уже много лет является оператором в Julia, и никто не использует его ни для чего. Это также похоже на доступ к полю.

Есть одна приятная новость: мы можем заменить отвратительный module_name на m..name . Отсутствие доступа к полям объектов модуля было бородавкой.

Да, я как раз собирался упомянуть возможность использования a.b значении «если a - это модуль, то сначала выполните поиск модуля». Это может помочь, и мы, конечно же, не хотим переопределять значение поиска модуля. Однако это имеет некоторую сложность, поскольку тогда мы не можем представить a.b как вызов getfield(a,:b) . Это должен быть специальный узел AST с неявной ветвью. Конечно, мы могли бы использовать _explicit_ ветку, но я бы побеспокоился о раздувании AST из-за этого.

Можем ли мы справиться с этим, сделав a.b безусловно означающим getfield(a,:b) а затем сделав ошибкой добавление методов к getfield которые пересекаются с методом getfield(::Module, ::Field) ? Это своего рода странный способ принудить к такому поведению, но в конечном итоге он даст тот же эффект. Тогда понижение могло бы просто использовать тот факт, что мы знаем, что вы не можете этого сделать, чтобы обмануть и понизить module.name для поиска квалифицированного имени.

Хорошо, я заявляю об обратном: будет ли кто-нибудь в этой ветке использовать .. и если да, то каков будет пример использования? (т.е. может быть полностью затенение доступа к внутреннему полю)

@StefanKarpinski Да, это может сработать. Может быть другой случай, когда нам нужны какие-то «запечатанные» методы.

@tknopp Доступ к module..name и module..parent :) Кроме того, просто для пояснения, защищаете ли вы синтаксис вызова функций, такой как get(obj,:field) для доступа к полям низкого уровня?

Нет, я не защищаю определенный синтаксис. Я просто думаю, что было бы хорошо узнать, зачем эта функция нужна и каковы варианты использования. Для динамических вариантов использования было бы нормально, если бы

  • a.b - доступ к полю, если Base.getfield(a, ::Field{:b}) не было определено
  • a.b - это динамическая версия, если определена Base.getfield(a, ::Field{:b}) . В этом случае реальный доступ к полю может быть затенен.

Мой вопрос был в том, есть ли варианты использования, когда затенение недопустимо.

Да; вы можете определить pyobject.x так, чтобы x всегда просматривался в словаре pyobject для всех x . Тогда нужен отдельный механизм для доступа к полям julia pyobject.

Ааа, так все или ничего? У меня почему-то создалось впечатление, что можно

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

Да, вы _ можете_ это сделать, но не все объекты. Некоторые захотят определить getfield(a::A, ::Field) для перехвата всех полей.

Хорошо, спасибо, теперь я понял. Все динамические варианты использования потребуют getfield(a::A, ::Field) и, следовательно, нуждаются в любом способе вызова внутренних полей.

Тогда я считаю, что Core.getfield достаточно, если кто-то не найдет практического варианта использования, в котором это раздражает.

Вероятно, это данность, но мы также позволим переопределить setfield! , верно? Мне бы очень хотелось, чтобы изменяемые представления были представлены в базе данных, в которой строки становятся типами.

Да, это было мое впечатление.

Хорошо, ИМХО, использовать ли .. для реального доступа к полю или Core.getfield - это не такая уж большая проблема. Можно было бы ввести общую функцию как экспериментальную и внести в нее изменения.

Вопрос в том, уложится ли это во временные рамки 0,4 или нет. Значит, # ​​5848 близок к окончательной реализации и что модуль решаем?

@johnmyleswhite : Я бы тоже проголосовал за то, чтобы сделать это симметричным, а также за разрешение setfield! . В Gtk.jl мы будем использовать оба.

Не совсем понятно, каков будет правило, когда использовать эту функцию, а когда нет. Я вижу точку для PyCall, где метод / поле необходимо искать динамически и, следовательно, не может быть методом / составным типом Julia (и результирующий синтаксис ближе к Python). Но тогда зачем использовать его для Gtk.jl? Если он начнет делать foo.bar = x вместо setbar!(foo, x) , тогда стандартный код Julia также непременно начнет использовать этот шаблон: это то, что мы хотим? Может быть, это так, но давайте проясним это.

Будет ли приемлемо / рекомендовано использовать эту функцию для реализации средств получения и установки свойств, определенных для абстрактных (и конкретных) типов?
Я предполагаю, что это позволит избежать конфликта имен методов, которые используются для получения свойств из разных типов разных модулей.

Ссылка: https://github.com/JuliaLang/julia/issues/4345 , https://groups.google.com/forum/#!msg/julia -users / p5-lVNlDC8U / 6PYcvvsg29UJ

@nalimilan : Gtk имеет систему динамических свойств, а не геттеры / сеттеры.

@tknopp О'кей , конечно. Но для большинства общих свойств у вас есть (быстрая) функция получения / установки плюс динамическое свойство. Итак, вы бы порекомендовали использовать функцию получения / установки, когда она доступна, и синтаксис перегрузки поля только для свойств, у которых ее нет? Мне кажется, это нормально, но ИМХО хорошо иметь четкую политику в отношении этого.

На мой взгляд, на данный момент (и я думаю, что нам нужно немного поэкспериментировать с этим, чтобы выяснить правильные правила), f(x) лучше, когда f имеет смысл в качестве общей автономной концепции, такой как " length ", а x.f следует использовать, когда f самом деле не является независимым от x . Чтобы попытаться приспособить мой предыдущий пример к этому, не очень полезно иметь общую функцию step поскольку большинство векторов и коллекций не имеют никакого понятия шага - это _ только_ имеет смысл, когда у вас есть диапазон какой-то. Таким образом, вполне нормально иметь x.step качестве способа получения шага диапазона x . Это что-то вроде суждения, но я думаю, что в жизни их полно.

Мне не нравится .. поскольку он не передает мне прямой доступ. Как насчет foo.bar. Дополнительная точка на конце указывает на прямой доступ.

Можно также выбрать символ юникода: у нас еще много осталось ...

@GlenHertz , это действительно не работает, если вам нужно

@simonbyrne , я вообще против того, чтобы в основном языке или стандартной библиотеке что-либо требовало использования Unicode. Одно дело - позволить людям использовать это - совсем другое.

На мой взгляд, на данный момент (и я думаю, что нам нужно немного поэкспериментировать с этим, чтобы выяснить правильные правила), f (x) лучше, когда f имеет смысл в качестве общей автономной концепции, такой как «длина», в то время как xf следует использовать когда f на самом деле не зависит от x.

Моим личным правилом использования этой функции будет: используйте ее только для языкового взаимодействия или для вещей, которые являются «почти полями» или «расширенными полями». Например, я могу использовать это для обновления кеша, который зависит от значения всех полей в типе.

У меня есть один большой вопрос об этой функции: как это взаимодействует с выводом типа? Похоже, вы собираетесь определить функцию getfield(x::T, s::Symbol) которая выдает разнотипный вывод для разных значений s . Это работает только потому, что getfield волшебно? Можете ли вы переопределить вывод getfield(x, s) для фиксированных x и s в любой момент программы? Если да, то как эта сетка связана с невозможностью переопределить тип?

Похоже, вы собираетесь определить функцию getfield(x::T, s::Symbol) которая выдает разнотипный вывод для разных значений s .

Вот почему план состоит в том, чтобы выразить это как getfield{s}(x::T, f::Field{s}) где s - это символ.

Я пропустил это. Спасибо, что поправили меня.

@nalimilan : Да, перегруженные поля будут использоваться только для динамических свойств. Вот как Джеймсон хочет решить эту проблему, и я думаю, что это хорошо. Все настоящие геттеры и сеттеры генерируются автоматически, но по-прежнему функционируют без всяких имен get / set. Концерт в модуле GAccessor (сокращенно G_ )

Что касается синтаксиса, почему бы не использовать <- для реального доступа к полю?
Он похож на -> в c ++, который используется для ламд, но <- в настоящее время не используется.
Его можно было прочитать как от типа, получить значение напрямую.

Он оставит ... неиспользованным для тех, кто хочет использовать его с интервалами, и будет использовать неиспользованную пару, у которой нет других применений, о которых я пока могу думать.

Давайте не будем использовать нотацию присваивания R для доступа к полю.

Мы могли бы использовать -> для прямого зеркалирования C / C ++ и получить новый синтаксис для
анонимные функции. Меня никогда особо не волновала анонимная функция
синтаксис, поскольку он немного кратковременный / нечитабельный. Может, вместо этого мы могли бы сделать
что-то вроде

func (x) x ^ 2 конец

или более длительный, более последовательный

функция (x) x ^ 2 конец

Возможно, есть способ придумать хороший синтаксис, который не требует
используя конец.

Не слишком сильно менять обсуждение, но это определенно имело бы смысл
использовать -> для реального доступа к полю.
Взаимодействие с другими людьми

В среду, 28 января 2015 г., в 8:49, Джон Майлс Уайт [email protected]
написал:

Давайте не будем использовать нотацию присваивания R для доступа к полю.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -71857083.

@quinnj : func (x) x^2 end уже работает. Но хорошо иметь очень лаконичный синтаксис для анонимных функций: map(x->x^2, 1:10)

Я не думаю, что для доступа к полю нужен особый синтаксис (символ Юникода, предложенный x -> x^2 .

Похоже, этот вопрос все еще открыт / обсуждается? Было интересно прочитать все различные комментарии здесь об операторе точки.

Были ли предложения добавить новые токены операторов? Использование чего-то вроде :> может быть хорошей альтернативой. Он имеет параллели с |> и может иметь более родную Julia _feel_:

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

Хотя писать гораздо проще, чем:

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

Или сравнивая:

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

Я новичок в Джулии, но быстро оценил подход с несколькими отправками. Объектно-ориентированное программирование - особенно для научного программирования - делает множество задач более громоздкими. Я был бы обеспокоен тем, что парадигма / синтаксис OO негативно повлияет на развитие стиля Джулии.

Или, в качестве альтернативы, поскольку я забыл об интернировании строк и / или цитировании:

someobj <: field_name |> length

@elcritch , <: в настоящее время используется как оператор Джулии "is subtype of", и если вводится :> вероятно, мы захотим использовать его для чего-то, связанного с типом из-за этого наследие.

Если использование instance..member является проблемой, вот несколько возможностей. Прикройте глаза! Вероятно, все из них хуже:

  • instance^.member (морковная точка)
  • instance~.member (тильда точка)
  • instance:.member (точка с двоеточием)
  • instance*.member (звездочка)
  • instance-.member (пунктирная точка)
  • [email protected] (точка в знаке)
  • instance&.member (амперная точка)
  • instance$.member (долларовая точка)
  • instance<.>member (точка космического корабля)

Я искренне думаю, что (а) .. кажется достаточно хорошим и (б) на самом деле не имеет значения, хорошо ли это выглядит, потому что это всегда будет неясным уголком языка. Большинство людей будут использовать instance.member потому что у них будет только поле или метод getfield , но не оба сразу.

(В этом отношении большинство людей, которые хотят использовать и член, и метод, вероятно, даже не потрудятся узнать о .. . Они просто определят метод для foo.member и назовут "настоящий "field foo._member . Возможно, это в любом случае лучший стиль - это означает, что когда вы читаете определение type , сразу становится очевидно, что _member не должно быть чем-то вы можете или должны получить доступ напрямую. Это является аргументом в пользу того, чтобы сделать .. чем-то уродливым и непонятным, например :. вместо того, чтобы занимать ценную пунктуацию недвижимости.)

Я бы упустил возможность использовать .. в качестве оператора инфиксного интервала, но доступ к перегружаемым полям - достойный компромисс. Хотя я не решаюсь слегка бояться добавления каких-либо значений к двоеточию, :. не кажется таким уж плохим.

Обратите внимание, что :. на самом деле является допустимым синтаксисом для symbol(".") прямо сейчас, поэтому использовать его может быть нецелесообразно. То, что .. потенциально полезно, хорошо принято; мы не должны тратить его на синтаксис, который вряд ли кто-то будет использовать. Я был бы совершенно счастлив использовать что-то еще более уродливое, например @. (поскольку . не является допустимым именем макроса и не может начинать идентификатор, это не похоже ни на что конфликтует ). Опять же, это будет настолько темный уголок Джулии, что не стоит пытаться сделать его красивым.

+1, чтобы просто сделать это с помощью .. и игнорирования любого потенциального уродства

Да, давайте все равно выберем .. если бы можно было использовать .. тогда я думаю, что это будет конструктор _range_, но, эй, он уже там с двоеточием, например. start:stop .

И последнее: что насчет .: ? Это слишком сложно, чтобы иметь ab, a. (B), a. (: B) _and_ a.:b?

@hayd , это кажется слишком тонким и простым в использовании случайно.

@ihnorton , есть ли шанс воскресить версию # 5848? Мы могли бы ответить на вопрос о синтаксисе и просто использовать Core.getfield(x, Field{y}) для доступа к «реальному» полю.

Bikeshed насчет синтаксиса Core.getfield , остались ли какие-то существенные вопросы?

В # 5848 @tknopp предлагал сделать только "настоящий" доступ к полям перегружаемым, в отличие от предложения x::Vector{Any} , тогда выполнение x[i].y может быть интерпретировано как getfield(x[i], Field{:y}) и система диспетчеризации будет делать правильные вещи независимо от того, y - это реальное поле, тогда как если вы хотите вызвать getfield для «виртуальных» полей, тогда разработчику кода придется реализовать миниатюрное подмножество диспетчерской системы для проверки во время выполнения x[i] тип.

Другой вопрос заключался в том, должна ли Module.foo быть перегружаемой. С одной стороны, существует определенная последовательность в использовании getfield для всего, и вышеупомянутый пример Vector{Any} может иметь элементы массива Module поэтому нам придется обрабатывать этот случай так или иначе. С другой стороны, @JeffBezanson указал, что это может усложнить компиляцию и усложнить поведение объявлений типа function Base.sum(...) . Я бы предпочел сделать Module.foo неперегружаемым, по крайней мере, на данный момент, в любом случае, когда компилятор знает, что он работает с Module (т.е. не Vector{Any} ) ; небольшая непоследовательность, кажется, стоит того, чтобы консервативно подходить к изменениям.

+1, чтобы не допустить перегрузки Module.foo .

Подчеркнем, что одна из областей научных вычислений, в которой объектно-ориентированное программирование и синтаксис на самом деле превосходят FP, - это агентное моделирование. Хотя мне не хватает конкретного и множественного наследования для настройки иерархии агентов, легкие и быстрые абстракции и быстрое прототипирование Джулии потрясают - уже появилось несколько сред ABM.

В ABM точечная нотация предпочтительнее для выражения взаимодействий агентов: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Это очевидный не самый большой вариант использования, но было бы неплохо сохранить этот синтаксический сахар для размышлений и кодирования ABM.

Я также очень хотел бы, чтобы этот синтаксис был доступен в Julia. В виде
насколько я ценю функционально-ориентированный подход в дизайне
В перспективе синтаксис вызова метода очень полезен и читается в нескольких
домены. Было бы здорово, если бы Ab (C) был эквивалентен b (A, C).
22 апреля 2015 г. в 8:50 "datnamer" [email protected] написал:

К слову, одна из областей научных вычислений, где объектно-ориентированное программирование
а синтаксис фактически превосходит FP - это агентное моделирование. хотя я
пропустить конкретное и множественное наследование для настройки иерархии агентов,
легкие и быстрые абстракции и быстрое прототипирование Юлии - это
удивительно - уже появилось несколько фреймворков ABM.

В ABM точечная запись предпочтительнее для выражения взаимодействия агентов:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Это очевидно не самый большой вариант использования, но было бы неплохо сохранить это
синтаксический сахар для размышлений и кодирования ABM.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95218555.

В ABM точечная нотация предпочтительнее для выражения взаимодействий агентов: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Почему так лучше? Изменить: я имею в виду конкретно в этом контексте ABM.

Пожалуйста, давайте не будем втягиваться в религиозные войны из-за правописания. @ dbeach24 , никто не предлагает, чтобы a.b(c) в Джулии было эквивалентно b(a,c) ; это не произойдет.

Перегружаемые точки имеют решающее значение для естественного взаимодействия с другими языками. Этого достаточно.

Subject.Verb (DirectObject)

Это довольно естественно в нескольких контекстах. Многие OO-программисты привыкли
это, и хотя это просто переупорядочение функции (A, B), это переупорядочение
много делает для удобочитаемости, ИМО.
22 апреля 2015 г., 10:32, «Энди Хайден» [email protected] написал:

В ABM точечная запись предпочтительнее для выражения взаимодействия агентов:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Почему так лучше?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95256390.

Я предлагал это. (Извините, я не хотел начинать войну, и я не знал
предложение было бы непопулярным.) Я думал, что видел это раньше
на форумах, но не понимал, что он уже был отклонен как
плохая идея. Могу я спросить, почему? (Вы можете указать мне на ветку?)

Благодарю.
22 апреля 2015 г., 11:09, "Стивен Дж. Джонсон" [email protected]
написал:

Пожалуйста, давайте не будем втягиваться в религиозные войны из-за правописания. @ dbeach24
https://github.com/dbeach24 , никто не предлагает использовать ab (c)
эквивалент в Юлии к b (a, c); это не произойдет.

Перегружаемые точки имеют решающее значение для бесперебойного взаимодействия с другими языками.
Этого достаточно.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95266671.

Одна из причин не делать этого заключается в том, что a.b ищет b в области a , в то время как b только ищет b в охватывающий объем. Было бы очень запутанно, если бы точечный доступ иногда не смотрел на левый объект.

Кстати, считается, что функции в Julia ищутся не внутри объектов, а в текущей области. Я считаю, что страх, что люди начнут использовать функции, просматриваемые внутри объектов, является одной из причин, сдерживающих перегрузку точек.

@toivoh , любая реализация точечной перегрузки будет использовать существующий метод dispatch, поэтому это не изменит поведение области видимости. @ dbeach24 , основная причина не поощрять неизбирательное использование a.b(c) заключается в том, что если у вас слишком много синтаксисов для выполнения одного и того же действия, язык и библиотеки превращаются в беспорядок. Лучше выбрать одно написание и придерживаться его, и множественная отправка Джулии отдает предпочтение b(a,c) потому что более ясно, что b не "принадлежит" a - b(a,c) Метод a и c .

Самым большим исключением, конечно же, является вызов внешних библиотек на таких языках, как Python или C ++, где хорошо иметь возможность отражать точечный синтаксис библиотеки, которую вы вызываете. (Чтобы, например, люди могли переводить документацию и примеры прямо на Джулию без особых изменений.)

Я весь мокрый, но разве ab (arg) не означает взять анонимную функцию, хранящуюся в поле b из a, а затем оценить ее с заданным аргументом?

отправлено из моего Айфона

22 апреля 2015 г. в 17:06 Стивен Джонсон [email protected] написал:

внешний

@ScottPJones. В настоящее время это прекрасно работает, но обычно не считается хорошим стилем.

Меня не волновал стиль или нет, я просто подумал, что, поскольку он уже имел значение, которое соответствовало способу работы Джулии (то есть возможность хранить анонимные функции в полях), это был хороший аргумент _не_ для попробуйте рассматривать ab (arg), как если бы это было b (a, arg).
Я _might_ могу использовать структуру (тип) с членами, хранящими анонимные функции, хотя я загружаю функции, написанные на Julia, из базы данных, а затем выполняю синтаксический анализ и сохраняю функции в объекте ...
В каком «юлианском» стиле было бы лучше сделать что-то подобное?

Благодаря!

@ScottPJones Я думаю, есть согласие, что они не должны быть эквивалентными *.

Могут быть исключения из правила «стиля», но должны быть убедительные аргументы в пользу использования функции вставки в поле, как и при перегрузке точек. Думаю, проблема в том, что люди не должны делать это волей-неволей / ради этого / потому что они могут.

Это может быть пример, но может быть и лучший способ (конечно, не единственный) ...

99% + случаев лучше отправлять на typeof (a); без функциональных полей, без точечной перегрузки.

* _Однако, я думаю, все знают, что как только это приземлится, появится пакет, который делает именно это ..._

В D у них даже есть название «унифицированный синтаксис вызова функций» для a.b(arg) и оно довольно популярно, но я думаю, что оно очень сильно расходится с универсальной функцией - способом множественной отправки, который работает Джулия. Если рассматриваемые функции анонимны или полностью типизированы, я полагаю, что все будет работать, но это довольно ограничительное ИМО. Я не думаю, что есть много причин для хранения функциональных полей внутри составного типа, кроме как по привычке в традиционных объектно-ориентированных языках на основе классов. Лучшее место для хранения общих функций, если вы загружаете их откуда-то, было бы в модулях.

Но сейчас мы довольно далеко ушли от «вопросов по существу». Я также сторонник консервативности в отношении того, как мы разрешаем перегрузку getfield, не разрешаем перегрузку getfield в модулях и не слишком беспокоимся о специальном синтаксисе для «истинного getfield».

@stevengj : Да, и, как я пытался сказать, это одна из фундаментальных причин, почему никогда не произойдет, что a.b(c) станет равным b(a, c) , независимо от того, что мы делаем с точечной перегрузкой в ​​противном случае .

В списке рассылки было некоторое обсуждение этого вопроса. С моей точки зрения, наиболее подходящее сообщение (от @nalimilan) в этой теме: https://groups.google.com/d/msg/julia-users/yC-sw9ykZwM/-607E_FPtl0J

Добавление к комментарию @johnmyleswhite относительно личной политики относительно того, когда использовать эту функцию - мне кажется, что здесь могут быть полезны некоторые идеи HTTP, и что getfield() не должно иметь побочных эффектов и setfield!() должен быть идемпотентным (то есть вызов его несколько раз с одним и тем же значением должен иметь тот же эффект, что и его однократный вызов). Не обязательно жесткие правила, которые соблюдаются компилятором, но рекомендации по использованию, чтобы все не стало слишком сумасшедшим.

Я опубликовал обходной путь с использованием параметрических типов с параметрами указателя и convert для вызова настраиваемого установщика при настройке поля:
сообщение: https://groups.google.com/forum/#!topic/julia -users / _I0VosEGa8o
код: https://github.com/barche/CppWrapper/blob/master/test/property.jl

Мне интересно, следует ли мне использовать этот подход в моем пакете в качестве обходного пути до setfield! возможна перегрузка, или это слишком большая нагрузка на систему параметрического типа?

Я хотел бы упомянуть одно дополнительное преимущество перегрузки getfield / setfield! , я надеюсь, что это подходящее место для этого, в противном случае извините. (Соответствующая тема появилась на https://groups.google.com/forum/#!topic/julia-users/ThQyCUgWb_Q)

Юля с гетфилдом / сетфилдом! перегрузка позволила бы удивительно элегантно реализовать функцию автозагрузки в _external package_. (См. Всю тяжелую работу, которую нужно было вложить в расширение автоматической перезагрузки IPython https://ipython.org/ipython-doc/3/config/extensions/autoreload.html, чтобы получить эту функциональность.) Идея автоматической перезагрузки заключается в том, что вы может изменять функции и типы во внешних модулях во время работы с REPL.

TL; DR: getfield / setfield! перегрузка, словари и пакет, аналогичный https://github.com/malmaud/Autoreload.jl, должны помочь.


Чтобы быть более конкретным, представьте себе пакет, похожий на Autoreload.jl, который выполняет следующие действия.

Сначала вы создаете модуль M.jl:

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

В REPL вы вводите

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

Затем вы меняете M.jl на

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

Это будет автоматически перезагружено и преобразовано в

# 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

а затем в REPL вы можете сделать

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

Производительность не будет хуже, чем с Python, поэтому люди, которые переносят свой рабочий процесс с автозагрузки IPython, ничего не потеряют с точки зрения производительности. И как только вы перезапустите REPL, вы вернетесь к полной производительности.

Я устал писать a[:field][:field2][:morestuff](b[:random_stuff]) потому что он не читается. Итак, я написал этот небольшой макрос, который работает для моих сценариев использования в версиях 0.4 и 0.5.
https://github.com/sneusse/DotOverload.jl

TL; DR
Макрос, преобразующий AST выражения a.b -> getMember(a, :b)

Удаление из версии 0.6, поскольку нет единого мнения о том, что это хорошая идея, и есть противоречивые предложения о том, что делать с точечным синтаксисом.

@Keno : У вас есть ссылка на противоречивое предложение?

Не думаю, что @StefanKarpinski еще написал об этом, но я ожидаю, что скоро об этом будет Джулеп.

Я нашел object.fieldname лучше, чем функции получения, такие как fieldname(object) или get_fieldname(object) . Может быть, object.fieldname (или object$fieldname ) - это вызов getpublicfield (возможно, с лучшим именем), а object..fieldname - это фактический getfield (частный) может быть хорошим вариантом. Таким образом, типы должны определять getpublicfield вместо геттеров, а попытка выполнения object.fieldname должна выдавать идентификатор ошибки, поле является частным (оно будет частным, если у него нет определения для getpublicfield ).

Я добавил метку решения. Этот вопрос подробно обсуждался, и делать это нужно или нет. При чтении номера # 5848 казалось, что @JeffBezanson @StefanKarpinski и @stevengj этого хотят. Если да, то эта проблема должна стать важной, чтобы ее не забыли. В противном случае закрыть. В любом случае я думаю, что это изменение должно быть сделано до версии 1.0.

@JeffBezanson и я только вчера обсуждали это. Предварительные выводы: (i) да, у нас должно быть это; (ii) не разрешать перегрузку точек для Module (которая будет обрабатываться особым образом); (iii) не предоставлять никакого специального синтаксиса для Core.getfield (поскольку нет необходимости, чтобы перегруженное поле getfield имело то же имя, что и "настоящее" поле; последнее может просто начинаться с подчеркивание).

@stevengj : Похоже на разумный план. Не могли бы вы указать, будет ли это ограничено одним аргументом или также должна поддерживаться версия с несколькими аргументами a.fieldname(b) ? Это сделает вывод из вышеизложенного обсуждения. Кроме того, было бы здорово поставить для этого соответствующую веху (1.0?). Благодаря!

Джефф и я не обсуждали случай с несколькими аргументами. Я считаю, что мы могли бы поддержать его, поскольку вы все равно можете смоделировать его, вернув функцию из случая без аргументов (но это не критично делать сразу по той же причине).

Я использую конвертер для преобразования значений и проверки данных.
как это:

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

Вот забавная трехстрочная реализация этого:

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

Я считаю, что a.b должен фактически вызывать функцию проекции Field{:b}() вместо getfield , чтобы вы получали такие функции, как x->x.a уже бесплатно. Это также позволяет getfield всегда означать низкоуровневый доступ к полю.

Вышеупомянутая реализация полностью работает, но довольно сложна для компилятора (sysimg + 5%, что действительно является приятным сюрпризом). Так что для этого потребуется некоторая эвристика специализации и некоторые ранние оптимизации, которые необходимо обновить, но, надеюсь, это будет жизнеспособным.

Я удивлен, что тесты могут пройти с этой реализацией. Разве эта семантика не делает невозможным оптимизацию ссылок на модули для кодогенератора? Также кажется, что это сделает глобальные переменные намного дороже (скорость и память). Не похоже, что это будет отображаться в sysimg. Хотя ухудшение логического вывода, похоже, должно было сделать sysimg меньше здесь, поэтому ухудшение на 5% не похоже на хорошее начало)

Да, в jl_resolve_globals есть код для преобразования их в GlobalRefs, который необходимо обновить. Кроме того, они должны быть встроены, чтобы кодогенератор мог их обрабатывать. Таким образом, нам, вероятно, просто нужен особый случай, подобный случаю для кортежа getindex .

нам, вероятно, просто нужен особый случай, подобный случаю для кортежа getindex

Этот особый случай предполагает и предполагает, что не будет определен ни один метод, который пересекается со встроенной сигнатурой метода getindex . Я не понимаю, как этот случай особенно применим здесь.

Я бы добавил метод для ::Module и сказал бы, что вам не разрешено затенять его - возможно, принудительно с реальной ошибкой.

@JeffBezanson У вас есть ветка с этими тремя строками? Я сам пытался добавить их в локальную сборку, но Джулия, похоже, к настоящему времени продвинулась, и я не смог заставить ее работать.

Я должен сказать, что если бы у меня было только одно желание добавить функцию в julia 1.0, это было бы ее. Это решило бы две давние проблемы, которые есть у меня как в Mimi, так и в Query .

Случай для Query, на мой взгляд, довольно общий. Я считаю, что с этим можно было бы написать версию NamedTuples, которая не должна была бы генерировать новые типы в макросах, и это, в свою очередь, позволило бы мне писать сгенерированные функции, которые возвращают NamedTuple с набором полей, которые вычисляется в сгенерированной функции. Я думаю, это позволило бы мне написать стабильную по типу версию blackrock / NamedTuples.jl # 4, которая сейчас является моим самым большим камнем преткновения в Query.jl.

Короче говоря, это было бы супер, супер ценно для всей истории обработки данных в julia.

Доступен ли приведенный ниже синтаксис?

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

Зачем вам нужен этот синтаксис?

Желание написать obj.plus(n) вместо obj + n - это серьезный стокгольмский синдром.

Хорошо, данный пример был тривиальным (и плохим).
Это была просто идея использовать этот синтаксис вместо того, чтобы перегружать getfield и иметь возможность использовать аргументы.

Меня больше волнует функциональность. Сокращенный синтаксис для перегрузки getfield кажется гораздо менее важным, и не стоит увлекаться спорами. Кроме того, getfield и setfield! напрямую отражают getindex и setindex! и поэтому в Джулии они естественны. Наконец, широкое использование объектно-ориентированного стиля не совсем подходит для идиомы Джулии, и я не думаю, что мы хотим поощрять его с помощью синтаксиса определения метода, подобного объектно-ориентированному (но см. Мой комментарий относительно аргументов

Одна вещь, которая пришла мне в голову, заключалась в том, что оператор $ устарел. Теперь очевидно, что это делает что-то вроде $(x, sym::Symbol) = ... уже доступным, но мы также могли бы развлечься более причудливой синтаксической переписью, например:

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

Я думаю, что это уже касается большинства случаев, упомянутых в этом выпуске, без полной перегрузки getfield. Откровенно говоря, оператор . в Джулии довольно семантически насыщен, поэтому что-то вроде этого легче усвоить и достаточно удобно, чтобы быть полезным.

@quinnj , уже предложенный в # 18696.

Поскольку у нас уже есть . для доступа к полю, кажется неэлегантным иметь два оператора, подобные доступу к полю, один перегружаемый, а другой нет. И это было бы немного неестественно для межъязыковых вызовов Python и других объектно-ориентированных языков, которые почти повсеместно используют . .

Я не считаю совместимость с другими языками веским аргументом в пользу введения чего-то подобного. Это все равно что сказать: «Код Python выглядит так, поэтому, чтобы иметь возможность притвориться Python, мы тоже должны сделать это». Я еще не видел аргумента в пользу этого, который сделает саму Джулию лучше и / или более последовательной. Уже хорошо известно, что Джулия не предоставляет синтаксис x.f() стиле ООП; разрешение подобных вещей требует несогласованности.

@stevengj , отчасти я x.f _isn't_ доступ к полю. Фактического члена поля f . Вся эта проблема связана с перегрузкой getfield, и я думаю, что основная проблема заключается в потенциальной путанице с тем, действительно ли x.f относится к полю или делает что-то еще под капотом.

Преимущество предложенного мною синтаксического переписывания состоит в том, что он реализует историю взаимодействия языков без дополнительной путаницы с getfield; это то, что я имел в виду, когда упомянул, что . будет перенасыщенным. Неужели x$f будет намного более громоздким? Я просто не понимаю, почему здесь _необходимо_ использовать оператор точки.

Вся эта проблема связана с перегрузкой getfield, и я думаю, что основная проблема заключается в потенциальной путанице относительно того, действительно ли x.f ссылается на поле или делает что-то еще под капотом.

Я не думаю, что в предложении @JeffBezanson из трех строк есть вероятность путаницы: a.b всегда опускается в вызов функции проекции, а не getfield . И еще есть метод по умолчанию / резервный в базе для функции проекции, которая вызывает getfield . При желании пользователи могут добавлять в эту функцию проекции методы для своих типов. getfield всегда означает доступ к низкоуровневому полю в этом предложении, и пользователи не будут добавлять методы в getfield (я предполагаю, что это то, что вы имели в виду под «перегрузкой getfield»). Мне это кажется очень чистым. Сказав это, мне не ясно, находится ли это предложение еще на столе или нет, похоже, есть некоторые проблемы компилятора, которые я не понимаю :)

Мне бы очень хотелось иметь эту функцию (мне она всегда нравилась в Scala). IMHO доступ к полям напрямую в Julia очень естественен (по сравнению, например, с Java-способом защитного определения геттеров и сеттеров для всего). Но без возможности добавлять «виртуальные» поля становится очень сложно развивать / реорганизовывать структуры, не нарушая большого количества кода.

Я знаю, что это было помечено как v2.0, но я хотел бы поднять этот вопрос еще раз в надежде пересмотреть это для версии v1.0. Эта функция будет очень ценной для разработчиков пакетов / библиотек.

При создании пакета для пользователей перегрузка средства доступа к атрибуту позволит сопровождающим пакета представить гораздо более чистый пользовательский интерфейс. Я бы сказал,

complex_data_structure.attribute

легче напечатать, чем

get(complex_data_structure, :attribute)

где get - это функция, необходимая для получения данных, описываемых :attribute .

Это потенциально могло бы также улучшить обнаруживаемость в REPL и было бы полезно для языкового кода и исследования данных.

Кроме того, перехватчик атрибутов установщика также был бы невероятно ценным. Рассмотрим следующий пример (который уже есть во многих моих скриптах :)),

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

Если бы геттер и сеттер атрибутов были включены как часть Julia, вышеперечисленное стало бы следующим.

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

По моему скромному мнению, это гораздо более читабельно. (Примечание: я мог бы использовать композицию конвейера, чтобы сделать его более читаемым, но additional_arguments снова усложнит его там. И аргумент здесь все равно останется в силе.)

Кроме того, ограничивающие значения, предоставляемые пользователем, являются действительно распространенным вариантом использования, и это будет действительно здорово иметь в версии 1.0. Это значительно улучшит пользовательский интерфейс для нескольких пакетов. В настоящее время, насколько мне известно, нет возможности сделать следующее (может ли кто-нибудь исправить меня, если я ошибаюсь)?

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)

Конструктор может ограничить входные данные домена неизменной структурой, однако пользователь по-прежнему может вводить недопустимые значения. Здесь могут помочь геттеры и сеттеры атрибутов, потому что они сделают обратную связь о достоверности данных здесь более незамедлительно, и это сделает интерфейс намного более понятным для пользователя языка.

Это также принесет большую пользу другим пакетам, таким как PyCall.jl и DataFrames.jl, для создания возможно лучших и более интуитивно понятных интерфейсов для пользователей. Авторы этих пакетов также выразили выше заинтересованность в наличии этой функции.

Мысли? Читая ветку, кажется, что это уже довольно близко к финалу. Мне любопытно, что думают по этому поводу авторы?

Здесь реализован дополнительный макрос типа stable: https://github.com/bramtayl/DotOverloading.jl. Я думаю, у него есть потенциал для Base.

@bramtayl , я думаю, что необходимость ставить @overload_dots перед выражениями a.b лишает смысла.

Я не предлагал добавить макрос в базу; Я предлагал встроить в парсер стратегию, используемую макросом. Однако это макрос, который можно запускать для файлов целиком или даже взломать в IDE.

Это повлечет за собой анализ a.b до Expr(:., :a, :(Val{:b}()) и понижение до get_field_overloadable(a, Val{:b}())

@bramtayl , см. реализацию Джеффа

Одна из проблем с перегрузкой getfield заключается в том, что некоторые библиотеки не имеют разумного интерфейса для внутренней структуры структуры данных. Когда все, что мне нужно, - это изменить точку данных в структуре, чтобы я мог запустить свой код и составить отчет на завтра, я не особенно жду возможности редактировать код в библиотеке на трех уровнях в глубину моей зависимости, чтобы я мог чтобы получить прямой доступ к точке данных разумным и быстрым способом. Я хочу чувствовать, что у меня есть контроль над структурами данных, которые я использую.

Во-вторых, когда я использую getfield и setfield, мне нужно разумное ожидание того, что я получаю прямой доступ к структуре данных, а не к какому-то огромному механизму функций. Кроме того, ключевое слово struct используется для определения типа, напоминая вам о C, и я чувствую, что это дает вам ожидание, что доступ к getfield должен быть прямым.

Итак, я думаю, что использование оператора $ вместо этого является разумным компромиссом, поскольку нет никаких ожиданий, что доступ будет прямым, как в getfield, это уже специальный символ в других контекстах, и поэтому это не будет слишком удивительно когда он используется таким образом, его будет легко отказаться от рекомендаций, потому что его мало кто использует и потому что он больше не будет функцией, и он аналогичным образом используется в R.

Одна из проблем с перегрузкой getfield заключается в том, что некоторые библиотеки не имеют разумного интерфейса для внутренней структуры структуры данных.

Приведенная выше реализация getfield (или setfield ).

Я также прошу сделать это в 1.0. Я часто разрываюсь между написанием геттеров / настроек, чтобы мой код оставался в некоторой степени гибким (и затем задаюсь вопросом, как их назвать), или «разрешением / поощрением» пользователя моего кода использовать прямой доступ к полю.

Защитное написание множества геттеров и сеттеров мне не кажется очень Джулианским - в конце концов, у Джулии нет закрытых / защищенных полей, и поэтому она побуждает пользователя обращаться к полям напрямую. Затем возникает вопрос, как назвать множество функций-получателей / установщиков без риска возникновения конфликтов с другими пакетами. И альтернатива, добавление методов для getfield и setfield! (или подобных), отправленных на Val{:fieldname} или около того, также не кажется очень удобной для пользователя.

С другой стороны, если прямой доступ к полям в основном блокирует все структуры навсегда, это явно не следует поощрять, особенно для долговременного кода. Реализация @JeffBezanson может показаться таким изящным выходом из этой дилеммы.

Верно, @davidanthoff. Я должен объединить перегрузку getfield и перегрузку синтаксиса доступа к полю . . Я имел в виду перегрузку синтаксиса доступа к полю.

Существуют обратно совместимые способы добавления этой функции, поэтому ее можно было бы добавить в выпуск 1.x, но этого не происходит в версии 1.0. У нас нет готового дизайна или времени на его реализацию, даже если бы он у нас был.

Было бы чрезвычайно удобно использовать точечный синтаксис для указателей на (C-) структуры.

Я предпочитаю использовать точки только для перемещения и преобразования указателей и использовать [] для deref.

Итак, struct somepair a :: Int b :: Int end и p :: Ptr {somepair}, тогда pa является указателем на поле a, и я пишу в него с pa [] = 3 или читаю с x = pa [].

Затем нам просто нужны прямые объявления типов и, возможно, способ импорта заголовков C, и тогда упаковка C становится легкой задачей.

Ps, чтобы уточнить, примерно так:

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

Что касается бонусных баллов, то типы из библиотеки времени выполнения julia могут быть экспортированы, а pointer_from_objref может фактически дать нам хорошо напечатанный указатель для более удобного самоанализа.

У меня такое чувство, что союзы должны работать автоматически, просто имея два поля с одинаковым смещением?

@chethega Я думаю, вы ищете https://github.com/JuliaLang/julia/pull/21912 , который предоставляет примерно ту же функцию, без проблем, связанных с принятием модели памяти C (нарушения типа каламбура, - ограничивает доступ, сглаживание внутренних указателей и т. д., что ограничивает оптимизацию производительности, возможную на этом языке).

Делает ли постоянное распространение это более выполнимым?

Пожалуйста, измените веху на 1.0! :)

@ Liso77 Что ты имеешь в виду? Это уже реализовано на git master, как указано в статусе при закрытии.

@nalimilan извините, если я неправильно понял! Но я думал, что 1.x помечены отложенными вещами, которые будут решены после 1.0. И это теперь решено ...

Открытый исходный код - это децентрализованное сообщество. Эта веха определяет, что должно быть завершено к 1.0, но участники и работают над тем, что хотят. В данном случае кто-то хотел этого в 1.0, поэтому они предоставили код, чтобы получить это там.

@ Liso77 Насколько я понимаю, в v1.0 этого не будет. Дата замораживания функции Julia v1.0 была назначена на 15 декабря, но эта проблема была закрыта 17 декабря, поэтому я думаю, что мы можем ожидать ее в выпуске 1.x. Разработчики ядра могут исправить меня, если моя интерпретация неверна.

Нет, он объединен в мастер и будет в следующем выпуске.

:) Хорошо! Я просто подумал, что хорошо обозначать 1.0 то, что происходит в 1.0. Если это не нужно, извините за беспокойство! :)

Я думаю, что файл NEWS - лучший способ узнать, что происходит в версии 1.0.

Эта веха была добавлена ​​для обозначения «может быть реализована без нарушения совместимости в серии выпусков 1.x», но затем, поскольку код был готов, он все равно был объединен до того, как функция 1.0 зависла. Я удалил веху для ясности, но на данный момент все, что объединено в мастере, будет в версии 1.0.

Спасибо за разъяснения! Это потрясающе! Файл NEWS также был особенно поучительным.

Спасибо за удаление! Я также очень рад, что он выйдет в версии 1.0! :)

Интересно, есть ли способ поддержать эти новые «динамически определяемые поля» в автозаполнении, например, разрешив перегрузить fieldnames ?. Это может быть очень мощным инструментом для интерактивного использования, например, при работе с DataFrame s (при условии, что они будут поддерживать df.column_name в будущем) со многими столбцами и / или длинными именами столбцов.

Я предполагаю, что в настоящий момент REPL (расширение табуляции), IJulia и т.д. смотрят на определение типа, а не на экземпляр? Но, возможно, это можно изменить для интерактивного использования. Вероятно, это невозможно поддерживать в IDE, такой как Juno, поскольку экземпляры недоступны во время кодирования.

@oschulz , уже можно перегрузить 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

И похоже, что REPL смотрит на fieldnames :

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

julia> x.ba<tab>
bar baz

@yurivish правильно - но "безопасно" ли сейчас это делать? Я не уверен, что еще зависит от fieldnames .

Если это небезопасно, должна быть возможность определить complete_fieldnames(x) = fieldnames(x) , использовать complete_fieldnames для завершения REPL и перегрузить его для пользовательских завершений.

Да, именно поэтому я поднял этот вопрос - на случай, если что-то нужно будет изменить / добавить в Base, чтобы REPL и друзья могли воспользоваться этим позже. Ввиду зависания функции 0.7 ...

Вот почему я рад, что мы вызвали функцию getproperty ; это помогает прояснить, что это не относится к тому же, что и fieldnames . Вмешательство в fieldnames определенно может вызвать серьезные проблемы.

Нам может потребоваться соответствующий propertynames чтобы указать допустимые значения свойств. Конечно, это может быть не совсем четко определенная концепция, поэтому, возможно, нам нужно что-то более конкретное для завершения.

У меня было ощущение, что это может потребовать более глубокого анализа и участия экспертов (спасибо!) - стоит ли нам открыть для этого новый выпуск?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги