Подтвердить что ты не робот

Проблема определения порядка упорядочения типов 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, поэтому мне пришлось преодолеть это ограничение.

4b9b3361

Ответ 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 знает только свой собственный тип.