Сразу скажу, что про ФЯ не знаю практически ничего, но есть большое желание разобраться. Открыл вот недавно документацию по Erlang — и сразу запнулся... Да, на первый взгляд, все там и хорошо, и красиво, но как только пробую "примерить" Erlang к чему-то "жизненному", сразу понимаю, что чего-то я тут не понимаю.
Впрочем, перейду к делу. Вот простейшая задача, которая элементрано решается на C++ (как впрочем, и любом другом ООП языке): Есть приложение, которое работает с базой данных. Хочется, чтобы в зависимости от аргументов командной строки, оно конектилось либо к.. ммм.. ну пусть будет mysql, либо к oracle.
В "обычном" языке я б сделал нечто подобное:
class Driver
{
public:
virtual void connect() = 0;
};
class MysqlDriver : public Driver {}; //implements connect() using some mysql API calls.
class OracleDriver : public Driver {}; //implements connect() using some oracle API calls.
И, наконец,
class DriverFactory
{
public:
static Driver * createDriver( string name ) //creates necessary driver
{
Driver * driver = 0;
if("mysql" == name ) driver = new MysqlDriver();
else if( "oracle" == name ) driver = OracleDriver();
else throw 1;
return driver;
}
};
Внимание, вопрос: как подобная задача решается с помощью функциональных языков??
Здравствуйте, Аноним, Вы писали:
А>Сразу скажу, что про ФЯ не знаю практически ничего, но есть большое желание разобраться. Открыл вот недавно документацию по Erlang — и сразу запнулся... Да, на первый взгляд, все там и хорошо, и красиво, но как только пробую "примерить" Erlang к чему-то "жизненному", сразу понимаю, что чего-то я тут не понимаю.
В Эрланге было бы примерно так (насколько я представляю, хотя могу заблуждаться): был бы процесс (некий репозиторий), который мапит (используя паттерн матчинг, см. доки по Эрлангу) названия драйверов в процессы этих драйверов (посылает обратно PID нужного драйвера), дальше уже этому драйверу посылаются сообщения, которые он разбирает. Хотя с практической точки зрения такое нужно не особо часто, чтобы было дофига дбшных драйверов, поэтому плодить сущности не обязательно и обойтись можно одним процессом-драйвером БД.
Re[2]: ФЯ и полиморфизм
От:
Аноним
Дата:
17.09.06 18:28
Оценка:
Здравствуйте, Quintanar, Вы писали:
Q>У тебя задачи, собственно, нет, поэтому сложно догадаться, что именно тебя интересует.
Окей, перефразирую вопрос — что в функциональных языках является аналогом связки "полиморфизм+фабрика" ? В повседневной работе она встречается сплошь и рядом, на месте приведенного мной примера кода работы с базой могло быть все что угодно: например, так мы могли загружать сериализованные объекты, считывать команлы сетевого протокола и т.д. и т.п.
>> как подобная задача решается с помощью функциональных языков??
Q>Ну, например, так. Q>
Q>let get_connect name =
Q> if name = "mysql" then mysql_connect else oracle_connect
Q>let main arg =
Q> let connect = get_connect arg in
Q> bla-bla-bla
Q>
Стоп, я наверное неправильно понял, но допустим, что у нас есть не только функция connect(), но и куча других функций, необходимых для работы с базой:
сlose(), commit() и т.д. Неужели мне для каждой из этих функций придется делать проверку параметра name? т.е. писать:
let get_close name =
if name = "mysql" then mysql_close else oracle_close
let get_commit name =
if name = "mysql" then mysql_commit else oracle_commit
Но ведь с тем же успехом я мог бы писать и на чистом C, проверяя в каждой функции параметр или глобальную переменную name!
Разве это не шаг назад по сравнению с приведенным мной полиморфным кодом??
Re[2]: ФЯ и полиморфизм
От:
Аноним
Дата:
17.09.06 18:37
Оценка:
К>В Эрланге было бы примерно так (насколько я представляю, хотя могу заблуждаться): был бы процесс (некий репозиторий), который мапит (используя паттерн матчинг, см. доки по Эрлангу) названия драйверов в процессы этих драйверов (посылает обратно PID нужного драйвера), дальше уже этому драйверу посылаются сообщения, которые он разбирает.
Да, походу ты прав — в Erlang'е полиморфизм вроде как реализован только на уровне процессов. Но в чем смысл этого решения? Мне же не всегда нужны активные полиморфные объекты! Все как раз наоборот...
К> Хотя с практической точки зрения такое нужно не особо часто, чтобы было дофига дбшных драйверов, поэтому плодить сущности не обязательно и обойтись можно одним процессом-драйвером БД.
Вот тут не соглашусь — дело не в базе, а в том, что на практике связка "полиморфизм+фабрика" встречается постоянно. Кроме того, допустим, я пишу не приложение, а библиотеку, которая должна уметь работать с множеством различных баз — тут приведенное мной решение по сути дела является "стандартным". В общем, задача имхо очень жизненная и, естетсвенно, должна как-то элегантно решаться на функциональных языках — только вот как?
Здравствуйте, Аноним, Вы писали:
Q>>Ну, например, так. Q>>
Q>>let get_connect name =
Q>> if name = "mysql" then mysql_connect else oracle_connect
Q>>let main arg =
Q>> let connect = get_connect arg in
Q>> bla-bla-bla
Q>>
А>Стоп, я наверное неправильно понял, но допустим, что у нас есть не только функция connect(), но и куча других функций, необходимых для работы с базой: А>сlose(), commit() и т.д. Неужели мне для каждой из этих функций придется делать проверку параметра name? т.е. писать:
В языках со статической типизацией могла бы быть так (схематично а-ля хаскель):
module DB where
-- ATDs:
data DBDriver;
data DBConnection;
data DBQueryHandler;
data DBResult;
data DBValue;
-- Type synonyms:
type DSN = String -- Data source
type DBQuery = String
-- Functions
connect :: DBDriver -> DSN -> DBConnection
prepare :: DBConnection -> DBQuery -> DBQueryHandler
execute :: DBQueryHandler -> [DBValue] -> DBResult
fetchAll :: DBResult -> [[DBValue]]
-- code
module Main where
import DB;
import MySql;
-- or
-- import Oracle;
getDriver :: String -> DBDriver
getDriver "mysql" = get_mysql_driver
getDriver "oracle" = get_ora_driver
getDriver x = error $ "unknown driver: " ++ x
main = let driver = getDriver "mysql"
conn = connect driver "path:to:database"
sth = prepare conn "SELECT * FROM test"
result = execute [] sth
resultSet = fetch result
resultValue = resultSet !! 0 !! 0 -- first row / first col
in print resultValue
Это все не привлекая классы типов, ML-модули, функторы и прочие навороты. В динамических языках типа эрланга и лиспа вообще все просто в этом смысле — фактически так же как в перле или руби.
Разумеется, это не рабочий код, а просто пример как могло бы выглядеть. За конкретными примерами обращаться к документации конкретных библиотек.
прежде чем понять рекурсию, необходимо понять рекурсию.
Абстрагирование в Эрланге возможно в двух направлениях:
1. Скрыть детали в процесс, и обращаться к нему через заданный протокол.
2. Скрыть детали в модуле, и обращаться к нему через экспортируемые функции.
Полиморфизм заменяется ФВП и паттерн-матчингом.
Вот один из прямолинейных вариантов: (нет обработки ошибок и исключительных ситуаций).
main() ->
Connection = driver:connect(mysql),
Data = driver:executeQuery(Connection, "..."),
% ... что-нибудь делаем с Data
driver:disconnect(Connection).
driver:connect использует паттерн-матчинг чтобы сформировать множество функций и других данных, необходимых для выполнения функции driver:executeQuery.
Если в общем виде эмулировать классы, то состояние класса представляется в виде кортежа {classname, state}, реализация методов содержится в модуле classname, публичный интерфейс класса — это экспортируемый интерфейс модуля.
class(Object) ->
element(0, Object).
some_generic_function(Object) ->
Class = class(Object),
Class:method1(Object).
А>class Driver
А>{
А>public:
А> virtual void connect() = 0;
А>};
А>class MysqlDriver : public Driver {}; //implements connect() using some mysql API calls.
А>class OracleDriver : public Driver {}; //implements connect() using some oracle API calls.
А>И, наконец,
А>class DriverFactory
А>{
А>public:
А> static Driver * createDriver( string name ) //creates necessary driver
А> {
А> Driver * driver = 0;
А> if("mysql" == name ) driver = new MysqlDriver();
А> else if( "oracle" == name ) driver = OracleDriver();
А> else throw 1;
А> return driver;
А> }
А>};
А>
А>Внимание, вопрос: как подобная задача решается с помощью функциональных языков??
Вариант 1
Каждый драйвер живет в своем модуле. Тогда, собственно, ты передаешь имя модуля — атом, туда, куда тебе нужно. А твой код, работающий с БД параметризован именем модуля "драйвера".
Вариант 2
Кроме этого, заворачиваешь свой модуль в процесс.
Здравствуйте, <Аноним>, Вы писали:
А>Вот тут не соглашусь — дело не в базе, а в том, что на практике связка "полиморфизм+фабрика" встречается постоянно. Кроме того, допустим, я пишу не приложение, а библиотеку, которая должна уметь работать с множеством различных баз — тут приведенное мной решение по сути дела является "стандартным". В общем, задача имхо очень жизненная и, естетсвенно, должна как-то элегантно решаться на функциональных языках — только вот как?
Не будучи практиком ФП-программирования, могу предложить решение в лоб: сделать фабрику в виде ФВП.
А>Есть приложение, которое работает с базой данных. Хочется, чтобы в зависимости от аргументов командной строки, оно конектилось либо к.. ммм.. ну пусть будет mysql, либо к oracle.
Здравствуйте, Аноним, Вы писали:
А>Да, походу ты прав — в Erlang'е полиморфизм вроде как реализован только на уровне процессов. Но в чем смысл этого решения? Мне же не всегда нужны активные полиморфные объекты! Все как раз наоборот...
В эрланге процес — аналог объекта.
У него даже состояние модет быть (словарь процесса).
Процесс — легкий, то есть уж по процессу на драйвер точно можешь делать.
Задумываться стоит начать если процессов будет получаться больше 200 000...
А их "активность"может быть весьма полезной... например процесс в фоне может иногда "щупать" соединение с БД, или запускать что-нибудь типа "VACUUM..." когда нет никаких запросов.