Функциональное программирование: как моделировать отношения? - PullRequest
2 голосов
/ 14 февраля 2020

Интересно, как моделировать отношения в функциональном программировании.

Давайте возьмем сотрудников, например: сотрудники могут дружить с 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 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.
*)

Может ли это быть сделано лучше? Спасибо.

Ответы [ 2 ]

1 голос
/ 16 февраля 2020

Вопрос не в функциональном программировании, так как он связан с мутацией. Объектно-ориентированное решение будет работать хорошо, потому что важная вещь - инкапсулировать logi c в одном месте и скрыть его за безопасным API. К счастью, ваш выбор F # все еще хорош, так как F # - очень хороший язык OO.

type Employee(id:int, name:string) =
    static let employees = ResizeArray<Employee>[]()
    static let friendships = ResizeArray<int*int>()
    static let getEmployee(eid:EmployeeId) = employees |> Seq.find(fun e -> e.Id = eid)
    member t.Id = id
    member t.Name = name
    member t.Friends =
        let friendIds = friendships ...
        friendIds |> Array.map getEmployee
    static member Employees = employees |> Array.ofSeq
    static member Add = employees.Add
    static member Remove(e:Employee) =
        employees.Remove e |> ignore
        friendships |> Seq.filter (fun (a,b) -> a = e.Id || b = e.Id)
        |> Seq.iter (friendships.Remove >> ignore)
    static member AddFriend(e1:Employee, e2:Employee) = ...

Вы можете сделать то же самое с записями и / или модулями, тоже сделав вещи private.

1 голос
/ 14 февраля 2020

Использование let rec / and может быть самым прямым способом. Вам нужно будет ввести лень, изменив friendships на IEnumerable.

type Employee =
    { 
        name : string
        friendships : seq<Employee>
    }

let rec peter = {name="Peter"; friendships=seq {yield paul; yield mary}}
and paul = {name="Paul"; friendships=seq {yield peter; yield mary}}
and mary = {name="Mary"; friendships=seq {yield peter; yield paul}}

Однако компилятор выдает следующее предупреждение:

Эта и другие рекурсивные ссылки на определяемый объект (ы) будет проверен на правильность инициализации во время выполнения посредством использования отложенной ссылки. Это потому, что вы определяете один или несколько рекурсивных объектов, а не рекурсивные функции. Это предупреждение может быть подавлено с помощью «#nowarn« 40 »или« --nowarn: 40 ».

..., что указывает на возможно лучшее решение:

type Employee =
    { 
        name : string
        friendships : unit -> Employee list
    }

let rec peter = {name="Peter"; friendships=fun () -> [paul; mary]}
and paul = {name="Paul"; friendships=fun () -> [peter; mary]}
and mary = {name="Mary"; friendships=fun () -> [peter; paul]}
...