我正在阅读F#中的 Lisp的土地这本书(是的,很奇怪,我知道)。对于他们的第一个示例文本冒险,他们使用全局变量变异,我想避免它。我的monad-fu很弱,所以现在我正在做这样丑陋的状态:
let pickUp player thing (objects: Map<Location, Thing list>) =
let objs = objects.[player.Location]
let attempt = objs |> List.partition (fun o -> o.Name = thing)
match attempt with
| [], _ -> "You cannot get that.", player, objs
| thing :: _, things ->
let player' = { player with Objects = thing :: player.Objects }
let msg = sprintf "You are now carrying %s %s" thing.Article thing.Name
msg, player', things
let player = { Location = Room; Objects = [] }
let objects =
[Room, [{ Name = "whiskey"; Article = "some" }; { Name = "bucket"; Article = "a" }];
Garden, [{ Name = "chain"; Article = "a length of" }]]
|> Map.ofList
let msg, p', o' = pickUp player "bucket" objects
// etc.
如何分析显式状态以使其更漂亮? (假设我有权访问State monad类型,如果有帮助的话;我知道F#中有示例代码。)
答案 0 :(得分:10)
如果你想使用状态monad通过pickUp
函数来线程化玩家的库存和世界状态,这里有一种方法:
type State<'s,'a> = State of ('s -> 'a * 's)
type StateBuilder<'s>() =
member x.Return v : State<'s,_> = State(fun s -> v,s)
member x.Bind(State v, f) : State<'s,_> =
State(fun s ->
let (a,s) = v s
let (State v') = f a
v' s)
let withState<'s> = StateBuilder<'s>()
let getState = State(fun s -> s,s)
let putState v = State(fun _ -> (),v)
let runState (State f) init = f init
type Location = Room | Garden
type Thing = { Name : string; Article : string }
type Player = { Location : Location; Objects : Thing list }
let pickUp thing =
withState {
let! (player, objects:Map<_,_>) = getState
let objs = objects.[player.Location]
let attempt = objs |> List.partition (fun o -> o.Name = thing)
match attempt with
| [], _ ->
return "You cannot get that."
| thing :: _, things ->
let player' = { player with Objects = thing :: player.Objects }
let objects' = objects.Add(player.Location, things)
let msg = sprintf "You are now carrying %s %s" thing.Article thing.Name
do! putState (player', objects')
return msg
}
let player = { Location = Room; Objects = [] }
let objects =
[Room, [{ Name = "whiskey"; Article = "some" }; { Name = "bucket"; Article = "a" }]
Garden, [{ Name = "chain"; Article = "a length of" }]]
|> Map.ofList
let (msg, (player', objects')) =
(player, objects)
|> runState (pickUp "bucket")
答案 1 :(得分:9)
如果你想在F#中使用可变状态,那么最好的方法就是编写一个可变对象。你可以像这样声明一个可变的Player
类型:
type Player(initial:Location, objects:ResizeArray<Thing>) =
let mutable location = initial
member x.AddThing(obj) =
objects.Add(obj)
member x.Location
with get() = location
and set(v) = location <- v
使用monads隐藏可变状态在F#中并不常见。使用monads为您提供了基本相同的命令式编程模型。它隐藏了状态的传递,但它并没有改变编程模型 - 有一些可变状态使得无法并行化程序。
如果该示例使用变异,那么可能是因为它是以强制方式设计的。您可以更改程序体系结构以使其更具功能性。例如,pickUp
函数可以返回表示选择项目的请求的某个对象,而不是选择项目(并修改播放器)。然后世界将有一些引擎来评估这些请求(从所有玩家收集)并计算世界的新状态。