Проблема определения порядка упорядочения типов F # из-за циклических ссылок
У меня есть некоторые типы, которые расширяют общий тип, и это мои модели.
Затем у меня есть типы DAO для каждого типа модели для операций CRUD.
Теперь мне нужна функция, которая позволит мне найти идентификатор, заданный для любого типа модели, поэтому я создал новый тип для некоторых разных функций.
Проблема в том, что я не знаю, как заказать эти типы. В настоящее время у меня есть модели до dao, но мне как-то нужно DAOMisc
до CityDAO
и CityDAO
до DAOMisc
, что невозможно.
Простым подходом было бы поместить эту функцию в каждый DAO, ссылаясь только на типы, которые могут возникнуть перед ним, поэтому State
предшествует City
, поскольку State
имеет отношение внешнего ключа с City
, поэтому разная функция будет очень короткой. Но это просто поражает меня как неправильное, поэтому я не уверен, как наилучшим образом подойти к этому.
Вот мой разный тип, где BaseType
- общий тип для всех моих моделей.
type DAOMisc =
member internal self.FindIdByType item =
match(item:BaseType) with
| :? StateType as i ->
let a = (StateDAO()).Retrieve i
a.Head.Id
| :? CityType as i ->
let a = (CityDAO()).Retrieve i
a.Head.Id
| _ -> -1
Вот один тип dao. CommonDAO
на самом деле имеет код для операций CRUD, но здесь это не важно.
type CityDAO() =
inherit CommonDAO<CityType>("city", ["name"; "state_id"],
(fun(reader) ->
[
while reader.Read() do
let s = new CityType()
s.Id <- reader.GetInt32 0
s.Name <- reader.GetString 1
s.StateName <- reader.GetString 3
]), list.Empty
)
Это мой тип модели:
type CityType() =
inherit BaseType()
let mutable name = ""
let mutable stateName = ""
member this.Name with get() = name and set restnameval=name <- restnameval
member this.StateName with get() = stateName and set stateidval=stateName <- stateidval
override this.ToSqlValuesList = [this.Name;]
override this.ToFKValuesList = [StateType(Name=this.StateName);]
Цель этой функции FindIdByType
заключается в том, что я хочу найти идентификатор для отношения внешнего ключа, поэтому я могу установить значение в моей модели, а затем, чтобы функции CRUD выполняли операции со всей правильной информацией. Таким образом, City
требуется идентификатор для имени состояния, поэтому я бы получил имя состояния, поместил его в тип State
, а затем вызовет эту функцию, чтобы получить идентификатор для этого состояния, поэтому моя вставка города также будет включать id для внешнего ключа.
Это, по-видимому, лучший подход, очень общий способ обработки вставок, который является текущей проблемой, которую я пытаюсь решить.
UPDATE:
Мне нужно исследовать и посмотреть, смогу ли я каким-то образом ввести метод FindIdByType в CommonDAO после того, как будут определены другие DAO, почти как бы закрытие. Если бы это была Java, я бы использовал AOP для получения функциональности, которую я ищу, но не уверен, как это сделать в F #.
Окончательное обновление:
Подумав о моем подходе, я понял, что это было смертельно ошибочно, поэтому я придумал другой подход.
Вот как я буду делать вставку, и я решил включить эту идею в каждый класс сущностей, что, вероятно, является лучшей идеей.
member self.Insert(user:CityType) =
let fk1 = [(StateDAO().Retrieve ((user.ToFKValuesList.Head :?> StateType), list.Empty)).Head.Id]
self.Insert (user, fk1)
Я еще не начал использовать fklist
, но это int list
, и я знаю, какое имя столбца относится к каждому из них, поэтому мне просто нужно сделать inner join
для выбора, например.
Это базовая вставка типа, которая обобщается:
member self.Insert(user:'a, fklist) =
self.ExecNonQuery (self.BuildUserInsertQuery user)
Было бы неплохо, если бы F # мог делать co/contra-variance, поэтому мне пришлось преодолеть это ограничение.
Ответы
Ответ 1
Этот пример очень далек от того, к чему я привык в функциональном программировании. Но для задачи упорядочения взаимно-рекурсивных типов существует стандартное решение: использовать параметры типа и создавать двухуровневые типы. Я приведу простой пример в OCaml, родственном языке. Я не знаю, как перевести простой пример в функции страшного типа, которые вы используете.
Здесь что не работает:
type misc = State of string
| City of city
type city = { zipcode : int; location : misc }
Здесь вы можете исправить это с помощью двухуровневых типов:
type 'm city' = { zipcode : int; location : 'm }
type misc = State of string
| City of misc city'
type city = misc city'
Этот пример - OCaml, но, возможно, вы можете обобщить его на F #. Надеюсь это поможет.
Ответ 2
В F # можно определить взаимно-рекурсивные типы, т.е. вы можете определить два типа, которые должны связывать друг с другом вместе, и они будут видеть друг друга. Синтаксис для написания:
type CityDAO() =
inherit CommonDAO<CityType>(...)
// we can use DAOMisc here
and DAOMisc =
member internal self.FindIdByType item =
// we can use CityDAO here
Ограничение этого синтаксиса состоит в том, что оба типа должны быть объявлены в одном файле, поэтому вы не можете использовать типичный тип С# организации 1 для каждого файла.
Как указывает Норман, это не типичный функциональный дизайн, поэтому, если вы разработали весь уровень доступа к данным более функциональным способом, вы, вероятно, могли бы избежать этой проблемы. Однако я бы сказал, что нет ничего плохого в объединении функционального и объектно-ориентированного стиля в F #, поэтому использование взаимно рекурсивных типов может быть единственным вариантом.
Возможно, вы можете написать код более красиво, если вы сначала определяете интерфейсы для двух типов - это может быть или не быть взаиморекурсивным (в зависимости от того, используется ли он в открытом интерфейсе другого):
type ICityDAO =
abstract Foo : // ...
type IDAOMisc =
abstract Foo : // ...
Это имеет следующие преимущества:
- Определение всех взаимно-рекурсивных интерфейсов в одном файле не делает код менее удобочитаемым
- Вы можете позже обратиться к интерфейсам, поэтому другие типы не должны быть взаимно рекурсивными
- В качестве побочного эффекта у вас будет более расширяемый код (благодаря интерфейсам).
Ответ 3
F # напрямую поддерживает взаимно-рекурсивные типы. Рассмотрим следующее определение типа цыпленка/яйца:
type Chicken =
| Eggs of Egg list
and Egg =
| Chickens of Chicken list
Дело в том, что взаимно-рекурсивные типы объявляются вместе с использованием оператора "и" (в отличие от двух отдельных типов)
Ответ 4
Как об исключении DAOMisc.FindIdByType и замене его на FindId в каждом классе DAO? FindId будет знать только, как найти свой собственный тип. Это устранило бы необходимость в тестировании базового класса и динамического типа и круговой зависимости между DAOMisc и всеми другими классами DAO. Типы DAO могут зависеть друг от друга, поэтому CityDAO может вызывать StateDAO.FindId. (Типы DAO могут зависеть друг от друга, если потребуется).
Это то, о чем вы говорили, когда вы сказали: "Простой подход состоит в том, чтобы поместить эту функцию в каждый DAO... Но это просто поражает меня как неправильное..."? Я не уверен, потому что вы сказали, что функция будет ссылаться только на типы, которые появляются перед ней. Идея, которую я представляю здесь, состоит в том, что каждая функция FindId знает только свой собственный тип.