Julia: Предложение: отказаться от поддержки, а затем удалить функцию конвейера

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

Предложение

Устарело текущее использование |> в качестве функционального канала. То есть синтаксис x |> f будет объявлен устаревшим в пользу обычного синтаксиса вызова f(x) . После периода устаревания Base.:(|>) будет неопределенным.

Первоначально это изменение было предложено Ткельманом в https://github.com/JuliaLang/julia/issues/16985#issuecomment -227015399.

Было много ожесточенных споров по поводу различных синтаксисов конвейерной передачи функций (в частности, см. #5571) с аргументами в пользу имитации различных языков. Это обсуждение было _ad nauseum_, и я не хочу перефразировать его. Это НЕ является целью данного предложения.

Обоснование

В ряде хорошо продуманных и хорошо поддерживаемых пакетов реализованы макросы, обеспечивающие удобный синтаксис конвейерной обработки для различных случаев использования, как общих, так и специфических. Примеры включают Lazy.jl , FunctionalData.jl , Pipe.jl и ChainMap.jl среди прочих.

StefanKarpinski и andyferris дали нам произвольную композицию функций в #17155, которая может служить той же цели во многих ситуациях.

Как Ткельман аналогичным образом утверждал в № 5571, конвейер функций в Base отличается от знакомого синтаксиса вызовов; наличие обоих в базовом языке, по сути, поддерживает использование двух разных синтаксисов для достижения одной и той же цели. Хотя часто есть несколько способов написать одно и то же, используя решения в Base, обычно решения, по крайней мере, придерживаются схожей ментальной модели. В этом случае синтаксисы используют буквально противоположные ментальные модели.

Конвейеры функций нарушают принцип наименьшего удивления, применяя действие после объекта. То есть, если вы читаете sum(x) , вы сразу понимаете, когда видите sum() , что собираетесь суммировать значения в аргументе. Когда вы видите x |> sum , вы видите x , тогда вы внезапно складываете его значения. Мало, если какие-либо другие базовые решения помещают действие в конец, что делает его нечетным.

Конвейер действительно имеет прецедент в других языках, например %>% Хэдли Уикхэма в R (который не является частью базового R), и иногда этот стиль/поток имеет смысл. Однако в интересах единообразия в Base Julia я предлагаю передать ответственность за предоставление синтаксиса конвейера пакетам, которые могут переопределять |> или предоставлять удобные макросы по своему усмотрению.

Пункты действий

Если это предложение будет принято, действия будут следующими:

  • [ ] Удалите использование синтаксиса в Base, если таковые существуют.
  • [ ] Обеспечить формальное устаревание для Base.:(|>) либо в 0.6, либо в 1.0.
  • [ ] Удалите его в следующем выпуске
deprecation design julep

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

Если мы отказываемся от этого, должны ли мы также отказаться * для конкатенации строк? Это имеет те же проблемы, что и с избыточностью с string(a, b) , и нарушает принцип наименьшего удивления, учитывая, что a и b не являются числами.

В более общем плане нам, вероятно, следует отказаться от всех инфиксных обозначений, так как наличие нескольких соглашений о вызовах, таких как *(a, b) против a * b , сбивает с толку — мы можем сократить наши текущие 3 несопоставимых синтаксиса до одного и получить полную согласованность. Чтобы избежать уродства, мы могли бы подумать о перемещении вызова функции внутри скобок и, возможно, также избавиться от лишних запятых.

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

Передача функций обеспечивает постфиксный синтаксис для вызова функций, который удобен в REPL для интерактивной генерации данных и дальнейшей визуализации/обобщения.

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

julia> somecomplicatedthingproducingarray
...

<ARROW UP>

julia> somecomplicatedthingproducingarray |> summarize

где функция summarize представляет собой что-то вроде графика или гистограммы

@jiahao Я не утверждаю, что это бесполезно, а скорее то, что мы должны быть последовательны в Base и позволить пакетам предоставлять такие вещи.

есть также ans для использования repl

Будет ли в этом предложении |> анализироваться как инфиксный оператор?

@ajkeller34 : определенно, пакеты будут свободны делать с ним все, что захотят (хотя им придется хорошо играть друг с другом с точки зрения пиратства типов и сосуществования), без такого большого ограничения семантической совместимости со старым базовое определение.

Удалите использование синтаксиса в Base, если таковые существуют.

Вот очень устаревшая попытка, которую я сделал для этого: https://github.com/tkelman/julia/commit/212727cdc4aaa3221763580f15d42cfe198bcc1c
В то время большинство применений в базе были довольно тривиальными. В некоторых тестах использование «направить эту вещь в эту анонимную функцию», возможно, лучше с конвейером, но, поскольку большинство из них повторно использовали одну и ту же анонимную функцию несколько раз, вероятно, стоило бы дать ей имя и вызвать его как нормальная функция на тот момент.

Если кому-то интересно, у меня уже есть ChainRecursive.jl . Я опубликую объявление о распаде ChainMap.jl и его различных дочерних элементов, как только он будет завершен.

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

Я согласен с @jiahao , что |> очень полезен, когда вы хотите быстро попробовать что-то в REPL. Кроме того, я нахожу это также полезным, когда ваш аргумент слишком велик или заслуживает некоторого равновесия (да, я сказал это) . В случае связанного примера на самом деле лучше, чтобы аргумент был более заметным, чем вызываемая функция. sum(x) — слишком простой пример, и его действительно следует писать как sum(x) ). В Escher.jl все функции, которые добавляют свойства к элементам, имеют каррированный метод. Это так хорошо сочетается с |> (это было запланировано, оно также отлично работает с map ), и это радость — иметь возможность попробовать что-то в конце строки и увидеть обновление пользовательского интерфейса. немедленно. Мне не нужно искать путь к началу выражения и болтаться по нему. По крайней мере, для использования с Эшером предлагаемая альтернатива состоит в том, чтобы присвоить большие выражения переменным с выдуманными именами, такими как padded_box_contents_aligned_right_tomato_background (или, что еще хуже, box34 ), а затем вызвать для них функцию. В отличие от красиво читаемого <big UI expression> |> aligncontents(right) |> pad(1em) |> fillcolor("tomato")

Я знаю, что после этого я смогу определить |> внутри Эшера, и я, вероятно, так и сделаю, но это убьет мой мозг, чтобы увидеть WARNING: using Escher.|> in module YourPackage conflicts with an existing identifier. Пакеты почти наверняка будут придавать этому разные значения, что для меня очень тревожно!

StefanKarpinski и andyferris дали нам произвольную композицию функций в #17155, которая может служить той же цели во многих ситуациях.

Альтернативой box |> fill("orange") |> pad(2em) будет (fill("orange") ∘ pad(2em))(box) вместо box |> fill("orange") ∘ pad(2em) ? Эти два кажутся ортогональными.

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

Удаление определения этого из Base позволит людям, которым нравится этот синтаксис, делать с ним более интересные вещи.

@shashi Я понимаю вашу точку зрения, но вы могли бы добиться такого же поведения, используя один из пакетов, которые я цитировал в выпуске, не так ли? Например, в вашем примере Эшера вы можете использовать FunctionalData для выполнения <strong i="6">@p</strong> vbox(<really big thing>) | pad(2em) или Lazy для выполнения @> vbox(...) pad(2em) .

Удаление определения этого из Base позволит людям, которым нравится этот синтаксис, делать с ним более интересные вещи.

За исключением того, что его нельзя будет использовать, поскольку единственным безопасным способом его использования будет Escher.|>(...) или Lazy.|>(...) .

Гипотетически, как можно было бы использовать |> в качестве инфиксного оператора, если вы используете два разных пакета, которые определяют и экспортируют его, предполагая, что он не определен в Base ?

@kmsquire Это зависит от варианта использования. |> по-прежнему будет анализироваться как инфиксный оператор, как и сейчас, просто у него не будет значения в Base. Если вы используете его в макросе, не имеет значения, как его определяет конкретный пакет, поскольку он просто становится первым аргументом в выражении вызова.

Возьмем, к примеру, <| , который анализируется как инфиксный оператор, но не имеет значения. Несмотря на то, что он не определен, у нас все еще есть

julia> dump(:(a <| b))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol <|
    2: Symbol a
    3: Symbol b
  typ: Any

Пакеты могут определять и экспортировать методы для Base.:(<|) , которые означают разные вещи, точно так же, как это можно сделать с + .

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

FWIW, ни один пакет цепочки не должен использовать |> во время оценки, потому что во время цепочки все сжимается в одно выражение. Я полагаю, что если пакеты действительно определяют |> , это будет точно такое же определение в базе. Хотя вместо этого они, вероятно, должны использовать макрос цепочки. См. DataFramesMeta в качестве хорошего примера создания интерфейса, который хорошо работает с цепочкой.

Если мы отказываемся от этого, должны ли мы также отказаться * для конкатенации строк? Это имеет те же проблемы, что и с избыточностью с string(a, b) , и нарушает принцип наименьшего удивления, учитывая, что a и b не являются числами.

В более общем плане нам, вероятно, следует отказаться от всех инфиксных обозначений, так как наличие нескольких соглашений о вызовах, таких как *(a, b) против a * b , сбивает с толку — мы можем сократить наши текущие 3 несопоставимых синтаксиса до одного и получить полную согласованность. Чтобы избежать уродства, мы могли бы подумать о перемещении вызова функции внутри скобок и, возможно, также избавиться от лишних запятых.

|> по-прежнему будет анализироваться как инфиксный оператор, как и сейчас, просто у него не будет значения в Base. Если вы используете его в макросе, не имеет значения, как его определяет какой-либо конкретный пакет.

Все еще не уверен, почему нам нужно удалить его из базы.

@bramtayl делает хорошее замечание:

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

И все же единственный способ использовать более одного пакета, который определяет это, - не использовать его инфикс.

Я не понимаю, почему удаление определения в Base требуется для использования |> внутри макросов.

Это не так. Я хочу сказать, что |> можно использовать внутри макросов независимо от ситуации в Base. То же самое касается любого оператора, который выполняет синтаксический анализ надлежащим образом. Суть предложения состоит в том, чтобы сделать Base самосогласованным с точки зрения вызовов функций, тогда конвейерное поведение может быть достигнуто с помощью пакетов. Используют ли пакеты |> , в частности, не имеет значения; с таким же успехом они могли бы использовать <| или буквально любой другой инфиксный оператор.

@ararslan верно, я не это хотел спросить, я сразу обновил свой комментарий, извините.

В любом случае, я не совсем понимаю мнение "Базовая автономность с точки зрения вызовов функций". Похоже, что это только усложнит использование |> в контексте, отличном от макроса. Я лично считаю, что |> стоит изучить новичку, несмотря на то, что это удивительно. По крайней мере, это экономит усилия в REPL. Довольно забавно позже понять, что |> — это функция, такая же, как и любая другая инфиксная функция, и это подкрепляет урок о том, что функции — это просто значения.

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

«Я не утверждаю, что это бесполезно, а скорее, что мы должны быть последовательны»

Иногда полезность побеждает постоянство? Я не знал о несоответствии, но синтаксис |> показался мне полезным. Если его убрать, я не буду чувствовать, что приобрел что-то ощутимое.

Объяснение моего отрицательного голосования, если можно:

Многое из того, что в настоящее время находится в Base, может вместо этого происходить в пакетах. Должны ли мы переместить словари в пакет? Может быть, список операций, таких как сортировка и перемешивание? Операции по сбору и т.д.? Я уверен, что были долгие и подробные обсуждения того, что должно и не должно быть включено в базу, но я предполагаю, что есть три причины, по которым некоторые функции могут быть включены в базу:
1) Эта функциональность необходима для включения других функций в базе.
2) Эта функциональность является неотъемлемой частью языка, многие программисты и пакеты Julia будут использовать ее, и поэтому желательно иметь единую реализацию/синтаксис, с которым согласны все, а не фрагментацию множества людей, создающих свои собственные.
3) Включение этой функциональности в базу делает «сырую» Джулию более приятной в использовании или заставляет ее чувствовать себя более полнофункциональной, что помогает в распространении языка и принятии.

Что-то вроде sum , вероятно, соответствует всем трем пунктам, и я бы сказал, что конвейерная обработка функций соответствует второму и третьему пункту:

Как в первоначальном (хорошо написанном) предложении, так и в обсуждении в этой ветке общей темой является существование нескольких пакетов, предоставляющих функциональность, подобную конвейеру, с помощью макросов: Lazy.jl, Pipe.jl, ChainMap.jl и т. д. Существование нескольких пакетов убедительно свидетельствует о том, что многие люди в сообществе считают конвейерную передачу полезной и желательной функцией, и присутствие этих пакетов в этой ветке обсуждения предполагает, что многие здесь понимают и поддерживают использование конвейерной обработки.

Учитывая, что конвейер является общей и популярной функцией в сообществе Julia и других языках, даже в этом обсуждении люди, кажется, соглашаются, что у него много применений, особенно в REPL (где Julia сияет), и в экосистеме Julia уже есть фрагментация. ... мое мнение состоит не в том, что его следует удалить из базы, а в том, что синтаксис конвейера, доступный в базе, должен быть улучшен, чтобы уменьшить потребность в фрагментации. Различные пакеты, предлагающие разные способы, например, построения графиков, кажутся приемлемыми; различные популярные пакеты, предлагающие различные способы применения функций, кажутся довольно пугающими.

Я также утверждаю, что удаление конвейера из Base, но оставление инфиксного оператора довольно удивительно: в Julia вы не можете определить свои собственные инфиксные операторы, но есть неиспользуемый инфиксный оператор |> , который можно определить как Вы, пожалуйста? Если это хорошая функциональность, почему бы не дать нам целых 10 или 20 инфиксных операторов, чтобы мы могли определять их по своему усмотрению?

Наконец, я считаю естественным продолжать конвейер именно потому, что он отличается от других приложений функций. Это особенность, а не ошибка, которая отличается от других соглашений о применении функций; эта разница позволяет ему сиять в некоторых случаях использования. И есть другие случаи, когда (слегка взмахнув рукой) существительное стоит перед глаголом, и многие из них являются синтаксическим сахаром в тех случаях, когда применение необработанных функций громоздко. Внезапно мне приходит в голову, что присваивание x = 5 ставит существительное (символ x ) перед глаголом (привязать к значению). Аналогично для доступа к полям типов t.a вместо getfield . И самое главное, индексирование массива z[5] читается как «из z взять 5-й элемент» и, как правило, более естественно, чем getindex(z, 5) .

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

Возможно, их будет больше, если включить все юникодные в дополнение к невостребованным ASCII, таким как <| , ++ , ...

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

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

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

Не читая всю ветку

😕

Я люблю быть в состоянии трубы

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

в Julia вы не можете определять свои собственные инфиксные операторы

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

Я как бы нейтрально отношусь к этому, но я поддержу мнение, что весь смысл |> в том, что он отстает от обычного синтаксиса вызова функции. Даже самые большие поклонники трубопроводов не просят (насколько мне известно) например, sin <| x , потому что это действительно избыточно с sin(x) . |> предназначен для тех случаев, когда глазам и/или мозгу легче думать о потоке данных слева направо без большого количества скобок.

Я бы хотел, чтобы |> был более мощным, например, позволял x |> f(_) + 2g(_) |> h и т. д., а не просто оператором. Каждый раз, когда кто-то определяет, что x |> f означает что-то помимо f(x) , это действительно сбивает меня с толку, потому что весь смысл оператора в том виде, в котором мы его использовали, заключается в том, что это синтаксис вызова другого порядка. Поскольку мы можем перегрузить вызов, я не вижу веских причин для того, чтобы x |> f означало что-то другое.

@StefanKarpinski Более мощные трубы уже можно получить с помощью макросов. См., например, Pipe.jl , который обеспечивает именно тот синтаксис, который вы описываете. Пока |> является оператором (лично я не считаю |> особым случаем), макросы могут использовать любой разделитель конвейера, который анализирует инфикс, даже если он не является оператором. :call . Например, можно было бы аналогичным образом использовать @~ для канала (по крайней мере, на момент написания этой статьи). Такой уровень гибкости является одним из преимуществ использования макросов в Julia.

Мы могли бы добавить в язык функциональность Pipe.jl, и тогда вам не нужно было бы писать @pipe .

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

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

Но если нам больше не нужно |> , я не вижу большого вреда в том, чтобы оставить его (тривиальное) определение в покое.

Я не думаю, что в настоящее время есть какие-либо предложения по перепрофилированию |> в Base. Мой аргумент в пользу того, чтобы не определять его в Base, заключается в том, что это дает нам большую согласованность без потери функциональности.

Будут ли какие-либо «более мощные конвейерные» предложения или реализации пакетов упрощены, если не будет этого существующего определения, о котором нужно беспокоиться или обходиться?

@ararslan «Это неправда; все, что анализируется как инфиксный оператор, может быть определено или переопределено».

Из руководства "операторы && и ||" они анализируются, но не могут быть переопределены (это хорошо). Я считаю, что только исключения.

Так называемые «логические операторы» && и || являются инфиксными. [унарное бинарное отношение] «оператор» - это ИМХО неправильный термин для них, поскольку это не так. Not аналогичен логическому побитовому & и | которые допускают перегрузку (что-то, что я не уверен, является хорошим выбором).

@PallHaraldsson Это поток управления, а не операторы в том же смысле, что и & , | , + и т. д.

Давайте постараемся оставаться в теме здесь, если это возможно, пожалуйста.

@tkelman Это хороший момент. Я подозреваю, что мы можем сделать будущий синтаксис конвейера обратно совместимым. Например, если _ зарезервировано, тогда |> может иметь особое значение, когда его аргументы содержат _ , и в противном случае делать то же самое, что и сейчас.

Есть еще одна проблема: чтобы заставить |> работать для вашего объекта, вы определяете |> или «оператор вызова функции» (т.е. добавляете к нему методы)? Было бы чище, если бы |> был встроенным синтаксисом для вызова функции, чтобы гарантировать, что f(x) и x |> f всегда одинаковы.

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

Я знаю, что этот вопрос закрыт. Просто хотел сказать "спасибо" за то, что сохранили оператора.

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