Вы не можете получить доступ к любым абстракциям «Исправлена» (RDDs, DataFrames, Datasets, SparkSession ...) Spark из функции, переданной в одну из преобразований DataFrame / RDD Spark. Вы также не можете обновлять измененные объекты на стороне драйвера из этих функций.
В вашем случае - вы пытаетесь использовать prodRows
и selection
(оба являются DataFrames) в функции, переданной в DataFrame.foreach
. Вы также пытаетесь обновить listOfProducts
(локальную драйверную переменную) из этой же функции.
Почему?
Как вы можете это решить? При работе с Spark, особенно с DataFrames, вы должны стараться избегать «итерации» над данными и вместо этого использовать декларативные операции DataFrame. В большинстве случаев, когда вы хотите ссылаться на данные другого DataFrame для каждой записи в вашем DataFrame, вы хотите использовать join
для создания нового DataFrame с записями, объединяющими данные из двух DataFrames.
В этом конкретном случае, это примерно эквивалентное решение, которое делает то, что вы пытаетесь сделать, если мне удастся это сделать правильно. Попытайтесь использовать это и прочитайте документацию DataFrame, чтобы выяснить детали:
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._
import spark.implicits._
val numRecProducts = 10
val result = prodRows.as("left")
// self-join by gender:
.join(prodRows.as("right"), $"left.gender_PK" === $"right.gender_PK" || $"right.gender_PK" === "UNISEX")
// limit to 10 results per record:
.withColumn("rn", row_number().over(Window.partitionBy($"left.product_PK").orderBy($"right.product_PK")))
.filter($"rn" <= numRecProducts).drop($"rn")
// group and collect_list to create products column:
.groupBy($"left.product_PK" as "product_PK")
.agg(collect_list(struct($"right.product_PK", lit(1))) as "products")
Как и многие другие, я часто предпочитаю передавать функцию options object
в функцию вместо передачи длинного списка параметров, но это действительно зависит от точного контекста.
Я использую читаемость кода как тест лакмуса.
Например, если у меня есть этот вызов функции:
checkStringLength(inputStr, 10);
Я думаю, что код вполне читабельный, как есть, и передача отдельных параметров просто прекрасна.
С другой стороны, существуют функции с такими вызовами:
initiateTransferProtocol("http", false, 150, 90, null, true, 18);
Полностью нечитабельно, если вы не проводите какое-либо исследование. С другой стороны, этот код хорошо читается:
initiateTransferProtocol({
"protocol": "http",
"sync": false,
"delayBetweenRetries": 150,
"randomVarianceBetweenRetries": 90,
"retryCallback": null,
"log": true,
"maxRetries": 18
});
Это скорее искусство, чем наука, но если бы мне пришлось называть эмпирические правила:
Использовать параметры параметр, если:
Возможно, я немного опоздал на вечеринку с этим ответом, но я искал мнения других разработчиков по этой теме и наткнулся на эту тему.
Я очень не согласен с большинством респондентов и сторон с подходом «множественных аргументов». Мой главный аргумент заключается в том, что он не поощряет другие анти-шаблоны, такие как «мутация и возвращение объекта param» или «передача одного и того же объекта param на другие функции». Я работал в кодовых базах, которые сильно злоупотребляли этим анти-шаблоном, и код отладки, который делает это быстро, становится невозможным. Я думаю, что это очень специфичное для Javascript правило, поскольку Javascript не сильно типизирован и допускает такие произвольно структурированные объекты.
Мое личное мнение заключается в том, что разработчики должны быть явными при вызове функций, избегать прохождения избыточные данные и избегать модификации по ссылке. Дело не в том, что эти шаблоны не позволяют писать сжатый, правильный код. Я просто чувствую, что для вашего проекта намного легче впасть в плохие методы разработки.
Рассмотрим следующий ужасный код:
function main() {
const x = foo({
param1: "something",
param2: "something else",
param3: "more variables"
});
return x;
}
function foo(params) {
params.param1 = "Something new";
bar(params);
return params;
}
function bar(params) {
params.param2 = "Something else entirely";
const y = baz(params);
return params.param2;
}
function baz(params) {
params.params3 = "Changed my mind";
return params;
}
Не только этот тип требует более явного документацию, чтобы указать намерение, но она также оставляет место для неопределенных ошибок. Что делать, если разработчик изменяет param1
в bar()
? Как долго, по-твоему, понадобится проглядеть кодовую базу достаточного размера, чтобы поймать это? По общему признанию, этот пример немного неискренен, поскольку предполагает, что разработчики уже совершили несколько анти-шаблонов к этому моменту. Но это показывает, как передающие объекты, содержащие параметры, позволяют больше места для ошибки и двусмысленности, требуя большей степени добросовестности и соблюдения корректности const.
Только мои двухценты в этом вопросе!
Несколько аргументов в основном относятся к обязательным параметрам. Нет ничего плохого в них.
Если у вас есть дополнительные параметры, он становится сложным. Если один из них полагается на других, так что у них есть определенный порядок (например, четвертый нуждается в третьем), вы все равно должны использовать несколько аргументов. Практически все родные EcmaScript и DOM-методы работают так. Хорошим примером является метод open
XMLHTTPrequests , где последние 3 аргумента являются необязательными - правило похоже на «без пароля без пользователя» (см. Также MDN docs ).
Опционные объекты пригождаются в двух случаях:
undefined
s). В вашем случае я рекомендую map(nodeList, callback, options)
. nodelist
и callback
требуются, остальные три аргумента происходят только изредка и имеют разумные значения по умолчанию.
Другим примером является JSON.stringify
. Вы можете использовать параметр space
без передачи функции replacer
- тогда вы должны вызвать …, null, 4)
. Объект arguments мог быть лучше, хотя его не очень разумно только для 2 параметров.
Я бы посмотрел на большие проекты javascript.
Такие вещи, как google map, вы часто увидите, что объекты-объекты, требующие объекта, требуют объекта, но функции требуют параметров. Я думаю, что это связано с аргументами OPTION.
Если вам нужны аргументы по умолчанию или необязательные аргументы, объект, вероятно, будет лучше, потому что он более гибкий. Но если вы не используете нормальные функциональные аргументы, более явные.
Javascript также имеет объект arguments
. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments
Использование подхода «параметры как объект» будет лучше. Вам не нужно беспокоиться о порядке свойств и есть большая гибкость в том, какие данные передаются (например, необязательные параметры)
Создание объекта также означает, что параметры могут быть легко использованы для нескольких функций:
options={
nodeList:...,
callback:...,
thisObject:...,
fromIndex:...,
toIndex:...
}
function1(options){
alert(options.nodeList);
}
function2(options){
alert(options.fromIndex);
}
Объект предпочтительнее, потому что если вы передаете объект, его легко расширить количество свойств в этих объектах, и вам не нужно следить за порядком, в котором были переданы ваши аргументы.
Для функции, которая обычно использует некоторые предопределенные аргументы, вам лучше использовать объект option. Простой пример будет чем-то вроде функции, которая получает бесконечное количество аргументов, таких как: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).
Ваш комментарий к вопросу:
в моем примере последние три являются необязательными.
blockquote>Так почему бы не сделать это? (Примечание: это довольно грубый Javascript. Обычно я использую хэш
default
и обновляю его с помощью опций, переданных с помощью Object.extend или JQuery.extend или аналогичный.)function map(nodeList, callback, options) { options = options || {}; var thisObject = options.thisObject || {}; var fromIndex = options.fromIndex || 0; var toIndex = options.toIndex || 0; }
Итак, теперь, поскольку теперь гораздо более очевидно то, что является необязательным, а что нет, все это действительное использование функции:
map(nodeList, callback); map(nodeList, callback, {}); map(nodeList, callback, null); map(nodeList, callback, { thisObject: {some: 'object'}, }); map(nodeList, callback, { toIndex: 100, }); map(nodeList, callback, { thisObject: {some: 'object'}, fromIndex: 0, toIndex: 100, });
Это зависит.
Основываясь на моем наблюдении за дизайном этих популярных библиотек, рассмотрим сценарии использования объекта option:
И сценарии использования списка параметров:
Я думаю, что если вы что-то создаете или называете метод объекта, вы хотите использовать объект options. Если это функция, которая работает только с одним или двумя параметрами и возвращает значение, предпочтительнее список аргументов.
В некоторых случаях полезно использовать оба параметра. Если у вашей функции есть один или два требуемых параметра и куча необязательных, сделайте первые два необходимых параметра, а третий - хэш дополнительных опций.
В вашем примере я сделал бы map(nodeList, callback, options)
. Нумеру и обратный вызов требуются, довольно легко рассказать, что происходит, просто прочитав звонок, и это похоже на существующие функции карты. Любые другие опции могут быть переданы как дополнительный третий параметр.