Я новичок в рубине, но до сих пор наслаждаюсь им. Есть некоторые вещи, которые дали мне некоторые проблемы, и следующее не является исключением.
То, что я пытаюсь сделать здесь, это создать своего рода "супер-каталог" путем подклассификации "Dir". Я добавил метод поддиалогов, который предназначен для отображения файлов объектов каталога и вставки их в массив, если файл является самой папкой. Проблема в том, что результаты моего теста (File.directory?) Странны - вот мой код метода:
И странно, хотя в каталоге, который я выбрал ( "/tmp" ), есть много каталогов - результат этого вызова содержит только списки ".". и ".."
Ответ 2
Марк Уэстлинг уже ответил на ваш ближайший вопрос, но, поскольку вы упоминаете, что вы новичок в Ruby, вот несколько других предложений стиля:
def subdirs
subdirs = Array.new
Обычно предпочтительнее использовать литерал-синтаксис, где это возможно, поэтому общий способ выразить это будет subdirs = []
.
self.each do |x|
self
- это неявный приемник в Ruby, поэтому вы можете просто оставить его. (В конце концов, это не Python.) Однако основная цель кода - общение, поэтому, если вы считаете, это лучше передает ваше намерение, оставьте его.
Говоря об общении: x
не является ужасно коммуникативным именем, если вы не говорите о точках в декартовой системе координат. Как насчет file
, или если вам неудобно понятие Unix, что каталоги также являются файлами, тем более нейтральным entry
? (На самом деле, лучший, вероятно, был бы path
, но мы скоро увидим, как это может запутаться.)
Мое третье предложение на самом деле является личным предпочтением, которое противоречит обычным рубиновым стилям: обычный стиль Ruby определяет, что однострочные блоки ограничены {
/}
, а многострочные блоки ограничены do
/end
, как и вы. Мне это не нравится, потому что это произвольное различие, которое не передает никакого смысла. (Помните, что "сообщение"?) Итак, я на самом деле делаю что-то по-другому: если блок необходим по своей природе и, например, изменяет какое-то глобальное состояние, я использую ключевые слова (потому что блок на самом деле do
), и если блок функциональный по своей природе и просто возвращает новый объект, я предпочитаю использовать фигурные скобки (потому что они выглядят хорошо математически). Итак, в этом случае я бы использовал фигурные скобки.
if File.directory?(x)
Как уже объяснил Марк, вам нужно сделать что-то вроде File.directory?(File.join(path, entry))
здесь, где Dir#path
является общедоступным атрибутом класса Dir
, возвращающим путь, который был инициализирован Dir.new
.
Здесь вы также видите, почему мы не использовали path
как имя параметра блока. В противном случае нам нужно было бы написать File.directory?(File.join(self.path, path))
.
subdirs.push(x)
Канонический способ добавления элемента к массиву или, действительно, почти что-либо добавить к чему-либо в Ruby, - это оператор <<
. Итак, это должно читать subdirs << entry
.
Array#push
является псевдонимом Array#<<
, который в основном предназначен для использования Array
в качестве стека (в сочетании с Array#pop
).
end
end
return subdirs
Вот еще один разрыв между моим личным стилем и обычным стилем Ruby: в Ruby, если нет явного return
, возвращаемое значение будет просто значением последнего выражения. Это означает, что вы можете оставить ключевое слово return
, а обычный стиль Ruby говорит, что вы должны. Однако я предпочитаю использовать это аналогично блочным разделителям: используйте return
для методов, которые являются функциональными по своей природе (потому что они фактически "возвращают" значение) и no return
для императивных методов (поскольку их реальное возвращаемое значение а не то, что происходит после ключевого слова return
, но какие побочные эффекты у них есть в среде). Итак, как и вы, я бы использовал ключевое слово return
, несмотря на общий стиль.
Также принято разделять возвращаемое значение с остальной части тела метода пустой строкой. (То же самое касается кода установки, кстати.)
end
Итак, вот где мы стоим прямо сейчас:
def subdirs
subdirs = []
each { |entry|
if File.directory?(File.join(path, entry))
subdirs << entry
end
}
return subdirs
end
Как вы можете видеть, выражение if
действительно служит только для пропуска одной итерации цикла. Это гораздо лучше, если использовать ключевое слово next
:
def subdirs
subdirs = []
each { |entry|
next unless File.directory?(File.join(path, entry))
subdirs << entry
}
return subdirs
end
Как вы можете видеть, нам удалось удалить один уровень вложенности из структуры блока, который всегда является хорошим знаком.
Эта идиома называется "оговоркой о защите" и довольно популярна в функциональном программировании, где многие языки даже имеют встроенные защитные конструкции, но также широко используются в любом другом языке на планете, потому что это значительно упрощает поток управления: идея аналогична охраннику, размещенному вне замка: вместо того, чтобы позволить плохим парням в замке (метод, процедура, функция, блок,...), а затем больно пытаться отслеживать каждое их движение и постоянно боясь пропустить что-то или потерять их, вы просто отправляете охранника у входа в свой замок (начало вашего метода, блок,...), который не позволяет им начинать (который прыгает до конца процедуры, возвращается раньше из метода, пропускает одну итерацию цикла,...). В Ruby вы можете использовать raise
, return
, next
и break
для этого. На других языках даже GOTO
является прекрасным (это один из тех редких случаев, когда GOTO
может реально очистить поток управления).
Однако мы можем упростить это еще больше, узнав шаблон итератора: вы берете список (записи каталога), а затем вы "сквоируете" этот список до одного объекта (массив subdirs
). Для теоретика категории это кричит "катаморфизм", хардкорному функциональному программисту "fold
", программисту-программисту "reduce
" программисту Smalltalk "inject:into:
" и рубисту "Enumerable#inject
":
def subdirs
return inject([]) { |subdirs, entry|
next subdirs unless File.directory?(File.join(path, entry))
subdirs << entry
}
end
inject
использует возвращаемое значение предыдущей итерации для семени следующего, поэтому нам нужно вернуть subdirs
, даже если мы пропустим итерацию, используя next subdirs
вместо plain next
( который возвратит nil
, так что на следующей итерации subdirs
будет nil
и subdirs << entry
будет поднимать NoMethodError
.)
(Обратите внимание, что я использовал фигурные скобки для блока, хотя блок фактически модифицирует его аргумент. Я считаю, что это все еще "функциональный" блок. YMMV.)
Последнее, что мы можем сделать, это признать, что то, что мы делаем, это просто фильтровать (или, другими словами, "выбирать" ) элементы массива. И Ruby уже имеет встроенный метод, который делает именно это: Enumerable#select
. Свидетель однолинейной удивительности, которая использует всю мощь Ruby:
def subdirs
return select { |entry| File.directory?(File.join(path, entry)) }
end
Как общий совет: узнайте чудеса Enumerable
. Это рабочая лошадь программирования Ruby, аналогичная IEnumerable<T>
в .NET, dict
в Python, списки в функциональных языках или ассоциативных массивах в PHP и Perl.