消除我通过like,monads和stuff传递的显式状态

J C*_*per 10 monads refactoring f# functional-programming land-of-lisp

我正在研究F#中的Land of 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.
Run Code Online (Sandbox Code Playgroud)

如何分析显式状态以使其更漂亮?(假设我有权访问State monad类型;如果有帮助的话我可以访问它;我知道F#中有示例代码.)

kvb*_*kvb 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")
Run Code Online (Sandbox Code Playgroud)


Tom*_*cek 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
Run Code Online (Sandbox Code Playgroud)

使用monads隐藏可变状态在F#中并不常见.使用monads为您提供基本相同的命令式编程模型.它隐藏了状态的传递,但它并没有改变编程模型 - 有一些可变状态使得无法并行化程序.

如果该示例使用变异,则可能是因为它是以命令方式设计的.您可以更改程序体系结构以使其更具功能性.例如,该pickUp函数可以只返回一些表示选择项目的请求的对象,而不是选择项目(并修改播放器).然后世界将有一些引擎来评估这些请求(从所有玩家收集)并计算世界的新状态.

  • @J Cooper:monad方法强制你以顺序方式编写程序,这就是为什么它在Haskell中很重要.在F#中你真的不需要它.就像可变性一样,它使状态隐含,所以如果你重新排序不依赖于彼此的行(直接),你可能会得到不同的程序行为(因为状态). (3认同)