Julia: Цепочка функций

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

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

sum(a::Int, b::Int) -> a + b

a = 1
sum(1, 2) # = 3
a.sum(2) # = 3 or
1.sum(2) # = 3

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

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

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

Пакеты

Прототипы без упаковки

Связанный:


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

дата обновления: 2020-04-20

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

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

Вопрос об исключениях - отдельная тема? я думаю, что ответ отрицательный, кроме обертывания тела функции в try..catch.

Пример 1.sum (2) тривиален (я также предпочитаю sum (1,2)), но он просто демонстрирует, что функция как таковая не принадлежит этому типу ex. 1 можно передать функции с первым параметром Real, а не только функциям, которые ожидают, что первый параметр будет Int.

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

validate_for(name).required().gt(3) 
# vs 
gt(required(validate_for(name)), 3) 

Исключения, о которых я только что говорил, связаны с функциями, возвращающими недетерминированные результаты (что в любом случае является плохой практикой). Примером может служить вызов a.sum (2) .sum (4), где .sum (2) иногда возвращает String вместо Int, но .sum (4) ожидает Int. Я полагаю, что компилятор / среда выполнения уже достаточно умен, чтобы оценивать такие обстоятельства - что будет таким же при вложении функции sum (sum (1, 2), 4) - но запрос функции потребует расширения указанной функциональности для обеспечения ограничений типа для точечные функции.

Один из вариантов использования, который нравится людям, - это «свободный интерфейс». В ООП API иногда приятно, когда методы возвращают объект, поэтому вы можете делать такие вещи, как some_obj.move(4, 5).scale(10).display()

На мой взгляд, это лучше выразить как композиция функций, но |> не работает с аргументами, если вы не используете anon. функции, например, some_obj |> x -> move(x, 4, 5) |> x -> scale(x, 10) |> display , что довольно некрасиво.

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

Другой вариант - какой-то макрос @composed , который добавит такое поведение к следующему выражению

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

function move(obj, x, y)
    # move the object
end

move(x, y) = obj -> move(obj, x, y)

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

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

В понедельник, 27 января 2014 г., Спенсер Рассел [email protected]
написал:

Один из вариантов использования, который нравится людям, - это «свободный интерфейс». Это
иногда приятно в ООП API, когда методы возвращают объект, поэтому вы можете сделать
такие вещи, как some_obj.move (4, 5) .scale (10) .display ()

На мой взгляд, это лучше выразить как композиция функций, но
|> не работает с аргументами, если вы не используете anon. функции, например some_obj
|> x -> move (x, 4, 5) |> x -> scale (x, 10) |> display, что довольно
некрасиво.

Одним из вариантов поддержки такого рода вещей было бы, если бы |> засунули LHS как
первый аргумент RHS перед оценкой, но тогда он не мог быть
реализована как простая функция, как сейчас.

Другой вариант - это какой-то макрос @composed , который добавит это
вид поведения к следующему выражению

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

функция move (obj, x, y)
# переместить объект
конец

move (x, y) = obj -> move (obj, x, y)

поэтому, когда вы не предоставляете объект, он выполняет частичное приложение функции
(возвращая функцию с 1 аргументом), которую затем можно использовать внутри
нормальная |> цепь.

-
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/5571#issuecomment -33408448
.

ssfrr Мне нравится, как ты думаешь! Я не знал о составе функции |> . Я вижу, недавно было подобное обсуждение [https://github.com/JuliaLang/julia/issues/4963].

kmsquire Мне нравится идея расширить текущую композицию функций, чтобы вы могли указывать параметры для вызывающей функции ex. some_obj |> move(4, 5) |> scale(10) |> display . Встроенная поддержка будет означать на одно закрытие меньше, но то, что предлагает ssfrr, на данный момент является жизнеспособным, и в качестве дополнительного преимущества он также должен быть совместим с расширенной функциональностью композиции функций, если она будет реализована.

Спасибо за оперативные ответы :)

На самом деле @ssfrr был правильным - это невозможно реализовать как простую функцию.

Вам нужны макросы потоков (например, http://clojuredocs.org/clojure_core/clojure.core/-%3E). К сожалению, синтаксис @ -> @ - >> @ -? >> в Julia неприменим.

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

Я думаю, что это работает для макроса @ssfrr compose:

Изменить: это может быть немного яснее:

import Base.Meta.isexpr
_ispossiblefn(x) = isa(x, Symbol) || isexpr(x, :call)

function _compose(x)
    if !isa(x, Expr)
        x
    elseif isexpr(x, :call) &&    #
        x.args[1] == :(|>) &&     # check for `expr |> fn`
        length(x.args) == 3 &&    # ==> (|>)(expr, fn)
        _ispossiblefn(x.args[3])  #

        f = _compose(x.args[3])
        arg = _compose(x.args[2])
        if isa(f, Symbol)
            Expr(:call, f, arg) 
        else
            insert!(f.args, 2, arg)
            f
        end
    else
        Expr(x.head, [_compose(y) for y in x.args]...)
    end
end

macro compose(x)
    _compose(x)
end
julia> macroexpand(:(<strong i="11">@compose</strong> x |> f |> g(1) |> h('a',"B",d |> c(fred |> names))))
:(h(g(f(x),1),'a',"B",c(d,names(fred))))

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

+1. Это особенно важно, когда вы используете Julia для анализа данных, где обычно используются конвейеры преобразования данных. В частности, Pandas в Python удобно использовать, потому что вы можете писать такие вещи, как df.groupby ("что-то"). Aggregate (sum) .std (). Reset_index (), что является кошмаром для написания с текущим синтаксисом |> .

: +1: для этого.

(Я уже подумал, предлагая использовать для этого инфиксный оператор .. ( obj..move(4,5)..scale(10)..display ), но оператор |> тоже подойдет)

Другая возможность - добавить синтаксический сахар для каррирования, например
f(a,~,b) переводится в x->f(a,x,b) . Тогда |> может сохранить свое текущее значение.

Ооо, это был бы отличный способ превратить любое выражение в функцию.

Возможно, что-то вроде анонимных функциональных литералов Clojure, где #(% + 5) - это сокращение для x -> x + 5 . Это также распространяется на несколько аргументов с% 1,% 2 и т. Д., Поэтому #(myfunc(2, %1, 5, %2) - это сокращение для x, y -> myfunc(2, x, 5, y)

С эстетической точки зрения я не думаю, что этот синтаксис очень хорошо подходит для очень удобочитаемой julia, но мне нравится общая идея.

Чтобы использовать мой пример выше (и переключиться на тильду @malmaud вместо%), вы можете сделать

some_obj |> move(~, 4, 5) |> scale(~, 10) |> display

что выглядит довольно красиво.

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

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

Очевидно, мы не можем сделать это с помощью ~ поскольку это уже стандартная функция в Julia. Scala делает это с помощью _ , что мы тоже могли бы сделать, но есть существенная проблема с выяснением того, какая часть выражения является анонимной функцией. Например:

map(f(_,a), v)

Что это значит?

map(f(x->x,a), v)
map(x->f(x,a), v)
x->map(f(x,a), v)

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

Хорошо, я понимаю вашу точку зрения на двусмысленность того, как далеко нужно заходить. В Clojure все выражение заключено в #(...) поэтому оно однозначно.

В Джулии идиоматично использовать _ как значение безразличия? Как x, _ = somfunc() если somefunc возвращает два значения, а вам нужно только первое?

Чтобы решить эту проблему, я думаю, нам понадобится макрос с использованием типа интерполяции:

some_obj |> @$(move($, 4, 5)) |> @$(scale($, 10)) |> display

но опять же, я думаю, что в этот момент становится довольно шумно, и я не думаю, что @$(move($, 4, 5)) дает нам что-то по сравнению с существующим синтаксисом x -> move(x, 4, 5) , который, по ИМО, и красивее, и более явным.

Я думаю, это было бы хорошим применением инфиксного макроса. Как и в случае с # 4498, если какое-либо правило определяет функции как инфиксные, применяемые также к макросам, у нас может быть макрос @-> или @|> который будет иметь многопоточное поведение.

Да, мне нравится идея инфиксного макроса, хотя для этого можно было бы просто ввести новый оператор вместо целой системы для макросов на месте. Например,
some_obj ||> move($,4,5) ||> scale($, 10) |> disp
или, может быть, просто оставить |> но иметь правило,
x |> f неявно преобразуется в x |> f($) :
some_obj |> scale($,10) |> disp

Ребята, все это выглядит ужасно: |> ||> и т. Д.
Пока что я обнаружил, что синтаксис Джулии настолько ясен, что все, что обсуждалось выше, не выглядит так красиво по сравнению с чем-либо еще.

В Scala это, наверное, худшая вещь - у них так много операторов, как ::,:, <<, >> + :: и так далее - это просто делает любой код уродливым и нечитаемым для человека без нескольких месяцев опыта использования язык.

Сожалею, что тебе не нравятся предложения, Антон. Было бы полезно, если бы вы сделали альтернативное предложение.

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

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

Мне нравится фраза «ученые, конструирующие языки» - она ​​звучит намного грандиознее, чем программисты-программисты, уставшие от Matlab.

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

Я еще раз поддержу предложение Спенсера - x |> f(a) переводится в f(x, a) , очень аналогично тому, как работают блоки do (и это усиливает общую идею, что первый аргумент функция является привилегированной в Julia для синтаксических сахарных целей). x |> f тогда рассматривается как сокращение для x |> f() . Это просто, не вводит никаких новых операторов, обрабатывает подавляющее большинство случаев, для которых нам нужна цепочка функций, имеет обратную совместимость и соответствует существующим принципам проектирования Julia.

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

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

Аналогично, в Julia разработчик библиотеки уже может поддерживать цепочку с |> , определяя свои функции из N аргументов для возврата функции с 1 аргументом при задании N-1 аргументов, как упоминалось здесь.

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

@JeffBezanson , кажется, что этот оператор мог бы быть реализован, если бы существовал способ делать инфиксные макросы. Знаете ли вы, есть ли здесь идеологическая проблема, или она просто не реализована?

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

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

В четверг, 6 февраля 2014 г., Спенсер Рассел [email protected]
написал:

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

Аналогично, в Julia разработчик библиотеки уже может поддерживать цепочку
с |> путем определения их функций из N аргументов для возврата функции
из 1 аргумента при наличии аргументов N-1, как указано

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

@JeffBezanson https://github.com/JeffBezanson , похоже, что это
Оператор можно было бы реализовать, если бы существовал способ делать инфиксные макросы. Вы
знаете, есть ли в этом идеологическая проблема, или она просто не реализована?

-
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/5571#issuecomment -34374347
.

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

function move(obj::MyType, x, y, args...)
    # do stuff
    obj
end

move(args...) = obj::MyType -> move(obj, args...)

Я думаю, что такое поведение может быть обработано макросом @composable который будет обрабатывать второе объявление.

Идея инфиксного макроса привлекательна для меня в ситуации, когда она будет объединена с объявлением инфиксных функций, что обсуждается в # 4498.

Почему создатели Julia так категорически против разрешения объектам содержать их собственные методы? Где я могу узнать больше об этом решении? Какие мысли и теории стоят за этим решением?

@meglio более полезным местом для общих вопросов является список рассылки или тег StackOverflow julia-lang . См. Доклад Стефана и архивы пользователей и списков разработчиков для предыдущих обсуждений этой темы.

Просто вмешиваюсь, для меня наиболее интуитивно понятная вещь - заменить какой-нибудь заполнитель на
значение предыдущего выражения в последовательности вещей, которые вы пытаетесь составить, аналогично макросу as-> в clojure. Итак, это:

<strong i="8">@as</strong> _ begin
    3+3
    f(_,y)
    g(_) * h(_,z)
end

будет расширен до:

g(f(3+3,y)) * h(f(3+3,y),z)

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

Я начал рисовать что-то крохотное, похожее на эту, в последней четверти во время промедления на финальной неделе.

Мы также могли бы поддерживать одинарную версию, используя |> :

<strong i="19">@as</strong> _ 3+3 |> f(_,y) |> g(_) * h(_,z)

@porterjamesj , мне нравится эта идея!

Я согласен; это довольно красиво и имеет привлекательную общность.
7 февраля 2014 г. в 15:19 «Кевин Сквайр» [email protected] написал:

@porterjamesj https://github.com/porterjamesj , мне нравится эта идея!

Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/5571#issuecomment -34497703
.

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

Обратите внимание, что в Джулии, поскольку мы не выполняем большую часть шаблона obj.method(args...) , а вместо этого используем шаблон method(obj, args...) , у нас обычно нет методов, возвращающих объекты, с которыми они работают, для экспресс цель объединения методов. (Это то, что делает jQuery , и отлично работает в javascript). Таким образом, здесь мы не экономим столько времени на набор текста, но я думаю, что это действительно хорошо для того, чтобы установить «каналы» между функциями.

Учитывая, что -> и ->> являются лишь частными случаями вышеперечисленного и довольно распространенными, мы, вероятно, могли бы довольно легко реализовать их. Хотя вопрос, как их назвать, немного сложный. Может быть, @threadfirst и @threadlast ?

Мне тоже нравится, что это макрос.

Не лучше ли, если расширение по примеру будет чем-то вроде

tmp = 3+3; tmp = f(tmp); return h(tmp, z)

чтобы избежать многократных вызовов одной и той же операции? (Возможно, это уже подразумевалось в идее @porterjamesj )

Другое предложение: возможно ли, что макрос расширяет ярлыки f на f(_) и f(y) на f(_,y) ? Может быть, это будет слишком много, но я думаю, что тогда у нас есть возможность использовать заполнитель только тогда, когда это необходимо ... (однако, ярлыки должны быть разрешены только для вызовов функций, а не для таких выражений, как g(_) * h(_,z) выше)

@cdsousa - это хорошая clojure использует для этого последовательные привязки let; Я не уверен, что нам это сойдет с рук, потому что я недостаточно знаю о производительности нашего let .

Итак, макрос @as использует разрывы строк и => качестве точек разделения, чтобы решить, какое выражение подстановки и что будет заменено?

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

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

@cdsousa :

Другое предложение: возможно ли, что макрос расширяет ярлыки f на f(_) и f(y) на f(_,y)

Для меня имеет смысл от f до f(_) . Во-вторых, я придерживаюсь мнения, что явное указание местоположения лучше, поскольку разумные люди могут утверждать, что либо f(_,y) либо f(y,_) более естественно.

Учитывая, что -> и ->> clojure являются частными случаями вышеперечисленного и довольно распространены, мы, вероятно, могли бы довольно легко реализовать их. Хотя вопрос, как их назвать, немного сложный. Может быть, @threadfirst и @threadlast ?

Я думаю, что указание явного местоположения с помощью f(_,y...) или f(y..., _) позволяет сделать код вполне понятным. Хотя дополнительный синтаксис (и операторы) имеет смысл в Clojure, на самом деле у нас нет дополнительных доступных операторов, и я думаю, что дополнительные макросы, как правило, делают код менее понятным.

Итак, макрос @as использует разрывы строк и => качестве точек разделения, чтобы решить, какое выражение замены и что будет заменено?

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

Просто чтобы вы знали, в Lazy.jl есть реализация макроса потоковой

@>> range() map(x->x^2) filter(iseven)

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

Я мог бы также реализовать @as> в Lazy.jl, если есть интерес. Lazy.jl теперь также имеет макрос @as .

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

Lazy.jl выглядит очень красивым и активно поддерживается. Есть ли веская причина, по которой это должно быть в базе?

Как будет работать цепочка функций с функциями, возвращающими несколько значений?
Каким будет результат объединения, например:

function foo(a,b)
    a+b, a*b   # x,y respectively
end

и bar(x,z,y) = x * z - y быть?

Разве для этого не потребуется синтаксис вроде bar(_1,z,_2) ?

Добавим еще один пример:

data = [2.255, 3.755, 6.888, 7.999, 9.001]

Чистый способ написать: log(sum(round(data))) is data|>round|>sum|>log
Но если бы мы хотели сделать журнал с основанием 2 и хотели округлить до 3 десятичных знаков,
затем: мы можем использовать только первую форму:
log(2,sum(round(data,3)))

Но в идеале мы хотели бы уметь:
data|>round(_,3)|>sum|>log(2,_)
(или похожие)

Я сделал прототип того, как, по моему мнению, он должен работать.
https://github.com/oxinabox/Pipe.jl

Это не решает проблему @gregid , но сейчас я работаю над этим.
Он также не обрабатывает необходимость расширения аргументов

Он похож на макрос потоковой передачи Lazy.jl @ one-more-minute, но сохраняет символ |> для удобства чтения (личные предпочтения).

Я потихоньку превращу его в упаковку, возможно, в какой-то момент

Еще один вариант:

data |>   x -> round(x,2)  |> sum |>  x -> log(2,x)

Хотя это обозначение длиннее log(2,sum(round(data,2))) иногда оно улучшает читаемость.

@shashi это неплохо, не думал об этом,
Я считаю, что в целом слишком многословный, чтобы его было легко читать

https://github.com/oxinabox/Pipe.jl Теперь решает проблему @gregid .
Хотя, если вы запрашиваете и _[1] и _[2] он делает это, выполняя несколько вызовов подстановки
Я не уверен, что это наиболее желательное поведение.

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

В результате [1:10] |> map(e -> e^2) [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] .

Оглядываясь назад, @ssfrr намекал на это, но аргумент obj в их примере будет автоматически передан map в качестве второго аргумента в моем примере, что избавит программистов от необходимости определять свои функции для поддержите это.

Что вы думаете, это значит?

5 июня 2015 г. в 17:22 H-225 [email protected] написал:

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

В результате [1:10] |> map (e -> e ^ 2) приведет к [1, 4, 9, 16, 25, 36, 49, 64, 81, 100].

Лично я считаю, что это красиво и ясно, но не слишком многословно.

Очевидно, можно было бы написать result = map (sqr, [1:10]), но зачем им вообще оператор конвейера?
Возможно, мне чего-то не хватает?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

@StefanKarpinski
В принципе, оператор должен работать так:

  • x |> y(f) = y(x, f)
  • x |> y(f) = y(f, x)

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

Это яснее?

Lazy.jl выглядит очень красивым и активно поддерживается. Есть ли веская причина, по которой это должно быть в базе?

Я думаю, что это важный вопрос.

Причина, по которой это может быть желательно в базе, 2 раза:

1.) Мы можем захотеть поощрить конвейерную обработку как юлианский путь - можно привести аргументы, что это более читабельно.
2.) такие вещи, как Lazy.jl, FunctionalData.jl и мой собственный Pipe.jl, требуют макроса для обертывания выражения, над которым он должен действовать, что делает его менее читаемым.

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

Я не уверен, что |>, (или их кузен блок do) вообще принадлежат ядру.
Но инструментов для их определения вне синтаксического анализатора не существует.

Возможность иметь такой синтаксис конвейерной обработки кажется очень приятной. Можно ли добавить в Base только это, т.е. x |> y(f) = y(f, x) часть, которую Lazy.j, FunctionalData.jl и Pipe.jl могли использовать? : +1:

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

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

Я понимаю, что вы имеете в виду; он был бы более унифицированным, если бы у вас был один синтаксис вызова функции для всего. Лично я считаю, что лучше упростить написание [сложного] кода, который будет легко понять. Конечно, вам нужно изучить синтаксис и его значение, но, IMHO, |> понять не сложнее, чем как вызвать функцию.

@tkelman Я бы посмотрел на это с другой точки зрения. Очевидно, что есть люди, которые предпочитают такой стиль программирования. Я вижу, что, возможно, вы захотите иметь согласованный стиль для исходного кода для Base, но речь идет только о добавлении поддержки синтаксического анализатора для их предпочтительного стиля программирования их приложений Julia. Неужели джулианцы действительно хотят пытаться диктовать или иным образом подавлять то, что другие люди считают полезным?
Я обнаружил, что конвейерная обработка данных очень полезна в Unix, поэтому, хотя я никогда не использовал язык программирования, который позволял бы это делать в этом языке, я бы, по крайней мере, высказал сомнения.

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

Конвейер отлично подходит для оболочки unix, где все принимает и выводит текст. С более сложными типами и множеством входов и выходов все не так однозначно. Итак, у нас есть два синтаксиса, но один имеет гораздо меньший смысл в случае MIMO. Поддержка парсером альтернативных стилей программирования или DSL обычно не требуется, поскольку у нас есть мощные макросы.

Хорошо, спасибо, я читал комментарий

Но инструментов для их определения вне синтаксического анализатора не существует.

Понятно ли, что было бы сделано для снятия упомянутых вами ограничений реализации?

Некоторые из предыдущих предложений потенциально могут быть реализованы, если |> анализирует свои аргументы как макрос, а не как функцию. Прежнее значение конвейера команд-объекта |> устарело, поэтому на самом деле его можно освободить для чего-то другого, например, 0.5-dev.

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

Парсинг ~ просто безумие, это функция в базе. Использование _ , _1 , _2 кажется _более_ разумным (особенно, если вы повысите, если эти переменные определены в другом месте в области видимости). Тем не менее, пока у нас не появятся более эффективные анонимные функции, похоже, что это не сработает ...

реализовано, заставляя |> анализировать его аргументы как макрос, а не как функцию

Если вы этого не сделаете!

Парсинг ~ просто безумие, это функция в базе

Это унарный оператор для побитовой версии. Бинарный Infix ~ анализирует как макрос, ref https://github.com/JuliaLang/julia/issues/4882 , что я считаю странным использованием оператора ascii (https://github.com/ JuliaLang / julia / pull / 11102 # issuecomment-98477891).

@tkelman

Итак, у нас есть два синтаксиса, но один имеет гораздо меньший смысл в случае MIMO.

3 Синтаксиса. Вид.
Pipe in, нормальный вызов функции и Do-блоки.
Спорный даже 4, поскольку макросы также используют другое соглашение.


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

Я делаю много кода вроде (с помощью iterators.jl и pipe.jl):

  • loaddata(filename) |> filter(s-> 2<=length(s)<=15, _) |> take!(150,_) |> map(eval_embedding, _)
  • results |> get_error_rate(desired_results, _) |> round(_,2)

Для SISO это лучше (по моим личным предпочтениям), для MIMO - нет.

Джулия, кажется, уже решила, что есть несколько правильных способов делать что-то.
В чем я не уверен на 100%, это хорошо.

Как я уже сказал, мне бы хотелось, чтобы блоки Pipe и Do были убраны из основного языка.

У Do-блоков есть довольно много очень полезных вариантов использования, но меня немного раздражало то, что они должны использовать первый вход в качестве функции, не всегда полностью вписывается в философию множественной отправки (и ни pandas / UFCS в стиле D с постфиксом data.map(f).sum() , я знаю, что он популярен, но не думаю, что его можно эффективно комбинировать с множественной отправкой).

Скорее всего, конвейеры скоро станут устаревшими и будут оставлены пакетам для использования в DSL, таких как Pipe.jl.

Джулия, кажется, уже решила, что есть несколько правильных способов делать что-то.
В чем я не уверен на 100%, это хорошо.

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

@tkelman @oxinabox
Мне еще предстоит найти четкую причину, по которой его не следует включать в язык или даже в «основные» пакеты. [например: База]
Лично я думаю, что создание макроса |> может быть решением.
Возможно, что-то подобное? (Я не мастер-программист Юля!)

macro (|>) (x, y::Union(Symbol, Expr))
    if isa(y, Symbol)
        y = Expr(:call, y) # assumes y is callable
    end
    push!(y.args, x)
    return eval(y)
end

В Julia v0.3.9 мне не удалось определить его дважды - один раз с помощью символа и один раз с помощью выражения; мое [ограниченное] понимание Union заключается в том, что его использование снижает производительность, поэтому я предполагаю, что это было бы что-то исправить в моем примере кода игрушки.

Конечно, есть проблема с синтаксисом использования для этого.
Например, чтобы запустить эквивалент log(2, 10) , вы должны написать @|> 10 log(2) , что здесь нежелательно.
Насколько я понимаю, вы должны иметь возможность каким-то образом помечать функции / макросы как «инфиксные», так что вы могли бы затем написать это так: 10 |> log(2) . (Исправьте, если не так!)
Надуманный пример, я знаю. Я не могу сейчас придумать хороший! знак равно

Также стоит указать на одну область, которую я не рассмотрел в моем примере ...
Итак, например:

julia> for e in ([1:10], [11:20] |> zip) println(e) end
(1,11)
(2,12)
(3,13)
(4,14)
(5,15)
(6,16)
(7,17)
(8,18)
(9,19)
(10,20)

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

9 июня 2015 г. в 21:37 H-225 [email protected] написал:

Я еще не нашел четкой причины, по которой его не следует включать в язык

Это неправильная ментальная позиция при проектировании языков программирования. Вопрос должен быть "почему?" а не "почему бы и нет?" Для включения каждой функции нужна веская причина, и даже если у вас есть веская причина, вам следует хорошо подумать, прежде чем что-либо добавлять. Вы можете жить без этого? Есть ли другой способ сделать то же самое? Есть ли другой вариант функции, который был бы лучше, более общий или более ортогонален существующим функциям? Я не говорю, что этой конкретной идеи не могло быть, но должно быть гораздо лучшее оправдание, чем «почему бы и нет?» с несколькими примерами, которые не лучше обычного синтаксиса.

Вопрос должен быть "почему?" а не "почему бы и нет?"

+ 1_000_000

Конечно.
См. Этот довольно известный пост в блоге :
Каждая функция начинается с -100 баллов.
Он должен быть значительно улучшен, чтобы его можно было добавить в язык.

FWIW, Pyret (http://www.pyret.org/) проходил именно это обсуждение несколько месяцев назад. Язык поддерживает нотацию «пушечное ядро», которая изначально функционировала во многом так, как предлагают люди с |> . В Пирете,

[list: 1, 2, 3, 5] ^ map(add-one) ^ filter(is-prime) ^ sum() ^ ...

Итак, обозначение пушечного ядра сократилось до добавления аргументов к функциям.

Вскоре они решили, что этот синтаксис слишком запутанный. Почему sum() вызывается без аргументов? и т.д. В конечном итоге они выбрали элегантную альтернативу каррированию:

[list: 1, 2, 3, 5] ^ map(_, add-one) ^ filter(_, is-prime) ^ sum() ^ ...

Это имеет то преимущество, что он более явный и упрощает оператор ^ до простой функции.

Да, мне это кажется более разумным. Это также более гибко, чем каррирование.

@StefanKarpinski Я немного запутался. Вы имели в виду более гибкую, чем связывание (не каррирование)? В конце концов, решение Пайрета заключалось в том, чтобы просто использовать каррирование, которое является более общим, чем связывание.

Возможно, если мы немного изменим синтаксис |> (я действительно не знаю, насколько это сложно реализовать, возможно, он конфликтует с | и > ), мы можно было установить что-нибудь гибкое и читабельное.

Определение чего-то вроде

foo(x,y) = (y,x)
bar(x,y) = x*y

Мы бы хотели иметь:

randint(10) |_> log(_,2) |> sum 
(1,2) |_,x>  foo(_,x)   |x,_>   bar(_,2) |_> round(_, 2) |> sum |_> log(_, 2)

Другими словами, у нас будет такой оператор, как |a,b,c,d> где a , b , c и d получат возвращаемые значения последнее выражение (по порядку) и используйте его в качестве заполнителей внутри следующего.

Если внутри |> нет переменных, он будет работать так же, как и сейчас. Мы также могли бы установить новую звезду: f(x) |> g(_, 1) будет получать все значения, возвращаемые f(x) и связывать их с заполнителем _ .

@samuela , я имел в виду, что при каррировании вы можете опустить только завершающие аргументы, тогда как с подходом _ вы можете опустить любые аргументы и получить анонимную функцию. Т.е. с учетом f(x,y) с каррированием вы можете сделать f(x) чтобы получить функцию, которая выполняет y -> f(x,y) , но с подчеркиванием вы можете сделать f(x,_) для того же самого, но также выполните f(_,y) чтобы получить x -> f(x,y) .

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

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

@StefanKarpinski А, я понимаю, о чем вы. Согласовано.

@ScottPJones очевидный ответ - разрешить художественные стрелки ASCII:
http://scrambledeggsontoast.github.io/2014/09/28/needle-announce/

@simonbyrne Это выглядит даже хуже, чем программирование в Fortran IV на перфокартах, как я делал в моей растраченной юности! Просто интересно, может ли какой-нибудь синтаксис типа _1, _2 и т. Д. Разделить множественный возврат, или это просто глупая идея с моей стороны?

@simonbyrne Замечательно . Реализация этого в виде строкового макроса была бы замечательным проектом GSoC.

Почему sum () вызывается без аргументов?

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

@simonbyrne Вы не думаете, что это можно сделать однозначно? Если да, то я думаю, что это стоит сломать (текущая нотация do ), если ее можно сделать более логичной, более общей и согласованной с цепочкой.

@simonbyrne Да, полностью согласен. Я понимаю мотивацию нынешней нотации do но я твердо уверен, что она не оправдывает синтаксическую гимнастику.

@samuela относительно map (f, _) vs just map (f). Я согласен с тем, что некоторое волшебное обессахаривание может сбить с толку, но я думаю, что map (f) должна существовать. Это не потребует, и сахар просто добавит простой метод в карту.
например

map(f::Base.Callable) = function(x::Any...) map(f,x...) end

т.е. map принимает функцию, а затем возвращает функцию, которая работает с объектами, которые можно итерировать (более или менее).

В более общем плане я думаю, что нам следует склоняться к функциям, которые имеют дополнительные «удобные» методы, а не к некоему соглашению о том, что |> всегда сопоставляет данные с первым аргументом (или аналогичным).

В том же духе может быть

type Underscore end
_ = Underscore()

и общее соглашение о том, что функции должны / могут иметь методы, которые принимают символы подчеркивания в определенных аргументах, а затем возвращают функции, которые принимают меньше аргументов. Я менее уверен, что это было бы хорошей идеей, так как нужно было бы добавить 2 ^ n методов для каждой функции, которая принимает n аргументов. Но это один подход. Интересно, можно ли было бы не добавлять так много методов явно, а скорее подключаться к поиску метода, чтобы, если какие-либо аргументы имеют тип Underscore, тогда возвращалась соответствующая функция.

В любом случае, я определенно думаю, что иметь версию map и filter, которая просто принимает вызываемый объект и возвращает вызываемый объект, имеет смысл, вещь с Underscore может работать или не работать.

@patrickthebold
Я могу представить, что x |> map(f, _) => x |> map(f, Underscore()) => x |> map(f, x) , как вы предлагаете, будет самым простым способом реализовать map(f, _) , верно? - просто пусть _ будет особой сущностью, для которой вы будете программировать?
Взаимодействие с другими людьми
Хотя я не уверен, будет ли это лучше, чем автоматическое определение Джулией - предположительно с использованием синтаксиса |> - вместо того, чтобы программировать это самостоятельно.

Кроме того, что касается вашего предложения по map - мне оно вроде как нравится. Действительно, для нынешних |> это было бы весьма удобно. Хотя, я полагаю, было бы проще просто реализовать автоматический вывод x |> map(f, _) => x |> map(f, x) ?

@StefanKarpinski Имеет смысл. Не думал об этом так.

Ничего из того, что я сказал, никоим образом не будет связано с |> . В отношении _ я имел в виду, например, добавление методов в < как таковых:

<(_::Underscore, x) = function(z) z < x end
<(x, _::Underscore) = function(z) x < z end

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

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

@patrickthebold Такой подход с определяемым пользователем типом подчеркивания и т. д. возложит на программиста значительную и ненужную нагрузку при реализации функций. Необходимо перечислить все 2 ^ n из

f(_, x, y) = ...
f(x, _, y) = ...
f(_, _, y) = ...
...

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

Кроме того, ваше предложение с map , я полагаю, предоставляет синтаксис обходного пути для map(f) с базовыми функциями, такими как map и filter но в целом он страдает от того же проблема сложности, поскольку подход подчеркивает ручной подход. Например, для func_that_has_a_lot_of_args(a, b, c, d, e) вам придется пройти через изнурительный процесс ввода каждого возможного "каррирования"

func_that_has_a_lot_of_args(a, b, c, d, e) = ...
func_that_has_a_lot_of_args(b, c, d, e) = ...
func_that_has_a_lot_of_args(a, b, e) = ...
func_that_has_a_lot_of_args(b, d, e) = ...
func_that_has_a_lot_of_args(a, d) = ...
...

И даже если бы вы это сделали, вы все равно столкнулись бы с абсурдной степенью двусмысленности при вызове функции: относится ли func_that_has_a_lot_of_args(x, y, z) к определению, где x=a,y=b,z=c или x=b,y=d,z=e и т. Д. ? Джулия могла различать их с помощью информации о типе среды выполнения, но для непрофессионала, читающего исходный код, это было бы совершенно непонятно.

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

@samuela
Не могли бы вы пояснить, что вы имеете в виду под «вытащить, чтобы создать лямбду»? - Мне любопытно. Я тоже задавался вопросом, как это можно реализовать.

@patrickthebold
Ах я вижу. Предположительно, вы могли бы тогда использовать такую ​​вещь, как это: filter(_ < 5, [1:10]) => [1:4] ?
Лично мне кажется, что filter(e -> e < 5, [1:10]) легче читать; более последовательный - менее скрытый смысл, хотя я согласен, он более краток.

Разве у вас есть пример, где он действительно светит?

@samuela

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

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

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

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

@ H-225 да, подчеркивание - это просто синтаксическое удобство. Не уверен, насколько это распространено, но в Scala определенно есть. Лично мне это нравится, но я думаю, что это одна из тех вещей стиля.

@ H-225 Что ж, в этом случае я думаю, что убедительным и уместным примером будет объединение функций. Вместо того, чтобы писать

[1, 2, 3, 5]
  |> x -> map(addone, x)
  |> x -> filter(isprime, x)
  |> sum
  |> x -> 3 * x
  |> ...

можно просто написать

[1, 2, 3, 5]
  |> map(addone, _)
  |> filter(isprime, _)
  |> sum
  |> 3 * _
  |> ...

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

Насколько мне известно, в настоящее время существует не менее 3,5 библиотек / подходов, которые пытаются решить эту проблему в Джулии: встроенная функция Джулии |> , Pipe.jl, Lazy.jl и 0,5 для встроенной функции Джулии do Нотация

@samuela, если вы хотите поиграть с реализацией этой идеи, вы можете попробовать FunctionalData.jl, где ваш пример будет выглядеть так:

<strong i="7">@p</strong> map [1,2,3,4] addone | filter isprime | sum | times 3 _

В последней части показано, как направить ввод во второй параметр (по умолчанию - аргумент один, и в этом случае _ можно опустить). Обратная связь очень ценится!


Изменить: приведенное выше просто переписано на:

times(3, sum(filter(map([1,2,3,4],addone), isprime)))

который использует FunctionalData.map и filter вместо Base.map и filter. Основное отличие - это порядок аргументов, второе отличие - соглашение об индексировании (см. Документацию). В любом случае Base.map можно просто использовать, изменив порядок аргументов в обратном порядке. @p - это довольно простое правило перезаписи (слева направо становится внутренним и внешним, плюс поддержка простого каррирования: <strong i="17">@p</strong> map data add 10 | showall становится

showall(map(data, x->add(x,10)))

Взлом может ввести что-то вроде этого: https://github.com/facebook/hhvm/issues/6455. Они используют $$ которого нет в таблице для Джулии ( $ уже слишком перегружен).

FWIW, мне очень нравится решение Hack для этого.

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

Нельзя было использовать __ ? О каком синтаксисе лямбда вы думаете? _ -> sqrt(_) ?

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

Разве в Mathematica нет аналогичной системы анонимных аргументов? Как делать
они обрабатывают объем ограничения этих аргументов?
Во вторник, 3 ноября 2015 г., в 9:09 Стефан Карпинский [email protected]
написал:

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

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

https://reference.wolfram.com/language/tutorial/PureFunctions.html , показывая
символ # - это то, о чем я думал.
Во вторник, 3 ноября 2015 г., в 9:34 Джонатан Мальмо [email protected] написал:

Разве в Mathematica нет аналогичной системы анонимных аргументов? Как делать
они обрабатывают объем ограничения этих аргументов?
Во вторник, 3 ноября 2015 г., в 9:09 Стефан Карпинский [email protected]
написал:

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

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

Mathematica использует & для его разграничения.

Вместо того, чтобы делать что-то столь же общее, как более короткий синтаксис лямбда (который мог бы принимать произвольное выражение и возвращать анонимную функцию), мы могли бы обойти проблему разделителя, ограничив допустимые выражения вызовами функций, а допустимые переменные / слоты - целыми параметрами. Это дало бы нам очень чистый синтаксис каррирования с несколькими параметрами в стиле Open Dyln . Поскольку _ заменяет целые параметры, синтаксис может быть минимальным, интуитивно понятным и однозначным. map(_ + 2, _) будет переведено в x -> map(y -> y + 2, x) . Большинство выражений вызова, не связанных с функциями, которые вы хотели бы использовать лямбда-фоном, вероятно, в любом случае были бы длиннее и удобнее для -> или do . Я действительно думаю, что компромисс между удобством использования и общностью того стоит.

@durcan , звучит многообещающе - не могли бы вы немного уточнить правило? Почему первый _ остается внутри аргумента map а второй занимает все выражение map ? Я не понимаю, что означает «ограничение допустимых выражений для вызовов функций» и что означает «ограничение допустимых переменных / слотов целыми параметрами» ...

Хорошо, я думаю, что понял правило, прочитав кое-что из этой документации Дилана, но я должен задаться вопросом о том, что map(_ + 2, v) работает, а map(2*_ + 2, v) не работает.

Также есть очень придирчивый бизнес: это означает, что _ + 2 + _ будет означать (x,y) -> x + 2 + y тогда как _ ⊕ 2 ⊕ _ будет означать y -> (x -> x + 2) + y потому что + и * - единственные операторы, которые в настоящее время разбираются как вызовы функций с несколькими аргументами, а не как попарные ассоциативные операции. Теперь можно утверждать, что это несоответствие должно быть исправлено, хотя это, казалось бы, влечет за собой, что _parser_ имеет мнение о том, какие операторы ассоциативны, а какие нет, что кажется плохим. Но я бы сказал, что любая схема, которая требует знания, анализирует ли парсер a + b + c как одиночный вызов функции или как вложенный вызов, может быть несколько сомнительной. Может быть, с инфиксной нотацией нужно обращаться особо? Но нет, это тоже подозрительно.

Да, вы пришли к компромиссу. С одной стороны, длинные строки операторов - это синтаксис, который вызывает наибольшие проблемы с учетом некоторых из наших текущих вариантов синтаксического анализа (хотя трудно ошибиться в семантике языковой функции, которая зависит от текущей семантики языка). С другой стороны, длинные строки вызовов функций - это то, чем он отличается. Например, мы могли бы переписать вашу проблему как:

2*_ |> 2+_ |> map(_, v)

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

_ ⊕ 2 ⊕ _    # y -> (x -> x + 2) + y
_ ⊕ 2 ⊕ _ &  # (y , x) -> x + 2 + y

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

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

Да, мне это нравится, если не считать инфиксов. Эта часть может быть исправлена.

Ну, каррирование в инфиксной позиции могло быть синтаксической ошибкой:

map(+(*(2, _), 2), v)      # curry is OK syntax, but obviously not what you wanted
map(2*_ + 2, v)            # ERROR: syntax: infix curry requires delimitation
map(2*_ + 2 &, v)          # this means what we want
map(*(2,_) |> +(_,2), v)   # as would this

Думаю, это также могло быть предупреждением.

Назвать это каррированием мне кажется непонятным и неправильным.

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

Я думаю примерно так:

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

Примеры:

  • f(_, b)x -> f(x, b)
  • f(a, _)x -> f(a, x)
  • f(_, _)(x, y) -> f(x, y)
  • 2_^2x -> 2x^2
  • 2_^_(x, y) -> 2x^y
  • map(_ + 2, v)map(x -> x + 2, v)
  • map(2_ + 2, v)map(x -> 2x + 2, v)
  • map(abs, _)x -> map(abs, x)
  • map(2_ + 2, _)x -> map(y -> 2y + 2, x)
  • map(2_ - _, v, w)map((x, y) -> 2x - y, v, w)
  • map(2_ - _, v, _)x -> map((y, z) -> 2y - z, v, x)
  • map(2_ - _, _, _)(x, y) -> map((z, w) -> 2z - w, x, y)
  • _x -> x
  • map(_, v)x -> map(x, v)
  • map((_), v)map(x -> x, v)
  • f = _f = x -> x
  • f = 2_f = x -> 2x
  • x -> x^_x -> y -> x^y
  • _ && _(x, y) -> x && y
  • !_ && _(x, y) -> !x && y

Единственное, где это может стать рискованным, - это условные выражения - эти примеры становятся немного странными.

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

Это кажется мне действительно плохой идеей, почти весь синтаксис в Julia хорошо знаком, если вы использовали другие языки программирования. Люди, которые смотрят на синтаксический сахар подобным образом, понятия не имеют, что принесет «выгода» сохранения пары символов.

Примеры, которые делают меня немного менее счастливым:

  • 2v[_]x -> 2v[x] (хорошо)
  • 2f(_)2*(x -> f(x)) (не очень хорошо)
  • _ ? "true" : "false"(x -> x) ? "true" : "false"
  • _ ? _ : 0(x -> x) ? (y -> y) : 0

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

Возможно, но тот же аргумент мог быть (и был) выдвинут относительно синтаксиса do-block, который, я думаю, в целом оказался очень успешным и полезным. Это тесно связано с улучшением синтаксиса векторизации. Это также не так уж и беспрецедентно - Scala использует _ аналогичным образом, а Mathematica использует # аналогичным образом.

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

Во вторник, 17 ноября 2015 г., в 12:09 Стефан Карпинский [email protected]
написал:

Возможно, но тот же аргумент мог (и был) выдвинут в отношении do-block
синтаксис, который, как мне кажется, в целом оказался очень успешным и полезным.
Это тесно связано с улучшением синтаксиса векторизации. Это тоже не
это беспрецедентно - Scala использует _ аналогичным образом, а Mathematica использует #
так же.

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

Это также существует в C ++ с несколькими библиотечными решениями, в частности, в Boost, которые используют _1, _2, _3 качестве аргументов (например, _1(x, y...) = x , _2(x, y, z...) = y т. Д.), Ограничение заключается в возможности вызов, например, fun(_1) для x -> fun(x) , fun должен быть явно совместим с библиотекой (обычно через вызов макроса, чтобы fun принимал "лямбда-тип" "в качестве параметра).

Мне бы очень хотелось, чтобы эта краткая лямбда-запись была доступна в Julia.
Что касается проблемы десугарирования 2f(_) до 2*(x -> f(x)) : имеет ли смысл изменить правила в соответствии со строками «если применяется первое правило, например, f(_) , затем повторно рекурсивно оценивать правила с f(_) играющим роль _ . Это также позволило бы, например, f(g(_))x -> f(g(x)) , с "правилом скобок", позволяющим легко остановитесь на желаемом уровне, например, f((g(_)))f(x->g(x)) .

Мне очень нравится название «краткая лямбда-нотация» для этого. (Намного лучше, чем карри).

Я бы действительно предпочел явность _1 , _2 , _3 если вы передаете лямбды с несколькими аргументами. В общем, я часто нахожу, что повторное использование имен переменных в одной и той же области может сбивать с толку ... а наличие _ be x и y в _одном выражении_ просто сбивает с толку.

Я обнаружил, что тот же краткий scala _ -синтаксис вызвал небольшую путаницу (см. Все варианты использования _ в scala ).

Кроме того, часто хочется сделать:

x -> f(x) + g(x)

или подобное, и я думаю, я был бы удивлен, если бы следующее не сработало:

f(_) + g(_)

Также вы можете изменить порядок аргументов:

x, y -> f(y, x)
f(_2, _1)  # can't do with other suggested _ syntax

Я думаю, было бы хорошо, если бы синтаксис допускал явную нумерацию анонимных аргументов ( _1 , _2 , _3 ... и т. Д.), Но основная проблема все еще остается : когда именно вы продвигаете частично примененную функцию в краткую лямбду? А что такое тело лямбды? Я бы, вероятно, ошибся в том, чтобы быть явным (с разделителем), а не неявно использовать какие-то сложные правила продвижения. Что должно

foo(_1, _1 + _2  + f(_1, v1) + g(_2, v3), _3 * _2, v2) + g(_4, v4) +
 f(_2, v2) + g(_3, v5) + bar(_1, v6)

имею в виду именно? Использование разделителя (я буду использовать λ ) несколько яснее:

λ(foo(_1, λ(_1 + _2)  + λ(f(_1, v1) + g(_2, v3)), _3 * _2, v2) + g(_4, v4)) + 
λ(f(_2, v2) + g(_3, v5) + bar(_1, v6))

Очевидно, это MethodError: + has no method matching +(::Function, ::Function) , но, по крайней мере, я могу сказать это по тому, как это написано.

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

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

Может быть, недостаточно кратко, но как насчет префиксной версии -> которая фиксирует _ аргументы? Например

(-> 2f(_) + 1)

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

map(->_ + 1, x)

Прямо сейчас я возился с реализацией https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665

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

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

Странный, который выходит из https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665

  • f(_,_)x,y -> f(x,y) (это разумно)
  • f(_,2_) → ??

    • f(_,2_)x,y -> f(x,2y) (разумно)

    • f(_,2_)x-> f(x,y->2y) (что, по моему мнению, предлагает правило, и что производит мой прототип)

Но я не уверен, что прав.

Итак, вот мой прототип.
http://nbviewer.ipython.org/gist/oxinabox/50a1e17cfb232a7d1908

На самом деле он определенно не проходит некоторые тесты.

Невозможно учесть брекетинг на текущем уровне AST - они часто (всегда?) Уже разрешены.

Тем не менее, я думаю, этого достаточно, чтобы поиграть

Некоторые правила от magrittr в R, которые могут быть полезны:

Если цепочка начинается с. , это анонимная функция:

. %>% `+`(1)

совпадает с функцией (x) x + 1

Есть два режима связывания:
1) Цепочка путем вставки в качестве первого аргумента, а также для любых появляющихся точек.
2) Привязка только к появившимся точкам.

Режим по умолчанию - режим 1. Однако, если точка появляется сама по себе в качестве аргумента связываемой функции, то magrittr переключается в режим 2 для этого шага в цепочке.

Так

2 %>% `-`(1) 

2 - 1,

и

1 %>% `-`(2, . )

также 2 - 1

Режим 2 также можно указать, заключив в скобки:

2 %>% { `-`(2, . - 1) }

будет таким же, как 2 - (2 - 1).

Также просто примечание, что возможность разумного переключения между режимом 1 и режимом 2 почти полностью решает проблему, из-за которой Джулия не очень последовательна в том, чтобы иметь аргумент, который, вероятно, будет привязан к первой позиции. Я также забыл отметить, что скобки позволяют оценивать кусок кода. Вот пример из руководства magrittr:

ирис%>%
{
n <- образец (1:10, размер = 1)
H <- голова (., N)
T <- хвост (., N)
rbind (H, T)
}%>%
резюме

На данный момент это только наполовину сформированная идея, но мне интересно, есть ли способ, которым мы могли бы решить проблемы «краткой лямбды» и «фиктивной переменной» одновременно, изменив конструктор Tuple таким образом, чтобы отсутствующее значение возвращало лямбда, которая возвращает кортеж вместо кортежа? Итак, (_, 'b', _, 4) вернет (x, y) -> (x, 'b', y, 4) .

Затем, если мы тонко изменим семантику вызова функции так, что foo(a, b) означает «применить foo к кортежу (a, b) или, если аргумент является функцией, применить foo в кортеж, возвращаемый функцией ". Это сделает foo(_, b, c)(1) эквивалентным apply(foo, ((x) -> (x, b, c))(1)) .

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

@jballanc Однако построение кортежей и применение функций - это две совершенно разные концепции. По крайней мере, если мое понимание семантики julia не имеет серьезных недостатков.

@samuela Я имел в виду, что foo(a, b) эквивалентно foo((a, b)...) . То есть аргументы функции можно концептуально представить как кортеж, даже если кортеж никогда не создается на практике.

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

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

3 |> foo == foo(3) # or foo() instead of just foo, but it would be nice if the parentheses were optional
3 |> foo(1) == foo(1, 3)
3 |> foo(1,2) == foo(1,2,3)

Другими словами, a |> f(x) делает с _last_ аргументом то же, что f(x) do; a; end делает с _first_. Это сразу сделает его совместимым с map , filter , all , any et. и др., не добавляя сложности определения параметров _ и учитывая уже существующий синтаксис do я не думаю, что это создает необоснованное концептуальное бремя для читателей кода.

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

Это макрос @>> из https://github.com/MikeInnes/Lazy.jl.

@malmaud Красиво ! Мне нравится, что это уже возможно: D

Однако разница в удобочитаемости между этими двумя вариантами действительно велика:

# from Lazy.jl
@> x g f(y, z)

# if this became a first-class feature of |>
x |> g |> f(y, z)

Я думаю, что основная проблема читабельности заключается в том, что нет визуальных подсказок, позволяющих определить границы между выражениями - пробелы в x g и g f(x, значительно повлияют на поведение кода, но пробелы в f(x, y) не будет.

Поскольку @>> уже существует, насколько возможно добавить это поведение к |> в 0.5?

(Я не хочу тратить слишком много энергии на обозначение оператора, поэтому не отклоняйте это предложение исключительно из-за обозначения. Однако мы можем отметить, что «трубопровод» кажется естественным понятием для этого (см. термин "конвейер сбора"), и что, например, F # уже использует для этого |> , хотя, конечно, он имеет немного другую семантику, поскольку функции F # отличаются от функций Julia.)

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

Точно так же можно было бы связать функции макро-анализа @>> Lazy.jl с |> .

Хм. Тогда я начну работать над PR для Lazy.jl, но это не значит, что я бы не хотел, чтобы это было в 0.5 :) Я не думаю, что знаю достаточно о парсере Julia и как изменить поведение |> чтобы помочь с этим, если я не получу достаточно обширного наставничества.

Не думаю, что я упоминал в этой теме, но у меня есть еще один пакет цепочки, ChainMap.jl. Он всегда заменяется на _ и условно вставляется в первый аргумент. Он также пытается интегрировать отображение. См. Https://github.com/bramtayl/ChainMap.jl

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

Пакеты

Прототипы без упаковки

Связанный:


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

дата обновления: 2020-04-20

Это скорее эксперимент с системой типов, чем реальная попытка реализовать частичное приложение, но вот странный: https://gist.github.com/fcard/b48513108a32c13a49a387a3c530f7de

Применение:

include("partial_underscore_generated.jl")
using GeneratedPartial

const sub = partialize(-)
sub(_,2)(1) == 1-2
sub(_,_)(1,2) == 1-2
sub(_,__)(1)(2) == 1-2
sub(__,_)(2)(1) == 1-2 #hehehe

# or
<strong i="8">@partialize</strong> 2 Base.:+ # evily inserts methods in + and allows partializations for 2 arguments
(_+2)(1) == 1+2

# fun:
sub(1+_,_)(2,3) == sub(1+2,3)
sub(1+_,__)(2)(3) == sub(1+2,3)
(_(1)+_)(-,1) == -1+1

# lotsafun:
appf(x::Int,y::Int) = x*y
appf(f,x) = f(x)
<strong i="9">@partialize</strong> 2 appf

appf(1+_,3)(2) == appf(1+2,3)
appf(?(1+_),3) == appf(x->(1+x), 3)
appf(?sub(_,2),3) == appf(x->x-2,3) # I made a method *(::typeof(?),::PartialCall), what of it!!?

# wooooooooooooooooooooooooooooooooo
const f = sub
f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,_)))))))))(1,2,3,4,5,6,7,8,9,10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))
f(_,f(__,f(___,f(____,f(_____,f(______,f(_______,f(________,f(_________,__________)))))))))(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))

# this answers Stefan's concern (which inspired me to make this hack in the first place)
#
#    const pmap = partialize(map)
#    map(f(_,a),   v) == map(x->f(x,a), v)
#    pmap(?f(_,a), v) == map(x->f(x,a), v)
#    pmap(f(_,a),  v) == x->map(f(x,a), v)
#
# it adds a few other issues, of course...


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

PS Забыл упомянуть, что @partialize также работает с литералами и диапазонами целочисленных массивов:

<strong i="15">@partialize</strong> 2:3 Base.:- # partialized for 2 and 3 arguments!

(_-_-_)(1,2,3) == -4
(_-_+_)(1,2,3) == +2

Хорошо, я думал о композиции функций, и хотя ИМО это ясно в случае SISO, я думаю, я думал о том, как использовать MISO Джулии (квази-MIMO?) В небольших связанных блоках кода.

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

Он также возвращает / определяет ans после каждого выражения. Это знает каждый пользователь MATLAB (хотя на данном этапе это плохой аргумент!). Вероятно, большинство пользователей Julia видели / использовали его раньше. Я использую ans в странных ситуациях, когда я играю с чем-то по частям, понимая, что хочу добавить еще один шаг к тому, что я написал выше. Мне не нравится, что его использование деструктивно, поэтому я стараюсь избегать его, когда это возможно, но _все_ предложения здесь имеют дело с возвращением времени жизни только одного шага композиции.

Для меня _ быть волшебным - это просто _odd_, но я понимаю, что многие люди могут не согласиться. Итак, если я _ хочу_ скопировать и вставить код из пакетов в REPL и посмотреть, как он работает, и если мне нужен синтаксис, который не кажется волшебным, то я мог бы предложить:

<strong i="12">@repl_compose</strong> begin
   sin(x)
   ans + 1
   sqrt(ans)
end

Если функция возвращает несколько выходов, я могу вставить ans[1] , ans[2] и т. Д. В следующую строку. Он уже точно соответствует одноуровневой модели композиции, модели MISO Джулии, и это уже _ очень стандартный синтаксис Джулии_, только не в файлах.

Макрос легко реализовать - просто преобразуйте Expr(:block, exprs...) в Expr(:block, map(expr -> :(ans = $expr), exprs) (также let ans в начале, и, возможно, может быть версия, которая делает анонимную функцию, которая принимает ввод что ли?). Ему не нужно было бы жить в базе (хотя REPL встроен в Julia, и это вроде как с этим связано).

Во всяком случае, только моя точка зрения! Это была длинная тема, на которую я давно не смотрел!

Он также возвращает / определяет ans после каждого выражения. Это знает каждый пользователь MATLAB (хотя на данном этапе это плохой аргумент!).

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

Существует немало прецедентов использования _ в качестве «его значения» в языках. Конечно, это противоречит предложенной идее использования _ в качестве имени, которое отбрасывает присваивания и более сжатые лямбды.

Я почти уверен, что это существует где-то в Lazy.jl как <strong i="5">@as</strong> and begin ...

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

type Track
  hit::Array{Hit}
end
type Event
  track::Array{Track}
end

event.track[12].hit[43]

дает мне 43-е попадание 12-го трека события, когда track и hit - простые массивы, поэтому

event.getTrack(12).getHit(43)

должен дать мне то же самое, если их нужно обслуживать динамически. Я не хочу говорить

getHit(getTrack(event, 12), 43)

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

Я пишу это сейчас, потому что только что узнал о чертах Rust , которые могут быть хорошим решением в Julia по тем же причинам. Как и Джулия, в Rust есть только данные structs (Julia type ), но также есть impl для привязки функций к имени struct . Насколько я могу судить, это чистый синтаксический сахар, но он позволяет использовать точечную нотацию, которую я описал выше:

impl Event {
  fn getTrack(&self, num: i32) -> Track {
    self.track[num]
  }
}

impl Track {
  fn getHit(&self, num: i32) -> Track {
    self.track[num]
  }
}

который в Юлии мог быть

impl Event
  function getTrack(self::Event, num::Int)
    self.track[num]
  end
end

impl Track
  function getHit(self::Track, num::Int)
    self.hit[num]
  end
end

Предложенный выше синтаксис не интерпретирует self : это просто аргумент функции, поэтому конфликтов с множественной отправкой быть не должно. Если вы хотите выполнить минимальную интерпретацию self , вы можете сделать тип первого аргумента неявным, чтобы пользователю не приходилось вводить ::Event и ::Track в каждой функции, но хорошая вещь в том, чтобы не выполнять никакой интерпретации, заключается в том, что «статические методы» - это просто функции в impl , у которых нет self . (Rust использует их для new фабрик.)

В отличие от Rust, у Джулии иерархия types . Он также может иметь аналогичную иерархию на impls чтобы избежать дублирования кода. Стандартное ООП можно построить, сделав иерархии данных type и метода impl точности одинаковыми, но это строгое зеркальное отображение не является необходимым и в некоторых случаях нежелательно.

Есть одна неприятная проблема: предположим, что я назвал свои функции track и hit в impl , а не getTrack и getHit , так что они конфликтовали с массивами track и hit в type . Тогда event.track вернет массив или функцию? Если вы сразу используете его как функцию, это может помочь устранить неоднозначность, но types может содержать объекты Function . Может быть, просто примените общее правило: после точки сначала отметьте соответствующий impl , а затем отметьте соответствующий type ?

Если подумать, как насчет того, чтобы избежать двух "пакетов" для концептуально одного и того же объекта ( type и impl ), как насчет этого:

function Event.getTrack(self, num::Int)
  self.track[num]
end

чтобы связать функцию getTrack с экземплярами типа Event , чтобы

myEvent.getTrack(12)

дает тот же байт-код, что и функция, примененная к (myEvent, 12) ?

Новым является синтаксис typename-dot-functionname после ключевого слова function и то, как он интерпретируется. Это по-прежнему допускает множественную отправку, Python-подобный self если первый аргумент совпадает с типом, к которому он привязан (или оставлен неявным, как указано выше), и позволяет использовать «статический метод», если первый аргумент отсутствует или типизирован иначе, чем тип, к которому он привязан.

@jpivarski Есть ли причина, по которой вы думаете, что точечный синтаксис (который, читая эту ветку, имеет много недостатков) лучше, чем какая-либо другая конструкция, позволяющая создавать цепочки? Я все еще думаю, что создание чего-то вроде do но для последнего аргумента , поддерживаемого какой-либо формой синтаксиса конвейера (например, |> ), было бы лучшим способом продвижения вперед:

event |> getTrack(12) |> getHit(43)

Основная причина, по которой я вижу, что что-то вроде подхода Rust может быть лучше, заключается в том, что он эффективно использует левую часть в качестве пространства имен для функций, поэтому вы можете делать такие вещи, как parser.parse не вступая в конфликт с существующими Юлия Base.parse function. Я был бы за предоставление и предложения Rust, и пайпинга в стиле Hack.

@tlycken Это неоднозначный синтаксис, в зависимости от приоритета.
Воспоминание о точности вызова |> vs может сбивать с толку, поскольку на самом деле он не дает никаких подсказок.
(Также не предлагаются некоторые другие варианты.)

Рассматривать

foo(a,b) = a+b
foo(a) = b -> a-b

2 |> foo(10) == 12   #Pipe Precedence > Call Precedence 
2 |> foo(10) == 8     #Pipe Precedence < Call Precedence   

@oxinabox Я на самом деле не предлагаю, чтобы это был "просто" обычный оператор, а скорее как элемент синтаксиса языка; 2 |> foo(10) десахаров в foo(10, 2) почти так же, как foo(10) do x; bar(x); end десахаров в foo(x -> bar(x), 10) . Это подразумевает приоритет канала над приоритетом вызовов (что, я думаю, в любом случае имеет наибольший смысл).

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

Правильно ли я читаю этот поток, что аргумент против точки состоит в том, что он неоднозначен, должен ли он получать данные члена из type или функцию-член из impl (мое первое предложение) или функции пространство имен (мое второе предложение)? Во втором предложении функции, определенные в пространстве имен функций, созданном type должны быть определены _после__определения type , поэтому он может отказаться перекрывать элемент данных прямо здесь.

Добавление обеих точек пространства имен (мое второе предложение) и |> меня устроит; они довольно разные по назначению и эффекту, несмотря на то, что их можно использовать для плавного связывания. Однако |> как описано выше, не полностью симметрично do , поскольку do требует, чтобы аргумент, который он заполняет, был функцией. Если вы говорите event |> getTrack(12) |> getHit(43) , то |> применяется к нефункциям ( Events и Tracks ).

Если вы говорите event |> getTrack(12) |> getHit(43) , то |> применяется к нефункциям ( Events и Tracks ).

На самом деле нет - он применяется к заклинаниям функции _права_, вставляя ее левый операнд в качестве последнего аргумента при вызове функции. event |> getTrack(12) - это getTrack(12, event) из-за того, что было справа, а не из-за того, что было слева.

Это должно означать а) приоритет над вызовами функций (поскольку это переписывание вызова) и б) порядок приложения слева направо (чтобы сделать это getHit(43, getTrack(12, event)) а не getHit(43, getTrack(12), event) ) .

Но подпись getTrack's

function getTrack(num::Int, event::Event)

поэтому, если event |> getTrack(12) вставляет event в последний аргумент getTrack's , он помещает Event во второй аргумент, а не Function . Я просто попробовал эквивалент с do и первым аргументом, а Джулия 0.4 пожаловалась, что аргумент должен быть функцией. (Возможно, потому что do event end интерпретируется как функция, возвращающая event , а не сам event .)

Мне кажется, что построение цепочки функций - это отдельный вопрос от того, что обсуждается вокруг синтаксиса точки ( . ). Например, @jpivarski , вы уже можете выполнить многое из того, что упомянули в Rust в Julia, без каких-либо новых функций:

type TownCrier
  name::AbstractString
  shout::Function

  function TownCrier(name::AbstractString)
    self = new(name)
    self.shout = () -> "HELLO, $(self.name)!"
    self
  end
end

tc = TownCrier("Josh")
tc.shout()                                #=> "HELLO, Josh!"
tc.name = "Bob"
tc.shout()                                #=> "HELLO, Bob!"

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

@andyferris Я использовал Python, и мне очень нравится _, ссылаясь на результат предыдущего выражения. Но внутри функций он не работает. Было бы здорово, если бы мы могли заставить его работать где угодно: внутри блоков begin, функций и т. Д.

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

begin
    1
    vcat(_, 2)
    vcat(3, _)
end

# [3, 1, 2]

Как упоминал @MikeInnes , это уже доступно в @_ в Lazy.jl (и хотя изначально это не сработало, ChainMap.jl теперь также использует такой вид цепочки).

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

begin
    [1, 2, 3]
    .+(_, 2)
    .*(_, 2)
    .-(10, _)
end

или, используя синтаксис @chain_map ,

begin
    ~[1, 2, 3]
    +(_, 2)
    *(_, 2)
    -(10, _)
end

В настоящее время существует способ объединения функций с объектами, если функция определена внутри конструктора. Например, функция Obj.times:

type Obj
    x
    times::Function
    function Obj(x)
       this = new(x)
       this.times =  (n) -> (this.x *= n; this)
       this
    end
end

>>>Obj(2).times(3)
Obj(6,#3)

Что насчет реализации функций-членов (особенно функций), определенных вне определения типа. Например, функция Obj.times будет записана так:

member function times(this::Obj, n)
     this.x *= n
     return this
end

>>>Obj(2).times(3)
Obj(6,#3)

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

Примерно так сделано в Rust с синтаксисом метода . Он концептуально отличается от объединения функций, хотя его можно использовать для создания цепочки, похожей на некоторые языки объектно-ориентированного программирования. Наверное, лучше рассмотреть вопросы отдельно.

Я прочитал этот и некоторые другие вопросы, вот мое предложение:

Базовая цепочка :
in1 |> function1
То же, что: in1 |> function1(|>)

in2 |> function2(10)
То же, что: in2 |> function2(|>,10)

Еще больше цепочек:
in1 |> function1 |> function2(10,|>)

Разветвление и слияние цепочек:
Дважды ветвление с ветвями out1 , out2 :
function1(a) |out1>
function2(a,b) |out2>

Используйте ветку out1 и out2 :
function3(|out1>,|out2>)

А как насчет лени?
Нам нужно что-то вроде соглашения function!(mutating_var) ?
Для ленивых функций можно использовать function?() ...

А при правильном использовании отступов легко визуально отслеживать зависимости данных на связанном графе вызовов.

Я просто поигрался с шаблоном для объединения функций с существующим оператором |> . Например, эти определения:
Джулия

Фильтр

неизменяемый MyFilter {F}
flt :: F
конец

функция (mf :: MyFilter) (источник)
фильтр (mf.flt, источник)
конец

функция Base.filter (flt)
MyFilter (flt)
конец

Взять

неизменный MyTake
n :: Int64
конец

функция (mt :: MyTake) (источник)
взять (источник, mt.n)
конец

функция Base.take (n)
MyTake (сущ.)
конец

карта

неизменяемая MyMap {F}
f :: F
конец

функция (мм :: MyMap) (источник)
карта (mm.f, источник)
конец

функция Base.map (f)
Моя карта (f)
конец
enable this to work: джулия
1:10 |> filter (i-> i% 2 == 0) |> take (2) |> map (i-> i ^ 2) |> собрать
`` Essentially the idea is that functions like filter return a functor if they are called without a source argument, and then these functors all take one argument, namely whatever is "coming" from the left side of the |> . The |> `` затем просто связывает все эти функторы вместе.

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

В моем примере я перезаписываю map(f::Any) в Base, я действительно не понимаю, что делает существующее определение map ...

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

Это не кажется работоспособным для произвольных функций, только для тех, для которых был определен MyF?

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

По сути, идея заключается в том, что такие функции, как filter, возвращают функтор, если они вызываются без аргумента источника, а затем все эти функторы принимают один аргумент, а именно то, что «приходит» из левой части |>.

В этом, по сути, и заключается суть преобразователей Clojure. Заметное отличие состоит в том, что Clojure построил преобразователи на основе концепции редукторов. Короче говоря, каждая функция, которая работает с коллекцией, может быть разложена на функцию «сопоставления» и функцию «сокращения» (даже если функция «сокращения» - это просто concat ). Преимущество представления функций сбора таким образом состоит в том, что вы можете переупорядочить выполнение так, чтобы все «отображение» могло быть конвейерным (особенно удобно для больших коллекций). Таким образом, преобразователи - это просто извлечение этих «сопоставлений», возвращаемых при вызове без набора для работы.

Не нужно, чтобы это было так сложно. Функции могут отказаться от каррирования с закрытием:

Base.map(f)    = (xs...) -> map(f, xs...)
Base.filter(f) = x -> filter(f, x)
Base.take(n)   = x -> take(x, n)

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

Я бы предпочел синтаксическое решение call-site, подобное рассмотренному выше, с понижением f(a, _, b) до x -> f(a, x, b) . Однако это сложно, как отмечалось в длинном обсуждении выше.

Не нужно, чтобы это было так сложно. Функции могут отказаться от каррирования с закрытием

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

какие аргументы должны иметь приоритет?

Да, и тогда у нас есть такие вещи, как filter и take , где в одном случае у нас есть коллекция в качестве первого аргумента, а в другом - в качестве последнего аргумента ... Мне кажется, что по крайней мере, для операций, подобных итератору, обычно может быть очевидный ответ на этот вопрос.

Как только _ становится доступным специальным символом

Да, я полностью согласен с тем, что существует более общее решение, и @malmaud может быть им.

Здесь нет perf diff, поскольку замыкания по сути просто генерируют код, который вы написали вручную. Но поскольку вы просто выполняете каррирование, вы можете написать функцию, которая сделает это за вас ( curry(f, as...) = (bs...) -> f(as..., bs...) ). Это касается карты и фильтра; в прошлом также были предложения по реализации curry который реализует контрольное значение, например curry(take, _, 2) .

Я приехала сюда, потому что сейчас изучаю три языка: D, Эликсир и теперь Юля. В D есть единый синтаксис вызова функций , как и в Rust, в Elixir есть оператор канала . Оба в основном реализуют предложенную здесь цепочку функций, и мне очень понравилась эта функция на обоих языках, поскольку ее легко понять, легко реализовать и можно сделать код, использующий потоки и другие типы конвейеров данных, намного более читаемым.

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

Привет народ,

Пожалуйста, позвольте мне добавить +1 к этому запросу функции. Это очень нужно. Рассмотрим следующий синтаксис Scala.

Array(1,2,3,4,5)
  .map(x => x+1)
  .filter(x => x > 5)
  .reduce(_ + _)

Люди, которые использовали Spark или другие инструменты для работы с большими данными на основе MapReduce, будут хорошо знакомы с этим синтаксисом и будут писать таким образом большие и сложные задания. Даже сравнительно древний R допускает следующее.

c(1,2,3,4,5) %>%
  {. + 1} %>%
  {.[which(. > 5)]} %>%
  sum

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

[1,2,3,4,5] |> 
  _ -> map(__ -> __ + 1, _) |>
  _ -> filter(__ -> __ < 5, _) |>
  _ -> reduce(+, _)

Но это ужасно и некрасиво. Если у нас не может быть объектно-ориентированного кода а-ля Scala, операторы каналов становятся невероятно важными. Самым простым решением является подача канала в _первый аргумент_, если явно не используется подстановочный знак, такой как _, но это будет иметь смысл только в том случае, если map были изменены, чтобы принять структуру данных в качестве первого аргумента и функции как второй.

Также должен быть какой-то эквивалент Scala Array(1,2,3).map(_ + 1) чтобы избежать чрезмерного _ -> _ + 1 и подобного синтаксиса. Мне нравится идея выше, где [1,2,3] |> map(~ + 1, _) переводится в map(~ -> ~ + 1, [1,2,3]) . Спасибо, что посмотрели.

Для последнего у нас есть трансляция с компактным синтаксисом [1, 2, 3] .+ 1 Это довольно затягивает. Что-то подобное для сокращения (и, возможно, фильтрации) было бы безумно круто, но кажется большим вопросом.

Разумно отметить, что и конвейер, и do борются за первый аргумент функции.

Напомню, новая ветка приходит, что у нас,
не один, не два, а ПЯТЬ пакетов, обеспечивающих расширения базовых функций трубопроводов SISO julia в соответствии с предложенным синтаксисом.
см. список по адресу: https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539

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

Если бы мы должны были получить дополнительную функциональность трубопроводов в базе, это не было бы позиции, отмеченной _ и т. Д.
Тогда я бы подумал, что это добавит аргументы в последнюю позицию, а не в первую.
это сделало бы его больше похожим на "притворное каррирование / частичное приложение"

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

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

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

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

Это должно работать в Juno and Julia 0.6

`` {julia}
используя LazyCall
@lazy_call_module_methods Базовый генератор
@lazy_call_module_methods Фильтр итератора

используя ChainRecursive
start_chaining ()


```{julia}
[1, 2, 3, 4, 5]
<strong i="14">@unweave</strong> ~it + 1
Base.Generator(it)
<strong i="15">@unweave</strong> ~it < 5
Iterators.filter(it)
reduce(+, it)

У меня есть вопрос относительно синтаксиса, который я видел в комментариях к этой проблеме:
https://stackoverflow.com/questions/44520097/method-chaining-in-julia

@somedadaism , проблемы предназначены для проблем, а не для "рекламы" вопросов о переполнении стека. Также Юля-люди очень активны на SO и (тем более) на https://discourse.julialang.org/. Я был бы очень удивлен, если бы вы не получили там ответ на большинство вопросов очень быстро. И добро пожаловать к Юлии!

Боже, невероятно, насколько это может быть сложно oO. +1 за приличный синтаксис. Для меня конвейерная система в основном используется для работы с данными (фреймами). Думаю, dplyr. Лично меня не волнует передача по умолчанию first / last, но я предполагаю, что у большинства разработчиков пакетов их функции будут принимать данные в качестве первого аргумента - а как насчет дополнительных аргументов? +1 за что-то вроде

1 |> sin |> sum(2, _)

Как уже упоминалось ранее, удобство чтения и простота очень важны. Я бы не хотел пропустить весь стиль dplyr / tidyverse для анализа данных ...

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

1
|> sin
|> sum(2)
|> println

Эквивалент println(sum(sin(1),2))

Просто чтобы отметить предложение в мире javascript . Они используют оператор ? вместо _ или ~ которые мы уже имеем в виду ( _ чтобы что-то игнорировать, и ~ как побитовое нет или формула). Учитывая, что в настоящее время мы используем ? же, как javascript, мы могли бы использовать его и для заполнителя каррирования.

так выглядит это предложение (оно написано на javascript, но действует и в julia :)

const addOne = add(1, ?); // apply from the left
addOne(2); // 3

const addTen = add(?, 10); // apply from the right
addTen(2); // 12

// with pipeline
let newScore = player.score
  |> add(7, ?)
  |> clamp(0, 100, ?); // shallow stack, the pipe to `clamp` is the same frame as the pipe to `add`.

const maxGreaterThanZero = Math.max(0, ...);
maxGreaterThanZero(1, 2); // 2
maxGreaterThanZero(-1, -2); // 0

Резюме, потому что я начал писать по другим причинам.
См. Также мой предыдущий комментарий к списку связанных пакетов .

Любое вмешательство в _ является нарушением и может быть выполнено в 1.x, потому что https://github.com/JuliaLang/julia/pull/20328

Все сводится к двум основным вариантам (помимо статус-кво).
Оба могут (для всех намерений и целей) быть реализованы с помощью макроса для переписывания синтаксиса.

Возиться с _ для создания анонимных функций

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

  • с этим почти можно справиться с помощью макроса see .

    • Единственное, что сделать нельзя, - это то, что (_) не совпадает с _ . Это просто функция идентификации, поэтому на самом деле это не имеет значения

    • Это применимо везде, поэтому было бы полезно не только с |> , но также, например, для компактного написания таких вещей, как map(log(7,_), xs) или log(7, _).(xs) чтобы взять журнал с базой 7 каждого элемента. из xs .

    • Я лично поддерживаю это, если мы что-то делаем.

Возиться с |> чтобы заставить его выполнять замены

Варианты включают:

  • Сделайте так, чтобы его RHS действовал так, как будто они карри

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

  • Сделайте так, чтобы _ действовал особенным (см. Параметры выше и / или различные способы подделки с помощью перезаписи)

    • один из способов сделать это - разрешить создание инфиксных макросов, тогда можно написать @|>@ и определить его, как вы хотите, в пакетах (это уже было закрыто однажды https://github.com/JuliaLang/julia/ вопросов / 11608)

    • или присвойте ему эти особые свойства по сути

  • Для этого у нас есть множество реализаций макросов, как я уже сказал, см. Мой список связанных пакетов
  • Некоторые люди также предлагают изменить его, чтобы он (в отличие от всех других операторов) мог вызывать выражение в строке, прежде чем оно произойдет. ТАК ты можешь написать
a
|> f
|>g

Вместо нынешних:

a |>
f |>
g

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

  • Мне лично не нравятся эти предложения, так как они делают |> (уже нелюбимый оператор) супер-магией.

Изменить : как @StefanKarpinski указывает ниже , это всегда фактически нарушает изменение.
Потому что кто-то может зависеть от typeof(|>) <: Function .
И эти изменения сделают его элементом синтаксиса языка.

Бонусный вариант: такого никогда не бывает вариант: везде добавить карри # 554

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

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

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

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

Единственный критический случай в этом списке - это когда |> заставляет свою правую часть притворяться каррированием.
Либо вставить свои аргументы в первую, либо в последнюю позицию последнего аргумента (/ s), либо в другую фиксированную позицию (вторая может иметь смысл).
Без использования _ в качестве маркера, в который нужно вставить аргумент.

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

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

Другая идея может заключаться в том, чтобы сохранить |> с текущим поведением и ввести новые функции под другим именем, которое не требует использования клавиши Shift, например \\ (что не даже разобрать как оператор прямо сейчас). Однажды мы говорили об этом в Slack, но я думаю, что история, вероятно, потеряна в песках времени.

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

Одинарный символ | тоже может быть неплохим.

В интерактивном режиме да, но тогда достаточно, чтобы он был в .juliarc.jl (который у меня был уже давно ;-p)

не требует использования клавиши Shift

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

Есть ли традиция использовать для этой цели |> ? [Mathematica] (http://reference.wolfram.com/language/guide/Syntax.html) имеет // для приложения постфиксной функции, которое должно быть легко набирать на большинстве клавиатур и может быть доступно, если оно еще не используется для комментариев (как в C ++) или целочисленного деления (как в Python).

Что-то с | в нем имеет хорошее соединение со сценариями оболочки, хотя, конечно, можно ожидать, что один | будет побитовым ИЛИ. Используется ли || для логического ИЛИ? А как насчет ||| ? Набрать труднодоступный символ три раза не намного сложнее, чем один раз.

Есть ли традиция использовать |> для этой цели?

Я считаю, что традиция |> происходит от семейства языков ML. Что касается операторов, немногие сообщества языков программирования исследовали это пространство, как сообщество ML / Haskell. Небольшая подборка примеров:

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

x %>% { if(. < 5) { a(.) } else { b(.) } }

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

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

Несмотря на то, что предлагаемые варианты использования |> синтаксически не несовместимы с текущим типичным использованием, они _are_ несовместимы с оператором |> , поскольку большинство из них предполагает предоставление |> гораздо большего мощность, чем простая инфиксная функция. Даже если мы уверены, что хотим сохранить x |> f |> g в значении g(f(x)) , оставление этого обычного оператора, вероятно, предотвратит любые дальнейшие улучшения. Хотя изменение |> на неоператор, которое выполняет приложение постфиксной функции, может не нарушить его _типичное_ использование для приложения связанных функций, это все равно будет недопустимо, так как это нарушит _атипичное_ использование |> - чего угодно который полагается на то, что он является оператором. Устранение атипичного использования по-прежнему является нарушением и поэтому не допускается в версиях 1.x. Если мы хотим выполнить любое из вышеперечисленных предложений с |> насколько я могу судить, нам нужно сделать синтаксис |> а не функцию в 1.0.

@StefanKarpinski В настоящий момент делает синтаксис |> а не функцию, даже в таблице? Можно ли поставить его на стол к моменту появления версии 1.0?

@StefanKarpinski Создает |> синтаксис, а не функцию, даже на данный момент? Можно ли поставить его на стол к моменту появления версии 1.0?

Предстоит исключить его из версии 0.7 и полностью удалить из версии 1.0.
Затем верните его на некоторое время в 1.x как элемент синтаксиса.
Что в тот момент было бы безрезультатным изменением.

Кому-то нужно будет это сделать, но я не думаю, что это ужасно сложное изменение, так что да, оно на столе.

Что было бы устаревшим |> ? Реализация в Lazy.jl?

x |> f можно заменить на f(x) .

Как насчет того, чтобы отказаться от l> но в то же время ввести, скажем, ll> который ведет себя так же, как текущий l> ?

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

Это сильно влияет на экосистему Query и друзей: я создал систему, которая очень похожа на синтаксис канала в тидиверсе R. Все это довольно комплексно: оно охватывает файл io для семи табличных форматов файлов (еще два очень близки), все операции запроса (например, dplyr) и построение графиков (недалеко, но я оптимистично настроен, что мы можем иметь что-то вроде ggplot скоро). Все это основано на текущей реализации l> ...

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

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

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

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

Разве мое предложение сверху не позволило бы этого? То есть сделать |> синтаксической ошибкой в ​​julia 1.0 и сделать ||> эквивалентом сегодняшнего |> . Для julia 1.0 это было бы незначительным раздражением для кода, который в настоящее время использует |> потому что придется переключиться на ||> . Но я считаю, что это было бы не так уж плохо, к тому же это можно было бы полностью автоматизировать. Затем, когда у кого-то появится хорошая идея для |> ее можно будет повторно ввести в язык. В этот момент вокруг будут и ||> и |> , и я предполагаю, что ||> медленно отойдет на задний план, если все начнут принимать |> . А потом, через пару лет, julia 2.0 могла просто удалить ||> . На мой взгляд, это а) не вызовет никаких серьезных проблем ни у кого в период julia 1.0 и б) оставит все варианты на столе для действительно хорошего решения для |> конечном итоге.

|>(x, f) = f(x)
|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...) # tuple
|>(x, f, args...) = f(x, args...) # args

x = 1 |> (+, 1, 1) |> (-, 1) |> (*, 2) |> (/, 2) |> (+, 1) |> (*, 2) # tuple
y = 1 |> (+, 1, 1)... |> (-, 1)... |> (*, 2)... |> (/, 2)... |> (+, 1)... |> (*, 2)... # args

Нелегко писать много раз, но писать слева направо и не использовать макрос.

function fibb_tuple(n)
    if n < 3
        return n
    end
    fibb_tuple(n-3) |> (+, fibb_tuple(n-2), fibb_tuple(n-1))
end

function fibb_args(n)
    if n < 3
        return n
    end
    fibb_args(n-3) |> (+, fibb_args(n-2), fibb_args(n-1))...
end

function fibb(n)
    if n < 3
        return n
    end
    fibb(n-3) + fibb(n-2) + fibb(n-1)
end

n = 25

println("fibb_tuple")
<strong i="8">@time</strong> fibb_tuple(1)
println("fibb_args")
<strong i="9">@time</strong> fibb_args(1)
println("fibb")
<strong i="10">@time</strong> fibb(1)

println("tuple")
<strong i="11">@time</strong> fibb_tuple(n)
println("args")
<strong i="12">@time</strong> fibb_args(n)
println("fibb")
<strong i="13">@time</strong> fibb(n)
fibb_tuple
  0.005693 seconds (2.40 k allocations: 135.065 KiB)
fibb_args
  0.003483 seconds (1.06 k allocations: 60.540 KiB)
fibb
  0.002716 seconds (641 allocations: 36.021 KiB)
tuple
  1.331350 seconds (5.41 M allocations: 151.247 MiB, 20.93% gc time)
args
  0.006768 seconds (5 allocations: 176 bytes)
fibb
  0.006165 seconds (5 allocations: 176 bytes)

|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...) ужасно.
|>(x, f, args...) = f(x, args...) нужно больше букв, но быстро.

Я думаю, что разрешение subject |> verb(_, objects) подобного синтаксиса как verb(subject, objects) означает поддержку SVO (но Джулия по умолчанию использует VSO или VOS). Однако Джулия поддерживает несколько патчей, поэтому субъект может быть субъектами. Я думаю, мы должны разрешить синтаксис типа (subject1, subject2) |> verb(_, _, object1, object2) как verb(subject1, subject2, object1, object2) если мы введем синтаксис SVO.
Это MIMO, если он понимается как конвейер, как заметил

Как насчет использования (x)f как f(x) ?
(x)f(y) может читаться как f(x)(y) и f(y)(x) поэтому сначала выберите оценку правильно:

(x)f # f(x)
(x)f(y) # f(y)(x)
(x)f(y)(z) # f(y)(z)(x)
(x)(y)f(z) # f(z)(y)(x)
(a)(b)f(c)(d) # f(c)(d)(b)(a)
1(2(3, 4), 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
1 <| (2 <| (3, 4), 5 <| (6, 7), 8 <| (9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10)), but 2 <| (3, 4) == 2((3, 4)) so currently emit error
3 |> 2(_, 4) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3)2(_, 4))1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
(3, 4) |> 2(_, _) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3, 4)2)1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))

Это может четко управлять vararg.
Но он сломает бинарные операторы без пробелов:

(a + b)+(c + d) # +(c + d)(a + b) == (c + d)(a + b): Error

Альтернативный вариант: добавить еще один регистр для синтаксиса разбивки. Попросите f ... (x) удалить сахар до (args ...) -> f (x, args ...)

Это позволит синтаксически облегчить (вручную) каррирование:

#Basic example:
f(a,b,c,d) = #some definition
f...(a)(b,c,d) == f(a,b,c,d)
f...(a,b)(c,d) == f(a,b,c,d)
f...(a,b,c)(d) == f(a,b,c,d)
f...(a)...(b)(c,d) == f(a,b,c,d) # etc etc

# Use in pipelining:
x |> map...(f) |> g  |> filter...(h) |> sum

Когда перестать карри? Функции Julia не имеют фиксированной арности.

У Юлии уже есть оператор сплаттинга. То, что я предлагаю, будет вести себя точно так же, как текущий оператор splat.

I, e: f ... (x) == (args ...) -> f (x, args ...) - сахар для создания лямбды со сплаттингом.

Это определение всегда дает вам объект-функцию. Возможно, вам иногда нужен ответ.

Вы получаете это, явно вызывая объект в конце. Например, обратите внимание на отсутствие ... перед последним набором круглых скобок в моем последнем примере f ... (a) ... (b) (c, d) == f (a, b, c, d) .

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

@saolof

Базовый пример:

f (a, b, c, d) = # некоторое определение
f ... (a) (b, c, d) == f (a, b, c, d)
f ... (a, b) (c, d) == f (a, b, c, d)
f ... (a, b, c) (d) == f (a, b, c, d)
f ... (a) ... (b) (c, d) == f (a, b, c, d) # и т. д. и т. д.

Хорошая интуиция для использования splat с цепочкой функций, но слишком сложна в моем понимании.
Вы подали несколько заявок в одной точке цепочки.
В цепочке функций вы делаете одно приложение шаг за шагом по всей цепочке.

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

- (обрезано) -

Извините, это бессмысленно и нечитабельно.
См. Мое второе сообщение ниже, чтобы получить более четкое объяснение (надеюсь).

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

Вы даже можете создать прототип, не выходя из собственного ответа:

ctranspose(f) = (a...) -> (b...) -> f(a..., b...)

map'(+)(1:10)

map'(+)'(1:10, 11:20)(21:30)

(+)'(1,2,3)(4,5)

1:10 |> map'(x->x^2) |> filter'(iseven)

Думаю, это приятно.

Изменить: Похоже, это также может быть путем к большему обобщению. Если мы можем написать map∘(+, 1:10) тогда мы можем написать map∘(_, 1:10) чтобы сначала поместить каррированный аргумент, а оператор карри определяет область действия лямбды, решая самую большую проблему для такого общего каррирования.

Эх, это умно, @MikeInnes.

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

(пояснение: я получаю 1:10 |> map'(x->x^2) |> filter'(iseven) за это предложение, так что я 💯% за это!)

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

Может нам стоит ввести новый оператор юникода? http://www.fileformat.info/info/unicode/char/1f35b/index.htm

(Сожалею...)

Мне кажется, что _ по-прежнему являются более гибким способом создания лямбда-выражений

@bramtayl Я думаю, что идея правки MikeInnes к его сообщению состоит в том, что эти два элемента могут сосуществовать - автономные подчеркивания, как в запросе на вытягивание @stevengj будут работать, автономное каррирование, как в приведенной выше идее Майка, будет работать, и объединить два также будет работать, позволяя вам использовать оператор каррирования для ограничения области _ внутри него.

ах понял

Это не сильно отличает его от LazyCall.jl.

Или предложение здесь: https://github.com/JuliaLang/julia/pull/24990#issuecomment -350490856

На более серьезном замечании:

Для ясности, я не думаю, что нам следует злоупотреблять оператором adjoint для этого

Наверное, разумный выбор. Однако я хотел бы выразить надежду, что если такое решение будет реализовано, ему будет предоставлен оператор, который легко набирать. Возможность делать что-то вроде 1:10 |> map'(x->x^2) значительно менее полезна, если какой-либо символ, заменяющий ' требует от меня поиска его в таблице юникода (или использования редактора, поддерживающего расширения LaTeX).

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

  • в (линейном) трубопроводе
  • внутри, в вызове функции

    • делать splat до, а не после

так

  • splat может вызвать отсутствующий аргумент итератора

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

ПРИМЕР

1:10
    |> map(...x->x^2)
    |> filter(...iseven)

ПРИМЕР 2

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      map(...i -> a*i/n) |>
      map(...t -> [r*cos(t), r*sin(t)]) 

мог стоять за

elmap = f -> (s -> map(f,s))

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      elmap(i -> a*i/n) |>
      elmap(t -> [r*cos(t), r*sin(t)]) 

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

mutable struct T
    move
    scale
    display
    x
    y
end

function move(x,y)
    t.x=x
    t.y=y
    return t
end
function scale(c)
    t.x*=c
    t.y*=c
    return t
end
function display()
    @printf("(%f,%f)\n",t.x,t.y)
end

function newT(x,y)
    T(move,scale,display,x,y)
end


julia> t=newT(0,0)
T(move, scale, display, 0, 0)

julia> t.move(1,2).scale(3).display()
(3.000000,6.000000)

Синтаксис кажется очень похожим на обычный ООП, с одной особенностью изменяемых «методов класса». Не уверен, каковы будут последствия для производительности.

@ivanctong То, что вы описали, больше похоже на свободный интерфейс, чем на объединение функций.

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

То, как elixir делает это, когда оператор канала всегда проходит в левой части в качестве первого аргумента и допускает дополнительные аргументы впоследствии, был довольно полезным, я бы хотел увидеть что-то вроде "elixir" |> String.ends_with?("ixir") как гражданин первого класса в Юлии.

В других языках это определяется как унифицированный синтаксис вызова функций .
Эта функция имеет ряд преимуществ (см. Википедию), было бы неплохо, если бы Юлия ее поддержала.

Так есть ли на данный момент свободный интерфейс для Джулии?

Пожалуйста, задавайте вопросы на дискуссионном форуме Julia Discourse .

В приступе взлома (и сомнительного суждения !?) я создал еще одно возможное решение проблемы жесткости привязки заполнителей функций:

https://github.com/c42f/MagicUnderscores.jl

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

julia> <strong i="12">@_</strong> [1,2,3,4] |> filter(_>2, _)
2-element Array{Int64,1}:
 3
 4

julia> <strong i="13">@_</strong> [1,2,3,4] |> filter(_>2, _) |> length
2

"просто работай". (Очевидно, что @_ исчезнет, ​​если можно сделать это общим решением.)

Некоторый вариант предложения @MikeInnes может показаться мне подходящим для моих нужд (обычно длинные цепочки filter, map, reduce, enumerate, zip и т.д. с использованием синтаксиса do ).

c(f) = (a...) -> (b...) -> f(a..., b...)

1:10 |> c(map)() do x
    x^2
end |> c(filter)() do x
    x > 50
end

Это работает, хотя я больше не могу заставить работать ' . Это немного короче:

1:10 |> x -> map(x) do x
    x^2
end |> x -> filter(x) do x
    x > 50
end

Также я думаю, можно было просто сделать

cmap = c(map)
cfilter = c(filter)
cetc = c(etc)
...

1:10 |> cmap() do x
    x^2
end |> cfilter() do x
    x > 50
end |> cetc() do ...

Начиная с версии 1.0 вам нужно будет перегрузить adjoint вместо ctranspose . Вы также можете:

julia> Base.getindex(f::Function, x...) = (y...) -> f(x..., y...)

julia> 1:10 |> map[x -> x^2] |> filter[x -> x>50]
3-element Array{Int64,1}:
  64
  81
 100

Если бы мы могли перегрузить apply_type мы могли бы получить map{x -> x^2} :)

@MikeInnes Я только что украл это

Поздний и слегка несерьезный вклад - как насчет передачи данных по конвейеру в любое место в списке аргументов с помощью комбинации левого и правого операторов карри:

VERSION==v"0.6.2"
import Base: ctranspose, transpose  
ctranspose(f::Function) = (a...) -> ((b...) -> f(a..., b...))  
 transpose(f::Function) = (a...) -> ((b...) -> f(b..., a...))

"little" |> (*)'''("Mary ")("had ")("a ") |> (*).'(" lamb")

В Clojure есть несколько хороших макросов многопоточности . Есть ли у нас где-нибудь такие в экосистеме Julia?

В Clojure есть несколько хороших макросов многопоточности . Есть ли у нас где-нибудь такие в экосистеме Julia?

https://github.com/MikeInnes/Lazy.jl

В Clojure есть несколько хороших макросов многопоточности . Есть ли у нас где-нибудь такие в экосистеме Julia?

у нас их не менее 10 штук.
Я разместил список выше в теме.
https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539

Можете ли вы отредактировать список, чтобы вместо двух других моих пакетов был LightQuery?

Поскольку оператор |> происходит из elixir, почему бы не вдохновиться одним из способов создания анонимных функций?
в elixir вы можете использовать &expr для определения новой анонимной функции и &n для захвата позиционных аргументов ( &1 - первые аргументы, &2 - второй, и т.д.)
В elixir есть дополнительные элементы, которые можно написать (например, вам нужна точка перед скобкой для вызова анонимной функции &(&1 + 1).(10) )

Но вот как это могло бы выглядеть в Юлии

&(&1 * 10)        # same as: v -> v * 10
&(&2 + 2*&5)      # same as: (_, x, _, _, y) -> x + 2*y
&map(sqrt, &1)    # same as: v -> map(sqtr, v)

Таким образом, мы можем использовать оператор |> более красиво

1:9 |> &map(&1) do x
  x^2
end |> &filter(&1) do x
  x in 25:50
end

вместо

1:9 |> v -> map(v) do x
  x^2
end |> v -> filter(v) do x
  x in 25:50
end

обратите внимание, вы можете заменить строки 2 и 3 на .|> &(&1^2) или .|> (v -> v^2)

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

Обратите внимание, что в моих примерах я использовал & , но использование ? , _ , $ или чего-то другого не изменит ничего в дело.

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

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

Но что касается префиксов для аргументов, $ имеет традицию в мире сценариев оболочки. Всегда хорошо использовать знакомых персонажей. Если |> от Elixir, то это может быть аргументом в пользу того, чтобы взять & от Elixir, с мыслью, что пользователи уже думают в этом режиме. (Если предположить, что есть много бывших пользователей Эликсира ...)

Одна вещь, которую подобный синтаксис, вероятно, никогда не сможет уловить, - это создание функции, которая принимает N аргументов, но использует меньше, чем N. $1 , $2 , $3 в теле подразумевает наличие 3 аргументов, но если вы хотите поместить это в положение, в котором он будет вызываться с 4 аргументами (последним будет проигнорировано), тогда нет естественного способа выразить это. (За исключением предопределения функций идентичности для каждого N и обертывания выражения одним из них.) Это не относится к мотивирующему случаю помещения его после |> , которое, однако, имеет только один аргумент.

Я расширил трюк @MikeInnes с перегрузкой getindex, используя Colon как если бы функции были массивами:

struct LazyCall{F} <: Function
    func::F
    args::Tuple
    kw::Dict
end

Base.getindex(f::Function,args...;kw...) = LazyCall{typeof(f)}(f,args,kw)

function (lf::LazyCall)(vals...; kwvals...)

    # keywords are free
    kw = merge(lf.kw, kwvals)

    # indices of free variables
    x_ = findall(x->isa(x,Colon),lf.args)
    # indices of fixed variables
    x! = setdiff(1:length(lf.args),x_)

    # the calling order is aligned with the empty spots
    xs = vcat(zip(x_,vals)...,zip(x!,lf.args[x!])...)
    args = map(x->x[2],sort(xs;by=x->x[1]))

    # unused vals go to the end
    callit = lf.func(args...,vals[length(x_)+1:end]...; kw...)

    return callit
end

[1,2,3,4,1,1,5]|> replace![ : , 1=>10, 3=>300, count=2]|> filter[>(50)]  # == [300]

log[2](2) == log[:,2](2) == log[2][2]() == log[2,2]()  # == true

Это намного медленнее, чем лямбды или макросы потоков, но я думаю, что это супер круто: p

Чтобы напомнить людям, комментирующим здесь, просмотрите соответствующее обсуждение на https://github.com/JuliaLang/julia/pull/24990.

Кроме того, я бы посоветовал вам попробовать https://github.com/c42f/Underscores.jl, который дает удобную для цепочки функций реализацию синтаксиса _ для заполнителей. @jpivarski на основе ваших примеров, вы можете найти его довольно знакомым и удобным.

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