К сожалению, я унаследовал очень большую кодовую базу Perl, и мне нужно внести некоторые изменения в одну из ее функций.
Программа читает CSV-файл и выполняет в нем некоторую очистку, чтобы создать сообщение protobuf. В части входной строки есть пара ключ-значение foo:bar
.
Мне удалось отследить, где, по моему мнению, анализируются temp_params. На данный момент, пожалуйста, игнорируйте dp, давайте предположим, что dp всегда будет пустым. Ключ-значение, которое я ожидаю, является частью tp, а не dp.
sub _add_temp_and_dynamic_params {
my ($self, $row) = @_;
state $raw_to_full = {
tp => 'temp_params',
dp => 'dynamic_params',
};
while ( my ( $raw, $full ) = each %$raw_to_full ) {
my @params =
map { { key => $_->[0], value => $_->[1] } }
grep { @$_ == 2 && $_->[0] ne '' }
map { [ split ":" ] }
split /,/, ( delete $row->{ $raw } or next );
$row->{ $full }->@* = @params if @params;
}
}
После этого я хочу вызвать temp_params{}
с выходными данными этого метода, описанного выше, и цель этого метода — взять это _my_new_subroutine
, чтобы temp_params{foo:bar}
превратилось в отдельную независимую пару ключ-значение.
Проблема в том, что я действительно понятия не имею, что происходит в этом методе и как выглядит результат. Мне это кажется крайне загадочным. Я не знаю, как foo:bar
может содержать какую-либо значимую информацию, поскольку кажется, что он просто создает хеш, в котором ключом является raw_to_full
, а значением является строка tp
. Я даже не уверен, будет ли мой метод работать с хешем или со строкой. Что вообще означает 'temp_params'
? или ( delete $row->{ $raw } or next )
? Я много раз слышал, что Perl имеет дурную славу как «язык, предназначенный только для записи», но я понятия не имел, что это будет настолько плохо.
Пока что я понимаю следующее:
Метод назначает переменную $self, поскольку методы Perl всегда передают в качестве аргумента свою собственную вызывающую программу. Затем добавляется $row. $ указывает на скаляр, и он работает со строками CSV, поэтому я предполагаю, что входные данные представляют собой строку.
Затем объявляется локальная переменная $raw_to_full, это хэш, где tp и dp — это ключи, а строки «temp_params» и «dynamic_params» — их соответствующие значения. (Это также приводит к другому вопросу: если это хэш, почему он обозначается $ вместо %?)
Затем создаются две новые локальные переменные: raw иfull, которые являются результатом «разделения» ключа и значений в каждой записи хеша raw_to_full. Для каждой итерации создается строка, в которой слово «ключ» фактически объявляется как сам ключ, а значение ключа — это «необработанное» значение, тогда как «полное» — это значение (в этом я не уверен.
Затем выполняется фильтр, включающий только строки с ключом-значением, где значение не является пустым.
Затем создается разделение в месте, где указано «:». Наконец, делается еще одно разделение, чтобы разделить, я предполагаю, разные пары kv внутри каждой строки «params». После этого я совершенно потерял понимание того, что здесь может происходить.
Я так и сделал, но не вижу, как существование dp меняет функциональность метода. Он по-прежнему будет выполнять один и тот же набор инструкций для обоих kv в хеше, не так ли?
Возможно также удалите комментарий «только для записи», если вы хотите, чтобы люди, знакомые с Perl, были дружелюбны. На самом деле эти строки не являются чем-то непонятным и лишь показывают, что вы не умеете их читать.
delete $row->{ $raw } or next
удаляет ключ из хэш-ссылки $row
. Возвращается связанное значение, и если оно ложно, запускается следующая итерация охватывающего цикла. $row->{ $full }->@* = @params if @params;
— это альтернативный способ записи @{ $row->{ $full } } = @params if @params
, т.е. если @params
есть, они присваиваются ссылке на массив, хранящейся в $row->{$full}
.
если это хеш, почему он обозначается $ вместо %? Прочтите о ссылках в Perl.
Итак, $raw_to_full — это анонимный хеш... но если значением является буквально строка «temp_params» (или динамическая), как оно может содержать какую-либо значимую информацию?
Кажется, tp
и dp
переводятся в их полные эквиваленты.
Любому, кого попросят поработать над программой, написанной на языке X, не зная этого языка, придется нелегко. Не вините здесь Perl.
(хотя бит delete
действительно выглядит так, будто автор умничает ради ума, остальное выглядит довольно просто. Помогло бы, если бы бит map
/grep
/map
был разбит на отдельные операторы с промежуточными переменными?)
@Шон Верно... удаляю.
Использование состояния кажется преждевременной оптимизацией, поскольку его цель — инициализировать переменную только один раз и сохранить это состояние во время выполнения. Поскольку переменная в этом блоке никогда не изменяется, цель состояния заключается в том, что оно инициализирует переменную только один раз. Так, возможно, попытка сэкономить несколько циклов процессора?
@Dasph На будущее: не следует быть таким многословным при написании вопроса. Постарайтесь выяснить, что важно, и будьте минимальными.
Используйте инструмент дампа, чтобы просмотреть структуру данных. Вы обнаружите, что это занимает
$row = {
tp => "foo:bar,abc:def",
};
и меняет его на
$row = {
temp_params => [
{ key => "foo", value => "bar" },
{ key => "abc", value => "def" },
],
];
Этот формат затрудняет доступ к параметрам по ключу, но позволяет использовать несколько параметров с одним и тем же ключом.
Чтобы найти значения ключа, вам нужно будет перебрать массив.
my @values_for_foo =
map { $_->{ value } }
grep { $_->{ key } eq "foo" }
$row->{ temp_params }->@*;
Тем не менее, вы сохраняете только последнее значение для любого данного ключа. (Так зачем использовать эту структуру?!?) Чтобы получить единственное значение ключа (или undef
, если оно не найдено),
my ( $value_for_foo ) =
map { $_->{ value } }
grep { $_->{ key } eq "foo" }
$row->{ temp_params }->@*;
Задаваемые конкретные вопросы:
delete $row->{ $raw }
— то же самое, что $row->{ $raw }
, за исключением того, что при этом элемент удаляется из хэша.or next
переходит к следующему проходу цикла, если левый операнд имеет значение false. Предположительно, он предназначен для проверки существования $row->{ temp_params }
.$row->{ $full }->@*
— то же самое, что и @{ $row->{ $full } }
, т. е. массив, на который ссылается $row->{ $full }
.$row->{ $full }->@* = @params
как $row->{ $full } = \@params
.$row
— это строка, но чтобы $row->{ }
было действительным, она должна быть ссылкой на хэш.$raw_to_full
технически содержит ссылку на хеш, а не на хеш.$raw
и $full
действительно являются ключом и значением (например, tp
и temp_params
) одного из элементов.f( g( h() ) )
звонит h
первым.
split /,/, "foo:bar,abc:def"
производит "foo:bar", "abc:def"
map { [ split ":" ] }
, который производит [ "foo", "bar" ], [ "abc", "def" ]
map { { key => $_->[0], value => $_->[1] } }
, который производит { key => "foo", value => "bar" }, { key => "abc", value => "def" }
.Пожалуйста, ограничьте свой вопрос одной проблемой в будущем.
Я много раз слышал, что Perl имеет дурную славу как «язык, предназначенный только для записи», но я понятия не имел, что это будет настолько плохо.
За исключением «скрытого» or next
и улучшения, которое я предложил выше, на самом деле это очень читабельно. Ваша проблема в том, что вы не знаете даже основ. Незнание языка не означает нечитабельность.
Поскольку вы сравниваете с Python,
Перл:
my @params =
map { { key => $_->[0], value => $_->[1] } }
grep { @$_ == 2 && $_->[0] ne '' }
map { [ split ":" ] }
split /,/,
$row->{ $raw };
Питон:
tmp = row[ "raw" ].split( "," )
tmp = [ x.split( ":" ) for x in tmp ]
tmp = [ x for x in tmp if len( x ) == 2 and x[ 1 ] != "" ]
params = [ { "key": x[ 0 ], "value": x[ 1 ] } for x in tmp ]
И вы называете Perl шумным?
(Я не программист на Python. Вполне возможно, что это можно было бы написать более чисто. Возможно, мне пришлось бы избегать подхода функционального программирования. Это было бы отстой.)
$row->{ $full }->@* = @params
— странный способ записи $row->{ $full } = \@params
. Это? Второй способ распространяет изменения в @params на $row->{$full}
, первый — нет.
@choroba, ты доказал мою точку зрения. Цель этого сабвуфера — создать $row->{ temp_params }
из $row->{ tp }
. Вы говорите, что текущий код подразумевает, что что-то уже имеет копию ссылки в $row->{ temp_params }
. Но мы еще не создали эту ссылку. Так что да, $row->{ $full } = \@params
определенно более понятный способ написания.
Спасибо, что помогли мне расшифровать это. Я знаю, что SO — не место для обсуждения, но если вам интересны мои мысли о Perl, я думаю, что этот пост от «dusktreader» прекрасно их объясняет: reddit.com/r/Python/comments/v2dwax/… Я не впервые пробую Perl, и каждый раз я сталкиваюсь с теми же препятствиями, которые описывают большинство людей. Чрезвычайная гибкость становится для меня проблемой.
Ответ на главный вопрос: Python не так стар, как утверждается в посте. Тогда, возможно, существовал Python, но он отличался от нынешнего языка. Perl намного старше, но его возраст немного очевиден. Читабельность тут ни при чем. Это отсутствие песочницы, тот факт, что через него просачивается наследие C, отсутствие текстовых потоков по умолчанию.
Однако это гораздо более приятный язык, чем Python. Для чего-то более современного я бы использовал C# вместо Python. Верхний ответ фактически жалуется, что везде есть доллары. Это единственная часть вашего вопроса, которую вы правильно поняли!? Так действительно ли это обоснованная жалоба?
Я добавил преобразование Python в свой ответ.
Вы удалили большую часть шума из части Perl, которая исходит из всего шаблонного кода, необходимого для подготовки ввода, тогда как в Python вам просто нужно указать, что метод получает один параметр, и вызов его с большим количеством параметров приведет к очень явные ошибки. Однако лично я работаю со Scala, а не с Python. Но да, Perl НАМНОГО шумнее и его труднее читать. Дело в том, что мы могли бы показать этот код людям, которые понятия не имеют о программировании, и они смогли бы хотя бы приблизительно понять, что делает код на Python. Что касается версии Perl, они были бы совершенно невежественны.
Но в любом случае вопрос решён и SO не совсем для дискуссий, так что давайте договоримся о несогласии по поводу текущего статуса и формы perl.
Re «Вы удалили большую часть шума из части Perl». Нет, я просто добавил пробелы. Я не конвертировал весь код, чтобы сделать его кратким и потому что в остальном не было ничего интересного. { tp => "temp_params", dp => "dynamic_params" }
становится { "tp": "temp_params", "dp": "dynamic_params" }
и т. д.
@ikegami Вы удалили удаление и/или следующую часть, но в остальном идентично. Отступы могут творить чудеса с читабельностью. :)
@TLP, я уже сказал в своем ответе, что часть or next
написана плохо. Это надо было сделать отдельно. Тот факт, что часть написана плохо, не делает Perl нечитабельным. Просто этот код. Вы также можете написать нечитаемый код на Python.
Вы не просто добавили пробелы, вы исключили начальный my ($self, $row) = @_;
(и будущие вызовы my и все эти объявления внутренних переменных, что, несомненно, является огромной частью шума, на который жалуются люди, которые не любят Perl)
Но разве это шум? Программист Perl анализирует это с первого взгляда. И у Perl теперь есть подписи: sub _add_temp_and_dynamic_params( $self, $row )
вместо def _add_temp_and_dynamic_params( self, row ):
).
Да, это шум. В этом суть с самого начала: это вообще не интуитивно понятно. Единственный способ понять это — хорошо знать синтаксис и все неявные механизмы работы языка. Если вы дадите его тому, кто никогда не программировал, он не разберется в этом, хотя, вероятно, получит общее представление о том, что происходит в Python. В то время как другие языки просто вызывают метод установки/обновления, например foo.update(newVals), Perl в конечном итоге вызывает $row->{$full}->@*=@params, что непонятно тем, кто не знаком с синтаксисом.
Кроме того, вам не нужно объявлять self для подписи Python, метод не должен знать о вызывающем объекте, поскольку он просто вернет новый объект. Наконец: я считаю ваше преобразование Python немного неискренним, поскольку вы просто «пишете Perl на Python». Подход к проблеме в реальном стиле Python был бы тривиален, так как вам просто нужно было бы вызвать какую-нибудь программу чтения csv, которая преобразует строку tp в dict, а затем обновить нужное поле, получив значение из dict. Нет необходимости в понимании списков или вызовах функторов.
Re «В этом суть с самого начала: это вообще не интуитивно понятно». Выполнение присвоения списка — одна из самых фундаментальных операций языка. Интуиция не играет роли. Когда вы впервые увидели 4 + 3
, вы понятия не имели, что он делает. Интуиция бы вам не помогла. Это не делает его нечитабельным. Опять вы путаете не зная языка и читабельности. Это разные вещи. Вы, очевидно, ожидаете, что сможете читать на языке, которого не знаете, но не стоит этого ожидать.
Re: «Я считаю ваше преобразование Python немного неискренним, поскольку вы просто «пишете Perl на Python».», Не программирование на Perl, а функциональное программирование. Вы знаете, то, что поддерживает любой другой язык, потому что это делает все очень простым, понятным и гибким. Python предоставляет все инструменты для функционального программирования, но он чертовски шумен. Вместо того, чтобы писать код как последовательность операций, Python заставляет вас использовать вложенный цикл, что приводит к гораздо большему количеству кода и более высоким умственным нагрузкам, поскольку это больше не последовательность операций, а повторяющиеся последовательности операций.
Я не собираюсь знать все тонкости, но НАДЕЮСЬ получить общую картину того, что происходит. Это не первый раз, когда я беру кодовую базу на неизвестном языке, но это было «самое сложное». Если вы не знаете Scala, и я спрошу вас, что делает val allMyNums = List(1, 2) ++ List(3, 4), вы очень легко получите ответ, даже если вы не знаете программирования il. В Perl такого не было бы, потому что синтаксис полон знаковых знаков препинания, которые для посторонних выглядят как шум. Не случайно одной из наиболее распространенных критических замечаний в адрес Perl является его неинтуитивный синтаксис.
Никаких проблем у вас не было с пунктуацией. Вы знали, что означает $
. Вы знали, что @$_ == 2
сделал. Вы знаете, что означают []
и {}
, потому что это то же самое, что Python. Кто на самом деле лукавит? Чего вы не поняли, так это того, что, учитывая f g h
(сокращение от f( g( h() ) )
, вы думали, что f
вызывается первым. И вы, вероятно, тоже не искали, что делает функция карты. Ваши проблемы не имели ничего общего с читабельностью.
Суть моей проблемы заключается в том, что практически невозможно понять, что происходит в этом коде, поскольку синтаксис полон небуквенно-цифровых символов, каждый из которых имеет очень значительный эффект.
Да. dp на данный момент не нужен (там разные ключи, нужный мне ключ приходит в tp)