10

私はF#のLand of 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.

明示的な状態を除外して、より美しくするにはどうすればよいですか?(役立つ場合は、Stateモナドタイプにアクセスできると仮定します。F#にサンプルコードがあることを知っています。)

4

2 に答える 2

10

state モナドを使用して、プレーヤーのインベントリと世界の状態をpickUp関数に通したい場合は、次の 1 つの方法があります。

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")
于 2011-03-04T04:05:42.567 に答える
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

モナドを使用して可変状態を非表示にすることは、F# では一般的ではありません。モナドを使用すると、基本的に同じ命令型プログラミング モデルが得られます。状態の受け渡しを隠しますが、プログラミング モデルは変更しません。プログラムの並列化を不可能にする変更可能な状態がいくつかあります。

例がミューテーションを使用している場合、それはおそらく命令型の方法で設計されているためです。プログラムのアーキテクチャを変更して、より機能的にすることができます。たとえば、アイテムを選択する (およびプレーヤーを変更する) 代わりに、pickUp関数は、アイテムを選択する要求を表すオブジェクトを返すことができます。ワールドには、これらのリクエスト (すべてのプレイヤーから収集されたもの) を評価し、ワールドの新しい状態を計算するエンジンがあります。

于 2011-03-03T19:26:02.013 に答える