Устранение моего явного состояния, проходящего через лайки, монады и прочее - PullRequest
10 голосов
/ 03 марта 2011

Я работаю над книгой Страна Лиспа на F # (да, странно, я знаю).Для своего первого примера текстового приключения они используют мутацию глобальной переменной, и я бы хотел ее избежать.Мой монад-фу слабый, поэтому сейчас я делаю ужасную передачу состояний, как это:

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 #.)

Ответы [ 2 ]

10 голосов
/ 04 марта 2011

Если вы хотите использовать монаду состояния, чтобы продвинуть инвентарь игрока и состояние мира через функцию 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")
9 голосов
/ 03 марта 2011

Если вы хотите использовать изменяемое состояние в 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 может просто вернуть некоторый объект, представляющий запрос на выбор элемента. Тогда у мира будет какой-то движок, который оценивает эти запросы (собранные у всех игроков) и вычисляет новое состояние мира.

...