F# 通过单子之类的东西消除我的显式状态

F# 通过单子之类的东西消除我的显式状态,f#,functional-programming,refactoring,monads,land-of-lisp,F#,Functional Programming,Refactoring,Monads,Land Of Lisp,我正在读《F#中的Lisp之地》(是的,很奇怪,我知道)。在他们的第一个文本冒险示例中,他们使用了全局变量变异,我想避免它。我的符单弱,所以现在我正在做这样的丑态传递: let pickUp player thing (objects: Map<Location, Thing list>) = let objs = objects.[player.Location] let attempt = objs |> List.partition (fun o ->

我正在读《F#中的Lisp之地》(是的,很奇怪,我知道)。在他们的第一个文本冒险示例中,他们使用了全局变量变异,我想避免它。我的符单弱,所以现在我正在做这样的丑态传递:

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.
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
让皮卡机播放东西(对象:地图)=
让objs=objects。[player.Location]
让尝试=objs |>List.partition(乐趣o->o.Name=thing)
匹配尝试
|[],->“你不能得到它。”,玩家,objs
|事物::,事物->
让player'={player with Objects=thing::player.Objects}
让msg=sprintf“您现在携带的是%s%s”物品。物品物品。名称
味精,播放器,东西
让player={Location=Room;Objects=[]}
让对象=
[房间,[{Name=“whisky”;Article=“some”};{Name=“bucket”;Article=“a”}];
花园,[{Name=“chain”;Article=“长度”}]]
|>列表地图
让msg,p',o'=皮卡播放器“bucket”对象
//等等。

我怎样才能算出显式状态使它更漂亮?(假设我有权访问状态单子类型(如果有帮助的话);我知道在F#中有它的示例代码。)

如果您想在F#中使用可变状态,那么最好的方法就是编写可变对象。您可以像这样声明一个可变的
Player
类型:

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.
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
类型播放器(初始:位置,对象:ResizeArray)=
设可变位置=初始位置
成员x.AddThing(obj)=
objects.Add(obj)
成员十.地点
使用get()=位置

并设置(v)=location如果您想使用state 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")
type State=状态('s->'a*'s)
输入StateBuilder=State(funs->v,s)

成员x.Bind(State v,f):State=statebuilder在f#中要做的惯用事情是在类或模块级别使用一些可变变量。但我知道你可能出于教学原因对使用国家单子感兴趣?@wmeyer,你是对的。不过,如果你用惯用的版本发布一个答案,我还是会投赞成票,因为其他人可能想知道如何用“F的正确方式”来做。克里斯·史密斯的《F的编程》中有一节是关于这一点的。您可以在上的预览中看到该部分的大部分(但不是全部)。(我写这篇文章是作为一个评论而不是一个答案,因为它不是一个完整的答案,只是一个参考。)但是状态单子方法是纯粹的,这不重要吗?不过,最后我可能还是会同意你的方法,因为它是最惯用的方法。让我觉得我在写C而不是F,不过:)@J Cooper:monad方法迫使你按顺序编写程序,这就是为什么它在Haskell中很重要。在F#中你真的不需要这个。就像易变性一样,它使状态隐式化,因此,如果您重新排列彼此不依赖(直接)的行,您可能会得到不同的程序行为(因为状态)。如果您使所有对象不可变,并且所有操作都建立对象或世界的新状态,则可以以更实用的方式编写程序。然后你可能会想使用稍微不同的架构。此外,即使你使用可变对象,你也可以从函数式风格中获益——在实现一些计算或处理算法时,函数式风格会有所帮助。你将如何构建新的世界状态?想象世界上有几个玩家,你需要改变其中一个玩家的某些状态,你会用一个旧列表中的一个玩家替换一个玩家列表来返回一个新世界吗?感觉效率很低。哇,这比我想象的要简单得多!谢谢