LINUX.ORG.RU

[Haskell][Java] а что (template?) haskell может противопоставить аннотациям?


0

0

Допустим, нам нужно указать названия полей базы данных, откуда будет грузиться / куда будет сохраняться объекты класса. Это можно сделать примерно так (там еще нужна аннотация или ключик для доступа через рефлексию/аннотации к приватным членам):

public @interface DBTable { String value; }
public @interface DBField { String value; }

@DBTable("forum_users") class User
{
  @DBField("email") String email;
  @DBField("f_name") String first_name;
  @DBField("l_name") String last_name;
};

class DBAccess
{
  public void Load(Object o, int key) { ... }
  public void Save(Object o, int key) { ... }
}

Функциям Load и Save не нужно указывать названия таблиц (и полей), куда сохранять объекты — они их узнают сами из аннотаций.

Теперь вопросы:

1. Делается ли это на хаскеле (только без извращений типа передачи каждой буквы строки «f_name» отдельным фантомным типом или тайпклассом)?

2. Делается ли это готовой библиотечкой на template haskell?

P.S. Вам не кажется, что это чем-то напоминает зависимые типы?

P.S.S. Для тех, кто хочет, что ВСЕ проверялось во время компиляции: члены аннотации могут быть не только строками, но и перечислениями. Т.е. внешним скриптом, исходя из схемы базы данных, можно сгенерить соответствующие таблицам БД enum-ы и ловить любую опечатку в названии таблицы или ее поля (пример приводить лень, сорри).

P.S.S. Для тех, кто хочет, что ВСЕ проверялось во время компиляции

Я тебе открою страшную тайну, шо как только в базу придется добавить пару табличек и по полтора поля в остальные ты отгребешь гиммороя с рефакторингм всего и вся.

wfrr ★★☆ ()

отличный вопрос!

Допустим, нам нужно указать названия полей базы данных, откуда будет грузиться / куда будет сохраняться объекты класса.

а что аннотации могут противопоставить объектным БД?

korvin_ ★★★★★ ()
Ответ на: комментарий от korvin_

а что аннотации могут противопоставить объектным БД?

Что курица может противопоставить нейтрино? Може хоре жопу с пальцем равнять.

wfrr ★★☆ ()
Ответ на: комментарий от korvin_

> а что аннотации могут противопоставить объектным БД?

вопрос сформулирован некорректно, один язык (а не фича) может что-то противопоставить определенной фиче другого языка (аналогичную фичу, набор заменяющих фич, или ничего)

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от cathode

> http://hackage.haskell.org/packages/archive/pkg-list.html#cat:database

огромный список софта, который, вполне возможно, пишется без проблем и на РНР, и на С не отвечает на мой вопрос

мне нужет ответ в виде 10-20 строк кода, возможно за-require-ив че-нить из этого списака

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

вопрос сформулирован некорректно, один язык (а не фича) может что-то противопоставить определенной фиче другого языка (аналогичную фичу, набор заменяющих фич, или ничего)

ммм... AllegroCache — объектная бд в качестве фичи CL, не?

а ля

(defparameter *db* (make-hash-table))

(defun insert (obj)
  (setf (gethash (gensym "ID-") *db*) obj))

(defclass foo ()
  ((slot1 ...)
   (slot2 ...)
   ...))

(insert (make-instance 'foo ...))

еще пару форм типа select, delete, update и готовая объектная бд... =)

korvin_ ★★★★★ ()

Для начала хотелось бы уточнить - что именно «делается»? В чём, собственно, выгода от того, что эти «аннотации» будут рядом с полями типа?

Вам не кажется, что это чем-то напоминает зависимые типы?

Пока не очень.

Т.е. внешним скриптом, исходя из схемы базы данных, можно сгенерить соответствующие таблицам БД enum-ы и ловить любую опечатку в названии таблицы или ее поля (пример приводить лень, сорри).

Жаль, ибо ни фига не понятно.

Miguel ★★★★★ ()
Ответ на: комментарий от Miguel

> Для начала хотелось бы уточнить - что именно «делается»?

название поля БД выписывается рядом с названием поля класса, и доступно через рефлексию

В чём, собственно, выгода от того, что эти «аннотации» будут рядом с полями типа?

упс. я даже не подозревал, что такой вопрос возникнет. начну издалека.

есть 2 подхода к проектированию — 1. ООП, ведущий к ООБД, и 2. ориентированный на реляционные БД

в первом нам вообще пофиг на названия полей в БД

во втором совсем не пофиг, и их нельзя назвать даже деталью реализации

плюсы второго подхода — возможность работать с БД из разных языков, ALTER TABLE как вариант МОР, ... да и вообще он ИМХО более популярен чем первый

хотя вместо многоточия по идее должен идти полный список; но для начала рассмотрим ALTER TABLE — какой его будет аналог в мейнстримовых ООП языках или хаскеле?

так вот, *есть* у нас РБД с ограничениями целостности, транзакциями, и к ее маленькой части надо приделать мордочку/подсистемку

теперь собственно о выгоде — альтернативой будет запихать эти аннотации в отдельный файл (для явы им чаще всего оказывается человековраждебный XML), и после этого человеку придется вручную соотносить 2 файла, чтобы выяснить, например, как ограничения целостности БД соотносятся с кодом на яве

да хотя бы выяснение «а какие же поля оказались без аннотаций» не является простым — в отдельном файле поля будут перепутаны, а сама ситуация «поля оказались без аннотаций» может вовсе не быть ошибкой — так задумано, эти поля объекта инициализируются не из базы, а кодом на яве

Жаль, ибо ни фига не понятно.

если мы вместо «f_name» напишем «fname», то компилятор явы это не поймает, но можно внешним скриптом нагенерить перечислений и юзать @DBTable(FORUM_USERS.F_NAME), где ошибка уже будет ловиться

для конкретного примера нужно вспоминать синтаксис перечислений в яве

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

сорри, засыпаю уже

s/@DBTable(FORUM_USERS.F_NAME)/@DBField(FORUM_USERS.F_NAME)/

___________________

кто-нить знает, как избавится от приставочки FORUM_USERS? похоже никак

т.е. хотелось бы, чтобы объявив @DBTable(FORUM_USERS) class User {...} внутри этого класса в аннотациях писать просто @DBField(F_NAME)

www_linux_org_ru ★★★★★ ()

пока тут объяснял свою точку зрения, мне показалось, что вариант «нагенерить внешним скриптом enum-ов» для явы в случае хаскеля вполне параллельно переходит в «нагенерить внешним скриптом типов», а приведенный в коде вариант с использованием строк вполне можно браковать как непроверяемый компилятором и неудобный для набора в IDE.

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от Miguel

и еще:

В чём, собственно, выгода от того, что эти «аннотации» будут рядом с полями типа?

делать diff-ы и мержить патчи (мягко говоря) намного удобнее, если аннотации не вынесены в отдельный файл

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от korvin_

> ммм... AllegroCache — объектная бд в качестве фичи CL, не?

почитай ответ мигелю насчет ООБД<-->РБД

т.е. тех. задание ставится так: есть большая РБД с кучей клиентов (возможно на разных языках), надо допустим на CL сделать следующее: ...

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

> т.е. тех. задание ставится так: есть большая РБД с кучей клиентов (возможно на разных языках), надо допустим на CL сделать следующее: ...

А где собственно задание?!

anonymous ()
Ответ на: комментарий от www_linux_org_ru

альтернативой будет запихать эти аннотации в отдельный файл

Э-э-э... это почему, собственно?

Альтернативой, ИМХО, является что-то вроде boost::serialization.

если мы вместо «f_name» напишем «fname», то компилятор явы это не поймает, но можно внешним скриптом нагенерить перечислений и юзать @DBTable(FORUM_USERS.F_NAME), где ошибка уже будет ловиться

Примерно ясно, но надо сначала разобраться с первым вопросом.

Miguel ★★★★★ ()
Ответ на: комментарий от Miguel

Вот, набросал библиотечку, используется так:

data User = User {
      email :: String, 
      firstName :: String, 
      lastName :: String,
      age :: Integer,
      garbage :: Integer -- служебное поле, его в базе не храним
} deriving Show
instance DB User where
    tableName = Table "forum_users"
    db = User <$>
         field email "email" <*>
         field firstName "f_name" <*>
         field lastName "l_name" <*>
         field age "age" <*>
         ignore garbage 0 -- по умолчанию, туда пишется ноль

Тестовые данные:

user1 = User {
          email = "xxx@yyy.com",
          firstName = "Andrew",
          lastName = "Jones",
          age = 21,
          garbage = 666
        }
Вот такую информацию можно извлечь:
*DB> user1
User {email = "xxx@yyy.com", firstName = "Andrew", lastName = "Jones", age = 21, garbage = 666}

*DB> getFieldNames user1
["email","f_name","l_name","age"]

*DB> getFields user1
["\"xxx@yyy.com\"","\"Andrew\"","\"Jones\"","21"]

*DB> fromFields ["\"xxx@yyy.com\"","\"Andrew\"","\"Jones\"","21"] :: User
User {email = "xxx@yyy.com", firstName = "Andrew", lastName = "Jones", age = 21, garbage = 0}
Сама библиотечка здесь: http://hpaste.org/fastcgi/hpaste.fcgi/view?id=20058#a20058

Это, конечно, первый драфт прототипа. Но уже кое на что годится.

Miguel ★★★★★ ()
Ответ на: комментарий от Miguel

Ага, это уже интересно. По коду реализации понять идеи без комментариев я не могу (а почему все так сложно? там реализуются что ли гетерогенные списки? так ведь можно и без них). Так что обсужу использование, тем более что оно не такое, как хочется.

В целом, такой подход больше в сторону ООБД. Там неизбежно сначала класс надо написать, а *потом* аннотировать. И поэтому, в частности, нарушается принцип DRY — все поля (firstName и т.д.) пришлось повторить 2 раза вместо одно.

Вот как бы я делал подход явы с аннотациями на с++ ЕСЛИ БЫ в шаблонах можно было юзать const char*:

class User: public DBTable<"forum_users">
{
  DBField<string, "email"> email;
  DBField<string, "f_name"> firstName;
  DBField<string, "l_name"> lastName;
  DBField<int, "age"> age;
  int garbage;
};

Вопрос, относящийся к хаскелю — как написать такой код без повторений, примерно как в яве. Чтобы был DRY.

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от anonymous

>> т.е. тех. задание ставится так: есть большая РБД с кучей клиентов (возможно на разных языках), надо допустим на CL сделать следующее: ...

А где собственно задание?!

Придумай по своему вкусу. Ну например сделать какой-то *сложный* веб-интерфейс к таблице forum_users (и каким-то другим таблицам), при этом

1. названия полей в базе менять ты не вправе

2. нельзя полагаться на то, что названия полей класса у тебя будут совпадать с названиями полей базы, т.к. названия полей в базе могут быть изменены, и в этом случае весь рефакторинг твоего кода должен состоять в изменении *одного слова* (или не изменении ничего), а делать search-replace по всему исходнику класса нельзя (а еще может случиться, что названия поля в твоем классе используется в других классах, тогда тем более будет много работы).

Да, конечно можно использовать вьюшку БД для приведения новых названий в соответствие со старыми, но это такой же костыль, как отдельный файл.

С подходом «название поля класса может не совпадать с названием поля таблицы БД» может и можно поспорить, но допустим, что это условие задачи (надеюсь, реалистичное?).

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

> .. а делать search-replace по всему исходнику класса нельзя (а еще может случиться, что названия поля в твоем классе используется в других классах, тогда тем более будет много работы).

очень странное требование, попахивает подстраиванием ТЗ под твоё решение. В требованиях может быть только то, что интерфейс класса меняться не должен. А внутренняя работа особо не регламентироваться, тем более, что такое изменение как название поля рефакторится в любой современной IDE за пару минут.

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

qnikst ★★★★★ ()
Ответ на: комментарий от qnikst

> В итоге ТЗ выглядит как: нужно сделать тоже самое, что сделал я средствами других языков, другие способы (даже если они логичные) не принимаются.

а по-моему разница между

1. поменять одну строку «f_name»

2. поменять ВСЕ вхождения слова firstName

есть и существенная, кроме того, поле firstName может оказаться публичным, и тогда

В требованиях может быть только то, что интерфейс класса меняться не должен.

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

> а по-моему разница между

1. поменять одну строку «f_name»

2. поменять ВСЕ вхождения слова firstName

Вы же знакомы с рефакторингом в современных IDE и не надо рассказывать вам, что там не меняются ВСЕ вхождения слова firstName, а чуть сложнее?

есть и существенная, кроме того, поле firstName может оказаться публичным, и тогда

А может и не оказаться. А если следовать тому ТЗ, что интерфейс меняться не должен, то скорее всего реализованы они будут через геттеры и сеттеры.

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

qnikst ★★★★★ ()
Ответ на: комментарий от qnikst

Вы же знакомы с рефакторингом в современных IDE и не надо рассказывать вам, что там не меняются ВСЕ вхождения слова firstName, а чуть сложнее?

И на этом «чуть сложнее» они глючат, еще год назад слышал такие жалобы на идею — по мнению многих, лучшую IDE для явы.

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

Я бы тут предпочел какой-нить статический вариант рефлексии.

_________________________________

А вообще по-моему ты не то критикуешь. Можно сказать, например, что в случае «база — эталон» наличие полей классов, отличающихся по названию от полей таблицы БД, но соответствующие им — это дефект. Можно принять это мнение, тогда в яве останется просто

class Forum_Users
{
  @db string email;
  @db string f_name;
  @db string l_name;
  @db int age;
  int garbage;
}

или даже

class Forum_Users
{
  string email;
  string f_name;
  string l_name;
  int age;
  @ignore int garbage;
}

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

По коду реализации понять идеи без комментариев я не могу

Идея, на самом деле, сосёт. Потому что для save используются имена полей, а для load - их позиции. Это надо фиксить.

Первая возможность фикса - юнит-тест. Причём, юнит-тест, который пишется автоматически. Использоваться будет как-то так:

*DB> checkDB (undefined :: User)
+++ OK, passed 100 tests

а если перепутать порядок полей, то

*DB> checkDB (undefined :: User)
*** Failed! Falsifiable (after 2 tests and 3 shrinks):
Это я подключил QuickCheck. Можно вызывать checkDB из Template Haskell.

Вторая возможность - действительно заюзать Template Haskell и писать что-то вроде

$(dbClass "forum_users" "User" <$>
    field "email" "email" ''String <*>
    field "f_name" "firstName" ''String <*>
    field "l_name" "lastName" ''String <*>
    field "age" "age" ''Integer <*>
    ignore (0 :: Integer) garbage)
и разворачивать это всё во что-то типа того, что я написал выше. Коррекнтость будет автоматической, но... мне не нравится.

Третья возможность - использовать позиционные параметры и для save, и для load. Выглядеть будет как-то так:

instance DB User where
    tableName = Table "forum_users" 
    db = User <$> 
         field "email" <*> 
         field "f_name" <*> 
         field "l_name" <*> 
         field "age" <*> 
         ignore 0

Miguel ★★★★★ ()
Ответ на: комментарий от Miguel

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

Miguel ★★★★★ ()
Ответ на: комментарий от korvin_

еще пару форм типа select, delete, update и готовая объектная бд... =)

AllegroCache и так объектная. А ещё есть свободный Elephant.

mv ★★★★★ ()

Кстати, твоя задача легко делается на, прости хоссподи, Коммон Лиспе: CLOS, MOP, макросы, EVAL-WHEN.

mv ★★★★★ ()
Ответ на: комментарий от mv

а я-то думал флейм тихо закончился :-)

ну так нарисуй, как будет выглядеть твой довесок к лиспу, который позволяет определять классы *вместе с дополнительной инфой, например, названиями полей БД*, причем соблюдая DRY

нарисуй хотя бы синтаксис использования, и желательно чтобы это реализовывалось готовой библиотечкой

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

вот например для явы (я уже не помню всех точных названий функций)


class DBAccess
{
  public static String sqlForUpdate(Object o) throw Exception
  {
    Class c = получить_класс_объекта(o);

    String table="";
    {
      СписокАннотаций aa = получить_список_аннотаций(c);
      for( каждой аннотации а из аа) {
        if( a это DBTable ) {
          table = a.value;
          break;
        }
      }
      if( table=="" ) throw "no table";
    }
    String pairs=" ";
    {
      for( всех полей f класса с) {
        СписокАннотаций aa = получить_список_аннотаций(f);
        for( каждой аннотации а из аа) {
          if( a это DBField )
            pairs += ", "+a.value+"=\""+значение_через_рефлексию(o,f)+"\"";//FIXME:sql injection
        }
      }
      pairs[0] = ' '; // затираем ненужную запятую
      if( pairs==" " ) throw "no fields";
    }
    return "UPDATE "+table+" SET"+ pairs;
  }
};

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

вот например для явы (я уже не помню всех точных названий функций)

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

mv ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

> ну так нарисуй, как будет выглядеть твой довесок к лиспу, который позволяет определять классы *вместе с дополнительной инфой, например, названиями полей БД*, причем соблюдая DRY

http://clsql.b9.com/manual/def-view-class.html

«ДОвесок» существует и работает.

Твой пример будет выглядеть так

[code] (def-view-class user (thing) ((email :column «email» :type (varchar 30) :initarg :email) (first-name :column «f_name» :type (varchar 30) :initarg :first-name) (last-name :column «l_name» :type (varchar 30) :initarg last-name) (:base-table «forum_users»)) [/code]

anonymous ()
Ответ на: комментарий от www_linux_org_ru

ну так нарисуй, как будет выглядеть твой довесок к лиспу, который позволяет определять классы *вместе с дополнительной инфой, например, названиями полей БД*, причем соблюдая DRY

http://clsql.b9.com/manual/def-view-class.html

«ДОвесок» существует и работает.

Твой пример будет выглядеть так

(def-view-class user (thing)
  ((email      
    :column "email"
    :type (varchar 30)
    :initarg :email)
   (first-name
    :column "f_name"
    :type (varchar 30)
    :initarg :first-name)
   (last-name
    :column "l_name"
    :type (varchar 30)
    :initarg last-name)
  (:base-table "forum_users"))
anonymous ()
Ответ на: комментарий от www_linux_org_ru

ты ветку читал? щас будет копипаста

Читал. Что-то сбивчиво написано, псевдкод какой-то явоподобный. На внятное ТЗ не тянет.

mv ★★★★★ ()
Ответ на: комментарий от mv

> Читал. Что-то сбивчиво написано, псевдкод какой-то явоподобный. На внятное ТЗ не тянет.

но инфы достаточно, чтобы дать правильную ссылку, например ту, что дал анонимус http://clsql.b9.com/manual/def-view-class.html

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от anonymous

удивило, что есть TRUNCATE-DATABASE но нет TRUNCATE-TABLE

а по сути придраться не к чему

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от mv

> псевдкод какой-то явоподобный. На внятное ТЗ не тянет.

и не должно тянуть.

ТЗ формулируется по-другому, и в конкретном ТЗ даже не факт, что программисту захочется создавать классы (может, достаточно будет написть функции).

У меня рассматривается не ТЗ, а определенная задача проектирования при некторых ТЗ.

www_linux_org_ru ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.