ФЯ и полиморфизм
От: Аноним  
Дата: 16.09.06 20:32
Оценка:
Сразу скажу, что про ФЯ не знаю практически ничего, но есть большое желание разобраться. Открыл вот недавно документацию по 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;
  }
};


Внимание, вопрос: как подобная задача решается с помощью функциональных языков??
Re: ФЯ и полиморфизм
От: Quintanar Россия  
Дата: 17.09.06 12:11
Оценка:
> как подобная задача решается с помощью функциональных языков??

Ну, например, так.
let get_connect name =
  if name = "mysql" then mysql_connect else oracle_connect

let main arg =
  let connect = get_connect arg in
  bla-bla-bla


У тебя задачи, собственно, нет, поэтому сложно догадаться, что именно тебя интересует.
Re: ФЯ и полиморфизм
От: Курилка Россия http://kirya.narod.ru/
Дата: 17.09.06 12:21
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Сразу скажу, что про ФЯ не знаю практически ничего, но есть большое желание разобраться. Открыл вот недавно документацию по 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'е полиморфизм вроде как реализован только на уровне процессов. Но в чем смысл этого решения? Мне же не всегда нужны активные полиморфные объекты! Все как раз наоборот...

К> Хотя с практической точки зрения такое нужно не особо часто, чтобы было дофига дбшных драйверов, поэтому плодить сущности не обязательно и обойтись можно одним процессом-драйвером БД.


Вот тут не соглашусь — дело не в базе, а в том, что на практике связка "полиморфизм+фабрика" встречается постоянно. Кроме того, допустим, я пишу не приложение, а библиотеку, которая должна уметь работать с множеством различных баз — тут приведенное мной решение по сути дела является "стандартным". В общем, задача имхо очень жизненная и, естетсвенно, должна как-то элегантно решаться на функциональных языках — только вот как?
Re[3]: ФЯ и полиморфизм
От: граммофон  
Дата: 17.09.06 22:50
Оценка:
Здравствуйте, Аноним, Вы писали:

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-модули, функторы и прочие навороты. В динамических языках типа эрланга и лиспа вообще все просто в этом смысле — фактически так же как в перле или руби.


Разумеется, это не рабочий код, а просто пример как могло бы выглядеть. За конкретными примерами обращаться к документации конкретных библиотек.
прежде чем понять рекурсию, необходимо понять рекурсию.
Re: ФЯ и полиморфизм
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 18.09.06 04:37
Оценка:
<Аноним>,

А>class Driver
А>{
А>public:
А>  virtual void connect() = 0;
А>};


А>Внимание, вопрос: как подобная задача решается с помощью функциональных языков?


Полагаю, что использование будет что-то типа:
void main(char * arcs[])
{
    Driver * driver = DriverFactory.createDriver("mysql");
    driver->connect();
    driver->executeQuery("...");
    driver->disconnect();
}


Абстрагирование в Эрланге возможно в двух направлениях:
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).
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re: ФЯ и полиморфизм
От: Gaperton http://gaperton.livejournal.com
Дата: 18.09.06 07:35
Оценка:
Здравствуйте, Аноним, Вы писали:

А>
А>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
Кроме этого, заворачиваешь свой модуль в процесс.

Ну а твой код превратится в примерно следующее:

create_driver( "mysql" ) -> create_driver( mysql_driver );
create_driver( "oracle" ) -> create_driver( oracle_driver );
create_driver( X ) -> { X, X:new() }.


Или так:

map_name( "mysql" ) -> mysql_driver;
map_name( "oracle" ) -> oracle_driver;

create_driver( Name ) ->
   Module = map_name( Name ),
   { Module, Module:new() }.
Re[3]: ФЯ и полиморфизм
От: Кодт Россия  
Дата: 18.09.06 08:41
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Вот тут не соглашусь — дело не в базе, а в том, что на практике связка "полиморфизм+фабрика" встречается постоянно. Кроме того, допустим, я пишу не приложение, а библиотеку, которая должна уметь работать с множеством различных баз — тут приведенное мной решение по сути дела является "стандартным". В общем, задача имхо очень жизненная и, естетсвенно, должна как-то элегантно решаться на функциональных языках — только вот как?


Не будучи практиком ФП-программирования, могу предложить решение в лоб: сделать фабрику в виде ФВП.
%% фабрика
db_provider(mysql)  -> { mysql_connect,  mysql_query,  mysql_disconnect  };
db_provider(oracle) -> { oracle_connect, oracle_query, oracle_disconnect }.

run_db(Type,Job) ->
    {connect,queryfunc,disconnect} = db_provider(Type),
    Handle = connect("my_base"),
    Executor = function(x) -> queryfunc(Handle,x) end,
    Job(Executor),
    disconnect(Handle).

some_job(Executor) ->
    selection1 = Executor("select * from my_table"),
    selection2 = Executor("select * from something_else"),
    .....
    .

run() -> run_db(mysql, some_job).
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re: ФЯ и полиморфизм
От: Mamut Швеция http://dmitriid.com
Дата: 18.09.06 13:15
Оценка:
А>Есть приложение, которое работает с базой данных. Хочется, чтобы в зависимости от аргументов командной строки, оно конектилось либо к.. ммм.. ну пусть будет mysql, либо к oracle.

В общем, придется курить, например, erlydb

Берем erlydb.erl:
start(Driver) ->
    start(Driver, []).
        
start(mysql, Options) ->
    [Hostname, Port, Username, Password, Database, OtherOptions] =
    lists:foldl(
      fun(Key, Acc) ->
          [proplists:lookup(Key, Options) | Acc]
      end, [],
      lists:reverse([hostname, port, username, password, database,
             options])),
    erlydb_mysql:connect(Hostname, Port, Username, Password, Database,
             OtherOptions);
    
start(_Driver, _Options) ->
    {error, driver_not_supported}.


то есть этот код вызывается тривиально:
erlydb:start(mysql).

% или там
erlydb:start(mnesia). % хотя поддержка mnesia еще не добавлена
                      % но планируется
... << RSDN@Home 1.2.0 alpha rev. 655>>


dmitriid.comGitHubLinkedIn
Рефакторинг по-эрланговски :)
От: Mamut Швеция http://dmitriid.com
Дата: 18.09.06 14:02
Оценка: 21 (2) :)
Тут я что-то подумал.

Если бы писал нечто подобное (мечты! мечты!), то, будучи насквозь императивщиком, я бы, наверное, проделал следующую эволюцию:

Сначала напишем через кучу case'ов (как в исходном сообщении
Автор:
Дата: 17.09.06
):
start(Driver) ->
    case Driver of
        mysql ->
            % совершаем коннект
            {};
        mnesia ->
            % совершаем коннект
            {};
        odbc ->
            % совершаем коннект
            {}
    end.


После чего я бы почесал репу, увидев, что процедура подсрединения к каждому драйверу довольно длинная, и переписал бы:

start(Driver) when Driver == mysql ->
    % совершаем коннект
    {};
start(Driver) when Driver == mnesia ->
    % совершаем коннект
    {};
start(Driver) when Driver == odbc ->
    % совершаем коннект
    {}.


А потом кто-нибудь (на RSDN или в erlang-questions) показал бы мне (а может, я и сам бы догадался — ), что все это дело можно еще сократить:
start(mysql) ->
    % совершаем коннект
    {};
start(mnesia) ->
    % совершаем коннект
    {};
start(odbc) ->
    % совершаем коннект
    {};


Следующим шагом было бы добавление Options, и, собственно, приход к результату:

start(Driver) ->
    start(Driver, []).
        
start(mysql, Options) ->
    % совершаем коннект
    {};
    
start(_, _) ->
    {error, driver_not_supported}.



Я уже говорил, что Эрланг рулит?

Кстати. Примерно такую же процедуру я прошел в своем http://orcas.googlecode.com/svn/trunk/src/test.erl для функции tagStart
... << RSDN@Home 1.2.0 alpha rev. 655>>


dmitriid.comGitHubLinkedIn
Re[3]: ФЯ и полиморфизм
От: Alex EXO http://aleksandr-zubarev.moikrug.ru/
Дата: 19.09.06 05:42
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Да, походу ты прав — в Erlang'е полиморфизм вроде как реализован только на уровне процессов. Но в чем смысл этого решения? Мне же не всегда нужны активные полиморфные объекты! Все как раз наоборот...


В эрланге процес — аналог объекта.
У него даже состояние модет быть (словарь процесса).
Процесс — легкий, то есть уж по процессу на драйвер точно можешь делать.
Задумываться стоит начать если процессов будет получаться больше 200 000...
А их "активность"может быть весьма полезной... например процесс в фоне может иногда "щупать" соединение с БД, или запускать что-нибудь типа "VACUUM..." когда нет никаких запросов.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.