Могу ли я написать {x, a, b} // Do [..., #] & вместо Do [..., {x, a, b}]? - PullRequest
8 голосов
/ 17 августа 2011

Я влюблен в Руби. В этом языке все основные функции на самом деле являются методами. Вот почему я предпочитаю постфиксную нотацию - когда данные, которые я хочу обработать, помещаются слева от тела анонимной функции обработки, например: array.map{...}. Я считаю, что он имеет преимущества в том, насколько легко читать этот код.

Но Mathetica, будучи функциональным (да, если хотите, может быть процедурным), диктует стиль, в котором имя функции размещается слева от данных. Как мы видим из руководств, // используется только тогда, когда это простая функция без аргументов, такая как list // MatrixForm. Когда функция нуждается в большом количестве аргументов, люди, написавшие руководства, используют синтаксис F[data].
Это было бы хорошо, но моя проблема в случае F[f,data], например Do[function, {x, a, b}]. Большинство функций Mathematica (если не все) имеют аргументы именно в этом порядке - [function, data], а не [data, function]. Поскольку я предпочитаю использовать чистые функции для поддержания чистоты пространства имен вместо создания множества именованных функций в моей записной книжке, аргумент function может быть слишком большим - настолько большим, что аргумент data будет помещен в 5-20-ю строку кода после строки с вызовом функции.

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

Do[f (x), {x, a, b}]\n{x, a, b} // Do[f (x), #] &

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

  1. это вызывает проблему синтаксического анализатора подсветки Mathematica: x в постфиксной нотации выделяется синим цветом, а не бирюзой;
  2. каждый раз, когда я заглядываю в руководства Mathematica, я вижу такие примеры: Do[x[[i]] = (v[[i]] - U[[i, i + 1 ;; n]].x[[i + 1 ;; n]])/ U[[i, i]], {i, n, 1, -1}];, что означает ... черт, они думают, что их легко читать / поддерживать / и т.д.?!

Итак, эти две вещи заставили меня задать этот вопрос: я такой плохой мальчик, что использую мой стиль Ruby, и должен ли я писать код, как эти парни делают, или это ОК и мне не о чем беспокоиться , и надо писать так, как мне нравится?

Ответы [ 6 ]

10 голосов
/ 17 августа 2011

Стиль, который вы предлагаете, часто возможен, но не рекомендуется в случае Do.Проблема в том, что Do имеет атрибут HoldAll.Это важно, потому что переменная цикла (в данном примере x) должна оставаться неоцененной и рассматриваться как локальная переменная.Чтобы увидеть это, попробуйте оценить следующие выражения:

x = 123;

Do[Print[x], {x, 1, 2}]
(* prints 1 and 2 *)

{x, 1, 2} // Do[Print[x], #]&
(* error: Do::itraw: Raw object 123 cannot be used as an iterator.
   Do[Print[x], {123, 1, 2}]
*)

Ошибка возникает из-за того, что у чистой функции Do[Print[x], #]& отсутствует атрибут HoldAll, что приводит к оценке {x, 1, 2}.Вы можете решить эту проблему, явно определив чистую функцию с атрибутом HoldAll, например:

{x, 1, 2} // Function[Null, Do[Print[x], #], HoldAll]

... но я подозреваю, что лекарство хуже болезни:)

Таким образом, когда вы используете «связывающие» выражения, такие как Do, Table, Module и т. Д., Безопаснее всего соответствовать стаду.

8 голосов
/ 17 августа 2011

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

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

Do[
  x[[i]] = (v[[i]] - U[[i, i + 1 ;; n]].x[[i + 1 ;; n]]) / U[[i, i]]
  , {i, n, 1, -1}
];

При этом мне нравится писать, используя больше префиксных (f @ x) и инфиксных (x ~ f ~ y) обозначений, которые я обычно вижу, и я считаю это ценным, поскольку легко определить, что такие функции получают один и два аргументы соответственно. Это несколько нестандартно, но я не думаю, что это выходит за рамки следов синтаксиса Mathematica . Скорее, я вижу это как использование синтаксиса в интересах. Иногда это приводит к сбою подсветки синтаксиса, но я могу жить с этим:

f[x] ~Do~ {x, 2, 5} 

При использовании чего-либо, кроме стандартной формы f[x, y, z] (с необходимыми разрывами строк), вы должны быть более осторожны с порядком оценки, и ИМХО, может ухудшиться читаемость. Рассмотрим этот надуманный пример:

{x, y} // # + 1 & @@ # &

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

Do[f[x], {x, 10000}] //Timing //First
7 голосов
/ 17 августа 2011

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

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

In[4]:= 
x=1;
{x,1,5}//Do[f[x],#]&

During evaluation of In[4]:= Do::itraw: Raw object 
1 cannot be used as an iterator. >>

Out[5]= Do[f[x],{1,1,5}]

Какой сюрприз, не правда ли?Этого не произойдет, если вы используете Do стандартным образом.

Во-вторых, обратите внимание, что, хотя этот факт в значительной степени игнорируется, f[#]&[arg] равен НЕ всегда так же, как f[arg].Пример:

ClearAll[f];
SetAttributes[f, HoldAll];
f[x_] := Print[Unevaluated[x]]

f[5^2]

5^2

f[#] &[5^2]

25

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

6 голосов
/ 17 августа 2011

Mathematica поддерживает 4 способа применения функции к своим аргументам:

  1. стандартная функциональная форма: f[x]
  2. префикс: f@x или g@@{x,y}
  3. постфикс: x // f и
  4. infix: x~g~y, что эквивалентно g[x,y].

Какую форму вы решите использовать, зависит от вас, и часто это эстетический выбор, больше всего на свете. Внутренне f@x интерпретируется как f[x]. Лично я, в основном, использую постфикс, как и вы, потому что я рассматриваю каждую функцию в цепочке как преобразование, и таким образом проще объединить несколько преобразований в один. Тем не менее, мой код будет завален как стандартной формой, так и префиксной формой, в основном в зависимости от прихоти, но я склонен использовать стандартную форму чаще, так как это вызывает чувство сдержанности в отношении параметров функций.

Я немного раскрепостился с префиксной формой, включив сокращенную форму Apply (@@) вместе с Prefix (@). Из встроенных команд только стандартная форма, инфиксная форма и Apply позволяют легко передавать более одной переменной в вашу функцию без дополнительной работы. Apply (например, g @@ {x,y}) работает путем замены Head выражения ({x,y}) на функцию, фактически оценивая функцию с несколькими переменными (g@@{x,y} == g[x,y]).

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

{x,y} // f[ #[[1]], #[[2]] ]&

для указания, какой элемент List соответствует соответствующему параметру. Я склонен делать это, но вы можете комбинировать это с Apply как

{x,y} // f @@ #&

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

Редактировать : Я должен отметить, что f и g выше являются просто заполнителями, они могут и часто заменяются чистыми функциями, например #+1& @ x в основном эквивалентно #+1&[x], см. Ответ Леонида .

Чтобы прояснить, согласно ответу Леонида , эквивалентность между f@expr и f[expr] является истинной, если f не обладает атрибутом , который препятствовал бы выражению expr, от оценки до передачи в f. Например, одним из Attributes из Do является HoldAll, который позволяет ему действовать как ограничивающая конструкция, которая позволяет оценивать его параметры внутренне, не отменяя внешнего влияния. Дело в том, что expr будет оцениваться до того, как оно будет передано f, поэтому, если вам нужно, чтобы оно оставалось неоцененным, необходимо соблюдать особую осторожность, например, создать чистую функцию с атрибутом стиля Hold .

5 голосов
/ 17 августа 2011

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

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

prodListAfterLongComputation[
    args,
]//ListPlot[#,PlotRange->Full]&

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

ListPlot[
    lst,
    Option1->Setting1,
    Option2->Setting2
]

Так что, по сути, все, что является случайным и, возможно, не важным, чтобы его можно было прочитать (мне не нужно иметь возможность мгновенно анализировать первый ListPlot поскольку смысл этого бита не в том, что он становится постфиксом, чтобы не нарушать уже написанный сложный код, к которому он применяется.С другой стороны, сложный код, который я обычно пишу так, как мне кажется, проще всего анализировать позже, что в моем случае выглядит как

f[
    g[
        a,
        b,
        c
    ]
]

, даже если для этого требуется больше ввода и, если вы не используетеПлагин Workbench / Eclipse упрощает реорганизацию кода.

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

Конечно, все это применимо, если вы единственный, кто работает с каким-то фрагментом кода;если есть другие, это совсем другой вопрос.

Но это всего лишь мнение.Я сомневаюсь, что кто-то может предложить больше, чем это.

1 голос
/ 17 августа 2011

Для функций с одним аргументом (f@(arg)), ((arg)//f) и f[arg] полностью эквивалентны даже в смысле применения атрибутов f. В случае функций с несколькими аргументами можно написать f@Sequence[args] или Sequence[args]//f с тем же эффектом:

In[1]:= SetAttributes[f,HoldAll];
In[2]:= arg1:=Print[];
In[3]:= f@arg1
Out[3]= f[arg1]
In[4]:= f@Sequence[arg1,arg1]
Out[4]= f[arg1,arg1]

Так что, похоже, решение для тех, кто любит постфиксную нотацию, - это использовать Sequence:

x=123;
Sequence[Print[x],{x,1,2}]//Do
(* prints 1 and 2 *)

Потенциально могут возникнуть некоторые трудности с функциями, имеющими атрибут SequenceHold или HoldAllComplete:

In[18]:= Select[{#, ToExpression[#, InputForm, Attributes]} & /@ 
   Names["System`*"], 
  MemberQ[#[[2]], SequenceHold | HoldAllComplete] &][[All, 1]]

Out[18]= {"AbsoluteTiming", "DebugTag", "EvaluationObject", \
"HoldComplete", "InterpretationBox", "MakeBoxes", "ParallelEvaluate", \
"ParallelSubmit", "Parenthesize", "PreemptProtect", "Rule", \
"RuleDelayed", "Set", "SetDelayed", "SystemException", "TagSet", \
"TagSetDelayed", "Timing", "Unevaluated", "UpSet", "UpSetDelayed"}
...