Я тут решил познакомиться с Ruby и наткнулся code block's, которые
позволяют реализовать вот такие интересные вещи:
class File
def File.open_and_process(*args)
f = File.open(*args)
yield f
f.close()
end
end
File.open_and_process("file", "r") do |file|
while line = file.gets
puts line
end
end
То есть, мы, как создатели класса File можем гарантировать закрытие файла,
не полагаясь на память и внимательность пользователя этого класса.
Похожую вещь можно сделать и в Python с помощью ключевого слова with
(http://expl0rer.wordpress.com/2009/03/04/with_in_python/), но даже в этом
случае мы не застрахованы от того, что пользователь при работе с классом
забудет о использовании слова with.
Наверное, в Python можно попробовать использовать декораторы, но я сходу не
нашел возможности получить из декоратора параметр (файловый дескриптор, в
данном случае). Такая возможность есть или параметры передаются только в
декоратор?
Если бы данную задачу решать на обобщенном ООП языке, то наверное можно
было бы воспользоваться примерно такой конструкцией:
Реализовать базовый класс:
Base(object):
private file;
public work_with_file(file):
pass
public run():
file = openfile()
work_with_file(file)
close_file(file)
и заставить пользователя создавать для работы с файлами наследника от
моего, перекрывая метод work_with_file(file):
First(Base):
public work_with_file(file):
// do some work with file
Может есть какие-то более удобные паттерны и приемы, не привязанные к
конкретному языку программирования, достигать такой же цели — гарантировать
освобождение ресурсов?
Здравствуйте, DemAS, Вы писали:
DAS>Может есть какие-то более удобные паттерны и приемы, не привязанные к DAS>конкретному языку программирования, достигать такой же цели — гарантировать DAS>освобождение ресурсов?
Конечно есть — для этого нужны первоклассные функции. Вот реализации на нескольких языках — общее уловить несложно:
— Если в языке нет аналога finally(unwind-protect) — написать его
— передавать то, что нужно сделать сделать с ресурсом в виде анонимной функции
// Scala - есть готовый finally - все просто
def withResource[A](f : Resource => A) : A = {
val r = getResource() // Replace with the code to acquire the resourcetry {
f(r)
} finally {
r.dispose()
}
}
//Client code becomes very simple:
withResource{ r =>
// do stuff with r....
}
// SML
// First of all, a function such as
fun finally (thunk, effect) =
(((fn x => fn () => x) (thunk ())
handle e => fn () => raise e) o effect) ()
// can be provided to make sure that a given effect will be performed after a
// given thunk returns - whether normally or by raising an exception.
// It is customary to provide functions that allocate a resource, pass the
// resource to a given body function, and take care of deallocating the
// resource. For example, one could provide the function
fun withInputString string =
fn body =>
let
val instream = TextIO.openString string
in
finally (fn () => body instream,
fn () => TextIO.closeIn instream)
end
// for executing a body with an instream that reads from a string.
// Another example of such a scoped resource management function could be
fun withDirStream path =
fn body =>
let
val dirstream = OS.FileSys.openDir path
in
finally (fn () => body dirstream,
fn () => OS.FileSys.closeDir dirstream)
end
// for executing a body with a directory stream. As you can see, there is
// some duplication in structure between the above two functions that could
// easily be factored out, but I'll ignore that aspect in this article.
--// "библиотечная" функция
--// pcall - это аналог try-catch
local function unwind_protect(thunk,cleanup)
local ok, result = pcall(thunk)
if cleanup then cleanup() end
if not ok then
error(res,0) -- // rethrow exception else
return result
end
end
--// общая функция для работы с открытыми файлами
local function with_open_file(name,mode)
return function(body)
local f = assert(io.open(name,mode))
return unwind_protect(function()return body(f) end,
function()return f and f:close() end)
end
end
--// usage: os-copy --
function os_copy2(source_path,dest_path)
return with_open_file(source_path,"rb") (function(source)
return with_open_file(dest_path,"wb") (function(dest)
assert(dest:write(assert(source:read("*a"))))
return 'copy ok'
end)
end)
end
Если бы я знал, как в Питоне передавать анонимные функции как аргументы (lambda?) — я добавил бы пример.
Здравствуйте, z00n, Вы писали:
Z>Здравствуйте, DemAS, Вы писали:
DAS>>Может есть какие-то более удобные паттерны и приемы, не привязанные к DAS>>конкретному языку программирования, достигать такой же цели — гарантировать DAS>>освобождение ресурсов?
Скип
А в языках без поддержки ФВП можно использовать GOF-паттерн Command.
<< RSDN@Home 1.2.0 alpha 4 rev. 1128>>
Сейчас играет Тінь Сонця — Гнилгород
Здравствуйте, DemAS, Вы писали:
DAS>Я тут решил познакомиться с Ruby и наткнулся code block's, которые DAS>позволяют реализовать вот такие интересные вещи:
DAS>
DAS>class File
DAS> def File.open_and_process(*args)
DAS> f = File.open(*args)
DAS> yield f
DAS> f.close()
DAS> end
DAS>end
DAS>File.open_and_process("file", "r") do |file|
DAS> while line = file.gets
DAS> puts line
DAS> end
DAS>end
DAS>
DAS>То есть, мы, как создатели класса File можем гарантировать закрытие файла, DAS>не полагаясь на память и внимательность пользователя этого класса.
Ага. Ну, или, аналогичным образом мы могли бы сделать эту вешчь на дотнете:
var s = f.OpenRead(); // нененене, Дэвид Блейн!var buf = new byte[1024]; // уйди, демон!
s.Read(buf, 0, buf.Length); // мы же забыли вызвать Dispose!
/// А теперь немного уличной магии:
f.OpenRead(s => {
var buf = new byte[1024];
s.Read(buf, 0, buf.Length);
}); // я сделал вам Dispose(),
// осталось скукожить "обычный" OpenRead!
А вот всё, что понадобилось, чтобы оно компилялось:
public static class FileHelper
{
public static void OpenRead(this FileInfo fi, Action<FileStream> action)
{
using (var s = fi.OpenRead())
action(s);
}
}
DAS>Если бы данную задачу решать на обобщенном ООП языке, то наверное можно DAS>было бы воспользоваться примерно такой конструкцией:
DAS>Может есть какие-то более удобные паттерны и приемы, не привязанные к DAS>конкретному языку программирования, достигать такой же цели — гарантировать DAS>освобождение ресурсов?
Вообще говоря — да. Вся идея выражается одним словом: callback.
То есть вместо того, чтобы дать пользователю неконтролируемый доступ к некоторому ресурсу (например, FileStream), мы заставляем его передать нам объект, в который мы дадим ресурс на время.
В моем примере всё построено на делегате Action<FileStream>. В жаве, к примеру, то же самое было бы сделано в виде интерфейса (а прикладной код был бы построен на анонимных классах).
Требовать наследования в данном случае — оверкилл. Попробуй себе представить код, который копирует из файла A в файл B.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Z>Если бы я знал, как в Питоне передавать анонимные функции как аргументы (lambda?) — я добавил бы пример.
Угу. Идею понял:
def withResource(f):
resource = open('decor1.py')
try:
f(resource)
finally:
resource.close()
def readlines(resource):
for line in resource:
print line
withResource(readlines)
Ок. В языках. допускающих использование функций, как параметр другой функции возможно такое решение.
В C++, наверное, можно воспользоваться указателем на функцию.
DAS>>Может есть какие-то более удобные паттерны и приемы, не привязанные к DAS>>конкретному языку программирования, достигать такой же цели — гарантировать DAS>>освобождение ресурсов? S>Вообще говоря — да. Вся идея выражается одним словом: callback.
"Скобки".
bracket open close action = do
h <- open
action h
close h
И даже имеет некоторое выражение в самих языках программирования. Собственно, открывая скобку { мы даём программисту возможность что-то делать с контекстом, который мы гарантировано вернём в исходное состояние путём закрывающей скобки }.
Они встречаются и в других местах, где надо контролировать состояние, glBegin/glEnd приходит в голову первым.
И вообще, такой подход позволяет тесней увязать языки программирования и библиотеки, получая DS(E)L на выходе.
S>То есть вместо того, чтобы дать пользователю неконтролируемый доступ к некоторому ресурсу (например, FileStream), мы заставляем его передать нам объект, в который мы дадим ресурс на время. S>В моем примере всё построено на делегате Action<FileStream>. В жаве, к примеру, то же самое было бы сделано в виде интерфейса (а прикладной код был бы построен на анонимных классах). S>Требовать наследования в данном случае — оверкилл. Попробуй себе представить код, который копирует из файла A в файл B.
withFile name action = bracket (openFile name) closeHandle action
copy a b = withFile a $
\ah -> withFile b $ \bh -> read ah >>= \text -> write bh text
В скобочной нотации как раз в самый раз.
Да и в с объектами-наследниками "хранителя файла" тоже должно получиться неплохо, хотя и более громоздко.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
DAS>class File
DAS> def File.open_and_process(*args)
DAS> f = File.open(*args)
DAS> yield f
DAS> f.close()
DAS> end
DAS>end
DAS>
DAS>То есть, мы, как создатели класса File можем гарантировать закрытие файла, DAS>не полагаясь на память и внимательность пользователя этого класса.
Гарантировать? А исключения как же?
Впрочем, переписать с использованием begin/rescue не сложно.
DAS>Может есть какие-то более удобные паттерны и приемы, не привязанные к DAS>конкретному языку программирования, достигать такой же цели — гарантировать DAS>освобождение ресурсов?
Здравствуйте, DemAS, Вы писали:
Z>>Если бы я знал, как в Питоне передавать анонимные функции как аргументы (lambda?) — я добавил бы пример.
DAS>Угу. Идею понял:
DAS>
DAS>def withResource(f):
DAS> resource = open('decor1.py')
DAS> try:
DAS> f(resource)
DAS> finally:
DAS> resource.close()
DAS>def readlines(resource):
DAS> for line in resource:
DAS> print line
DAS>withResource(readlines)
DAS>
DAS>Ок. В языках. допускающих использование функций, как параметр другой функции возможно такое решение.
Различие в добавлении сахара.
Вот в питоне, к примеру, невозможно сделать с лямбдой более одного действия и приходится придумывать локальную функцию.
А в руби можно передать сразу анонимный блок.
DAS>В C++, наверное, можно воспользоваться указателем на функцию.
В С++ пользуются шаблоннами. Смотреть Standard Library:
Здравствуйте, thesz, Вы писали:
T>В скобочной нотации как раз в самый раз.
Это понятно. T>Да и в с объектами-наследниками "хранителя файла" тоже должно получиться неплохо, хотя и более громоздко.
Вот это — нет. Непонятно, как мы будем наследоваться от хранителя файла дважды.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, DemAS, Вы писали:
... DAS>Ок. В языках. допускающих использование функций, как параметр другой функции возможно такое решение. DAS>В C++, наверное, можно воспользоваться указателем на функцию.
В С++ есть деструкторы и все ресурсы, которые были инициализированы, будут гарантированно освобождены.
{
std::ifstream f("1.txt");
// работаем с файлом
...
//закрытие файла при уничтожении переменной f
}
>> Ага, деструкторы.
DAS>В управляемых языках, насколько я понимаю, деструктор вызывается при сборке DAS>объекта GC, и когда он уничтожит наш объект мы гарантировать не можем.