Необязательные параметры Ruby

121

Если я определю функции Ruby следующим образом:

def ldap_get ( base_dn, filter, scope=LDAP::LDAP_SCOPE_SUBTREE, attrs=nil )

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

ldap_get( base_dn, filter, , X)

возможно или, если возможно, как это сделать?

Бруно Антунес
источник

Ответы:

131

В настоящее время это невозможно с рубином. Вы не можете передавать "пустые" атрибуты методам. Самое близкое, что вы можете получить, - передать nil:

ldap_get(base_dn, filter, nil, X)

Однако это установит для области значение nil, а не LDAP :: LDAP_SCOPE_SUBTREE.

Что вы можете сделать, так это установить значение по умолчанию в своем методе:

def ldap_get(base_dn, filter, scope = nil, attrs = nil)
  scope ||= LDAP::LDAP_SCOPE_SUBTREE
  ... do something ...
end

Теперь, если вы вызовете метод, как указано выше, поведение будет таким, как вы ожидаете.

tomafro
источник
21
Небольшая ошибка с этим методом: например, если вы пытаетесь установить значение по умолчанию для scopetrue и вы переходите false, scope ||= trueне сработает. Он оценивает то же самое nilи будет устанавливать его наtrue
Джошуа Пинтер
4
возможно ли это с текущей версией ruby ​​через 3 года после этого ответа?
dalloliogm
1
@JoshPinter, хорошее объяснение. По сути, || = не a = b или c, я съежился, увидев xyz||=true. Он говорит, что если это ноль, это всегда правда. Если это правда, это правда.
Дэймон Ав
7
Когда все говорят, насколько это плохо scope ||= true, я удивлен, что никто не упомянул, что лучший способ сделать это - использовать scope = LDAP::LDAP_SCOPE_SUBTREE if scope.nil?. Конечно, даже это предполагает nilнедопустимое значение.
Эрик Сэндберг,
1
Обновление этой старой версии: альтернативой является использование символа подчеркивания. К сожалению, это имеет тот же эффект, что и установка параметра на nil. Некоторым может понравиться обозначение: ldap_get(base_dn, filter, _, X)(примечание: я не знаю (пока), когда это было введено в Ruby. Интересная тема SO ).
Эрик Платон
137

Практически всегда лучше использовать хеш опций.

def ldap_get(base_dn, filter, options = {})
  options[:scope] ||= LDAP::LDAP_SCOPE_SUBTREE
  ...
end

ldap_get(base_dn, filter, :attrs => X)
jshen
источник
23
Распространенная стратегия - иметь хэш опций по умолчанию и объединять все, что было передано:options = default_options.merge(options)
Натан Лонг,
7
Я не одобряю этого, потому что варианты не сообщают вам, что ожидает метод или какие значения по умолчанию
Брон Дэвис
53

Время прошло, и начиная с версии 2 Ruby поддерживает именованные параметры:

def ldap_get ( base_dn, filter, scope: "some_scope", attrs: nil )
  p attrs
end

ldap_get("first_arg", "second_arg", attrs: "attr1, attr2") # => "attr1, attr2"
steenslag
источник
1
Вы также можете использовать двойной знак для сбора дополнительных неопределенных аргументов ключевого слова. Это связано с этой проблемой: stackoverflow.com/a/35259850/160363
Генри Ценг,
3

Это невозможно сделать так, как вы определили ldap_get. Однако, если вы определите ldap_getтак:

def ldap_get ( base_dn, filter, attrs=nil, scope=LDAP::LDAP_SCOPE_SUBTREE )

Теперь вы можете:

ldap_get( base_dn, filter, X )

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

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

Крис Банч
источник
1

1) Вы не можете перегрузить метод ( почему Ruby не поддерживает перегрузку метода? ), Так почему бы вообще не написать новый метод?

2) Я решил аналогичную проблему с помощью оператора splat * для массива нулевой или большей длины. Затем, если я хочу передать параметр (ы), который я могу, он интерпретируется как массив, но если я хочу вызвать метод без какого-либо параметра, мне не нужно ничего передавать. См. Страницы языка программирования Ruby 186/187.

rupweb
источник
0

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

Я смоделировал это, передав массив в качестве параметра, а затем проверив, было ли значение в этом индексе нулем или нет.

class Array
  def ascii_to_text(params)
    param_len = params.length
    if param_len > 3 or param_len < 2 then raise "Invalid number of arguments #{param_len} for 2 || 3." end
    bottom  = params[0]
    top     = params[1]
    keep    = params[2]
    if keep.nil? == false
      if keep == 1
        self.map{|x| if x >= bottom and x <= top then x = x.chr else x = x.to_s end}
      else
        raise "Invalid option #{keep} at argument position 3 in #{p params}, must be 1 or nil"
      end
    else
      self.map{|x| if x >= bottom and x <= top then x = x.chr end}.compact
    end
  end
end

Пробуем использовать наш метод класса с разными параметрами:

array = [1, 2, 97, 98, 99]
p array.ascii_to_text([32, 126, 1]) # Convert all ASCII values of 32-126 to their chr value otherwise keep it the same (That's what the optional 1 is for)

вывод: ["1", "2", "a", "b", "c"]

Ладно, круто, работает как задумано. Теперь давайте проверим и посмотрим, что произойдет, если мы не передадим третий параметр (1) в массив.

array = [1, 2, 97, 98, 99]
p array.ascii_to_text([32, 126]) # Convert all ASCII values of 32-126 to their chr value else remove it (1 isn't a parameter option)

вывод: ["a", "b", "c"]

Как вы можете видеть, третий вариант в массиве был удален, тем самым инициировав другой раздел в методе и удалив все значения ASCII, которые не находятся в нашем диапазоне (32-126)

В качестве альтернативы мы могли указать в параметрах значение nil. Это будет похоже на следующий блок кода:

def ascii_to_text(top, bottom, keep = nil)
  if keep.nil?
    self.map{|x| if x >= bottom and x <= top then x = x.chr end}.compact
  else
    self.map{|x| if x >= bottom and x <= top then x = x.chr else x = x.to_s end}
end
сингулярность
источник
-1

Возможно :) Просто измените определение

def ldap_get ( base_dn, filter, scope=LDAP::LDAP_SCOPE_SUBTREE, attrs=nil )

в

def ldap_get ( base_dn, filter, *param_array, attrs=nil )
scope = param_array.first || LDAP::LDAP_SCOPE_SUBTREE

область видимости теперь будет в массиве на первом месте. Когда вы указываете 3 аргумента, вам будут назначены base_dn, filter и attrs, а param_array будет [] Если 4 и более аргументов, тогда param_array будет [argument1, or_more, and_more]

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

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

m4risU
источник
4
Это решение совершенно неверно. Использование параметра значения по умолчанию ( attrs=nil) после splat ( *param_array) является синтаксической ошибкой .
Эрик Сандберг
3
-1: Эрик прав. Вызывает синтаксическую ошибку в irb 2.0.0p247. Согласно языку программирования Ruby , в Ruby 1.8 параметр splat должен был быть последним, за исключением символа &parameter, но в Ruby 1.9 за ним также могли следовать «обычные параметры». Ни в том, ни в другом случае параметр с допустимым значением по умолчанию не был после параметра со знаком.
andyg0808
На странице 186/187 языка программирования Ruby splat можно использовать с методами. Это должен быть последний параметр в методе, если не используется &.
rupweb
Итак, AndyG прав, порядок должен быть следующим: def ldap_get (base_dn, filter, attrs = nil, * param_array)
rupweb
-1

Вы можете сделать это с частичным приложением, хотя использование именованных переменных определенно приводит к более читаемому коду. Джон Ресиг в 2008 году написал в блоге статью о том, как это сделать в JavaScript: http://ejohn.org/blog/partial-functions-in-javascript/

Function.prototype.partial = function(){
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function(){
    var arg = 0;
    for ( var i = 0; i < args.length && arg < arguments.length; i++ )
      if ( args[i] === undefined )
        args[i] = arguments[arg++];
    return fn.apply(this, args);
  };
};

Вероятно, можно было бы применить тот же принцип в Ruby (за исключением прототипного наследования).

EriF89
источник