Functional programming 函数式编程:如何建立关系模型?
我想知道如何在函数式编程中建立关系模型 以员工为例: 员工可以与0..n名同事建立友谊。 友谊总是相互的:如果A是B的朋友,那么B就是A的朋友 我如何建模?我有3个想法(如下所列)。它们都有缺点 第一次尝试:Functional programming 函数式编程:如何建立关系模型?,functional-programming,f#,modeling,Functional Programming,F#,Modeling,我想知道如何在函数式编程中建立关系模型 以员工为例: 员工可以与0..n名同事建立友谊。 友谊总是相互的:如果A是B的朋友,那么B就是A的朋友 我如何建模?我有3个想法(如下所列)。它们都有缺点 第一次尝试: type Employee = { name : string friendships : Employee list } // My list is mutable because I want to be able to update employees. // I
type Employee =
{ name : string
friendships : Employee list
}
// My list is mutable because I want to be able to update employees.
// I suppose the same problems would occur with e. g. Elmish.
let mutable employees : Employee list = [ (* ... *) ]
(* Downsides:
Friendships are copies. Changes to an employee would have to be propagated manually.
*)
第二次尝试:
type EmployeeId = int
type Employee =
{ id : EmployeeId
name : string
friendships : EmployeeId list
}
let mutable employees : Employee list = [ (* ... *) ]
(* Downsides:
Friendships are not modeled as mutual.
When the friendship of an employee changes,
the friend's friendships don't change accordingly.
*)
第三次尝试:
type EmployeeId = int
type Friendships = list<EmployeeId * EmployeeId>
type Employee =
{ id : EmployeeId
name : string
}
let mutable employees : Employee list = [ (* ... *) ]
(* Downsides:
After deleting an employee from the list,
employeeFriendships contain invalid "references"
to the deleted Employee.
*)
类型EmployeeId=int
键入友谊=列表
类型员工=
{id:EmployeeId
名称:string
}
让可变员工:员工列表=[(*…*)]
(*缺点:
从列表中删除员工后,
员工友谊包含无效的“推荐信”
发送给已删除的员工。
*)
这能做得更好吗?谢谢。使用
let rec
/和
可能是最直接的方法。你需要通过将友谊
更改为IEnumerable
来引入懒惰
类型员工=
{
名称:string
友情:seq
}
让rec peter={name=“peter”;friendships=seq{yield paul;yield mary}
保罗={name=“paul”;友谊=seq{yield peter;yield mary}
而玛丽{name=“mary”;友谊{yield peter;yield paul}}
但是,编译器会发出以下警告:
此引用和其他对所定义对象的递归引用
将在运行时通过
延迟引用的使用。这是因为您正在定义一个或多个
更多的递归对象,而不是递归函数。这一警告
可以通过使用“#nowarn”40”或“--nowarn:40”来抑制
…这表明了一个可以说更好的解决方案:
类型员工=
{
名称:string
友谊:单位->员工名单
}
让rec peter={name=“peter”;friendships=fun()->[paul;mary]}
保罗={name=“paul”;friendships=fun()->[peter;mary]}
玛丽={name=“mary”;friendships=fun()->[peter;paul]}
问题不在于函数式编程,因为它涉及到变异。面向对象的解决方案可以很好地工作,因为重要的是将逻辑封装在一个地方,并将其隐藏在一个安全的API后面。幸运的是,您对F#的选择仍然很好,因为F#是一种非常好的OO语言
type Employee(id:int,name:string)=
静态let employees=ResizeArray[]()
静态let friendships=ResizeArray()
静态let getEmployee(eid:EmployeeId)=雇员|>Seq.find(乐趣e->e.Id=eid)
成员t.Id=Id
成员t.名称=名称
成员t.朋友=
让FriendID=友谊。。。
friendIds |>Array.map getEmployee
静态成员Employees=Employees |>Array.ofSeq
静态成员Add=employees.Add
静态成员删除(e:员工)=
员工。删除e |>忽略
友谊|>顺序过滤(乐趣(a,b)->a=e.Id | | b=e.Id)
|>Seq.iter(friendships.Remove>>忽略)
静态成员AddFriend(e1:Employee,e2:Employee)=。。。
您也可以对记录和/或模块执行相同的操作,方法是将内容设置为“私有”
private
这类问题的最佳答案在很大程度上取决于您希望如何使用这些数据。根据用例的不同,有不同的“正确方法”来实现。例如,如果您想显示社交圈的交互式地图,那么拥有一个具体化的树,其中包含每个友谊的实际员工数据可能很有价值。但是,如果您只想查询一个人是否是另一个人的朋友,那么在Employee
对象上只需要一个朋友ID列表就足够了。我只是想知道如何建模。我试图在网上找到解释,结果却很少。阅读建议不胜感激。什么是“更好”?2.如果你依赖突变,它就不起作用。3.这个问题实际上与函数式编程没有任何关系。你实际上想要的是如何保持一个不变量,答案是,不管什么样的范式,都倾向于封装。如何实现这一点对于堆栈溢出来说是一个很好的问题,但事实并非如此。它太宽泛了,需要征求意见。你会希望有一个模型,在保持实用性的同时,减少不一致的可能性。一种可能性是考虑您希望如何在外部存储/检索数据,因为相同的范例可以在内部使用。在这种特殊情况下,单独列出员工和关系对我来说是有意义的(选项3)。可以使用集合
,并确保始终存储元组以避免重复/歧义。在这种情况下,您指出的缺点是不可避免的,但并不严重。选项1会产生大量冗余,维护非常困难,并且产生不一致的可能性非常高。选项2也可能在一名员工所说的与另一名员工所说的不一致(尽管这会使它更真实!)。更好的解决方案的问题是它是硬编码的,但您可以轻松创建一个具有不同友谊的副本:{peter with friendships=fun()->[]}