由于循环引用而确定如何订购F#类型的问题

Jam*_*ack 13 f# types functional-programming reference mutual-recursion

我有一些扩展常见类型的类型,这些是我的模型.

然后我为CRUD操作的每个模型类型都有DAO类型.

我现在需要一个允许我在给定任何模型类型的情况下找到id的函数,因此我为一些其他函数创建了一个新类型.

问题是我不知道如何订购这些类型.目前我在dao之前有模型,但我不知何故需要DAOMisc之前CityDAOCityDAO之前DAOMisc,这是不可能的.

简单的方法是把每个DAO此功能,指的只是前,可以来的类型,所以,State之前谈到CityState有一个外键关系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
Run Code Online (Sandbox Code Playgroud)

这是一种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
    )
Run Code Online (Sandbox Code Playgroud)

这是我的模型类型:

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);]
Run Code Online (Sandbox Code Playgroud)

这个FindIdByType函数的目的是我想找到外键关系的id,所以我可以在我的模型中设置值,然后让CRUD函数用所有正确的信息进行操作.所以,City需要状态名称的id,所以我会获得状态名称,将其放入state类型中,然后调用此函数来获取该状态的id,因此我的city插入也将包含外键的id.

这似乎是最好的方法,以非常通用的方式处理插入,这是我试图解决的当前问题.

更新:

在定义了所有其他DAO之后,我需要研究并查看是否可以以某种方式将FindIdByType方法注入CommonDAO,几乎就像是一个闭包.如果这是Java,我会使用AOP来获取我正在寻找的功能,而不是确定如何在F#中执行此操作.

最后更新:

在考虑了我的方法后,我发现它有致命的缺陷,所以我想出了一个不同的方法.

这就是我将如何进行插入,并且我决定将这个想法放入每个实体类中,这可能是一个更好的主意.

member self.Insert(user:CityType) =
    let fk1 = [(StateDAO().Retrieve ((user.ToFKValuesList.Head :?> StateType), list.Empty)).Head.Id]
    self.Insert (user, fk1)
Run Code Online (Sandbox Code Playgroud)

我还没有开始使用fklist它,但它确实是int list我知道每个列的名称,所以我只需要inner join选择,例如.

这是一般化的基类型插入:

member self.Insert(user:'a, fklist) =
    self.ExecNonQuery (self.BuildUserInsertQuery user)
Run Code Online (Sandbox Code Playgroud)

如果F#可以进行共同/反向差异会很好,所以我不得不解决这个限制.

Tom*_*cek 10

在F#中,可以定义相互递归的类型,也就是说,您可以定义需要相互引用的两种类型,它们将相互看到.写这个的语法是:

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

and DAOMisc = 
  member internal self.FindIdByType item =  
    // we can use CityDAO here
Run Code Online (Sandbox Code Playgroud)

此语法的限制是两个类型都需要在单个文件中声明,因此您不能每1个文件使用典型的C#organization 1类型.

正如Norman所指出的,这不是典型的功能设计,因此如果您以更加功能的方式设计整个数据访问层,则可以避免此问题.但是,我要说在F#中组合功能和面向对象的样式没有任何问题,因此使用相互递归类型可能是唯一的选择.

如果您首先为这两种类型定义接口,则可以更好地编写代码 - 这些接口可能需要也可能不需要相互递归(取决于是否在另一种的公共接口中使用):

type ICityDAO = 
  abstract Foo : // ...

type IDAOMisc = 
  abstract Foo : // ...
Run Code Online (Sandbox Code Playgroud)

这有以下好处:

  • 在单个文件中定义所有相互递归的接口不会降低代码的可读性
  • 您可以稍后引用接口,因此其他类型不需要相互递归
  • 作为副作用,您将拥有更多可扩展的代码(由于接口)


Nor*_*sey 7

这个例子与我在函数式编程中习以为常的情况相去甚远.但是对于排序相互递归类型的问题,有一个标准的解决方案:使用类型参数并制作两级类型.我将在相关语言OCaml中给出一个简单的例子.我不知道如何将简单示例转换为您正在使用的可怕类型函数.

这是不起作用的:

type misc = State of string
          | City  of city

type city = { zipcode : int; location : misc }
Run Code Online (Sandbox Code Playgroud)

以下是使用两级类型修复它的方法:

type 'm city' = { zipcode : int; location : 'm }

type misc = State of string
          | City of misc city'
type city = misc city'
Run Code Online (Sandbox Code Playgroud)

这个例子是OCaml,但也许你可以推广到F#.希望这可以帮助.


nac*_*man 5

F# 直接支持相互递归类型。考虑以下鸡/蛋类型定义:

type Chicken =
   | Eggs of Egg list
and Egg =
   | Chickens of Chicken list
Run Code Online (Sandbox Code Playgroud)

关键是使用“and”运算符一起声明相互递归类型(而不是两个单独的类型)