Проблема определения того, как упорядочить типы F # из-за циклических ссылок - PullRequest
13 голосов
/ 18 мая 2010

У меня есть несколько типов, которые расширяют общий тип, и это мои модели.

Затем у меня есть типы 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

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

Похоже, что это лучший подход, очень общий способ обработки вставок, что является текущей проблемой, которую я пытаюсь решить.

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

Ответы [ 4 ]

10 голосов
/ 18 мая 2010

В F # можно определить взаимно рекурсивные типы , то есть вы можете определить два типа, которые должны ссылаться друг на друга, и они будут видеть друг друга. Синтаксис для написания этого:

type CityDAO() = 
  inherit CommonDAO<CityType>(...)
  // we can use DAOMisc here

and DAOMisc = 
  member internal self.FindIdByType item =  
    // we can use CityDAO here

Ограничением этого синтаксиса является то, что оба типа должны быть объявлены в одном файле, поэтому вы не можете использовать типичный тип организации C # 1 на 1 файл.

Как отмечает Норман, это не типичный функциональный дизайн, поэтому, если вы спроектировали весь уровень доступа к данным более функциональным способом, вы, вероятно, могли бы избежать этой проблемы. Однако я бы сказал, что нет ничего плохого в объединении функционального и объектно-ориентированного стилей в F #, поэтому использование взаимно рекурсивных типов может быть единственным вариантом.

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

type ICityDAO = 
  abstract Foo : // ...

type IDAOMisc = 
  abstract Foo : // ...

Это имеет следующие преимущества:

  • Определение всех взаимно рекурсивных интерфейсов в одном файле не делает код менее читабельным
  • Позже вы можете ссылаться на интерфейсы, поэтому никакие другие типы не должны быть взаимно рекурсивными
  • В качестве побочного эффекта у вас будет более расширяемый код (благодаря интерфейсам)
7 голосов
/ 18 мая 2010

Этот пример очень далек от того, к чему я привык в функциональном программировании. Но для проблемы упорядочения взаимно рекурсивных типов существует стандартное решение: использовать параметры типа и создавать двухуровневые типы. Я приведу простой пример на родственном языке 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 #. Надеюсь это поможет.

4 голосов
/ 11 августа 2014

F # напрямую поддерживает взаимно рекурсивные типы. Рассмотрим следующее определение типа курица / яйцо:

type Chicken =
   | Eggs of Egg list
and Egg =
   | Chickens of Chicken list

Дело в том, что взаимно рекурсивные типы объявляются вместе с использованием оператора 'и' (в отличие от двух отдельных типов)

2 голосов
/ 18 мая 2010

Как насчет исключения DAOMisc.FindIdByType и замены его FindId в каждом классе DAO? FindId знает только, как найти свой собственный тип. Это устранит необходимость в тестировании базового класса и динамического типа, а также циклическую зависимость между DAOMisc и всеми другими классами DAO. Типы DAO могут зависеть друг от друга, поэтому CityDAO может вызывать StateDAO.FindId. (Типы DAO могут взаимно зависеть друг от друга, если это необходимо.)

Это то, о чем вы говорили, когда говорили: «Простой подход заключается в том, чтобы поместить эту функцию в каждый DAO ... Но это просто кажется мне неправильным ...»? Я не уверен, потому что вы сказали, что функция будет ссылаться только на типы, предшествующие ей. Идея, которую я здесь представляю, состоит в том, что каждая функция FindId знает только свой собственный тип.

...