В сообществе F # императивный код и изменяемые данные не осуждаются на , если они не являются частью вашего открытого интерфейса .То есть, использование изменяемых данных - это нормально, если вы инкапсулируете их и изолируете от остального кода.Для этого я предлагаю что-то вроде:
type DeviceStatus =
{ RPM : int
Pressure : int
Temperature : int }
// one of the rare scenarios in which I prefer explicit classes,
// to avoid writing out all the get/set properties for each field
[<Sealed>]
type private DeviceStatusFacade =
val mutable RPM : int
val mutable Pressure : int
val mutable Temperature : int
new(s) =
{ RPM = s.RPM; Pressure = s.Pressure; Temperature = s.Temperature }
member x.ToDeviceStatus () =
{ RPM = x.RPM; Pressure = x.Pressure; Temperature = x.Temperature }
let UpdateStatusITimes status i =
let facade = DeviceStatusFacade(status)
let rec impl i =
if i > 0 then
facade.RPM <- 90
impl (i - 1)
impl i
facade.ToDeviceStatus ()
let initStatus = { RPM = 80; Pressure = 100; Temperature = 70 }
let stopwatch = System.Diagnostics.Stopwatch.StartNew ()
let endStatus = UpdateStatusITimes initStatus 100000000
stopwatch.Stop ()
printfn "endStatus.RPM = %d" endStatus.RPM
printfn "stopwatch.ElapsedMilliseconds = %d" stopwatch.ElapsedMilliseconds
stdin.ReadLine () |> ignore
Таким образом, открытый интерфейс не затрагивается - UpdateStatusITimes
по-прежнему принимает и возвращает внутренне неизменяемый DeviceStatus
, но внутренне UpdateStatusITimes
использует изменяемый классчтобы исключить накладные расходы.
РЕДАКТИРОВАТЬ: (в ответ на комментарий) Этот стиль класса я обычно предпочел бы, используя основной конструктор и свойства let
s + вместо val
s:
[<Sealed>]
type private DeviceStatusFacade(status) =
let mutable rpm = status.RPM
let mutable pressure = status.Pressure
let mutable temp = status.Temperature
member x.RPM with get () = rpm and set n = rpm <- n
member x.Pressure with get () = pressure and set n = pressure <- n
member x.Temperature with get () = temp and set n = temp <- n
member x.ToDeviceStatus () =
{ RPM = rpm; Pressure = pressure; Temperature = temp }
Но для простых классов фасадов, где каждое свойство будет слепым получателем / установщиком, я нахожу это несколько утомительным.
F # 3+ позволяет вместо этого следующее, но я все еще не нахожу это улучшением, лично (если кто-то догматически избегает областей):
[<Sealed>]
type private DeviceStatusFacade(status) =
member val RPM = status.RPM with get, set
member val Pressure = status.Pressure with get, set
member val Temperature = status.Temperature with get, set
member x.ToDeviceStatus () =
{ RPM = x.RPM; Pressure = x.Pressure; Temperature = x.Temperature }