Устранение моей явной передачи состояния через лайки, монады и прочее

Я работаю над книгой Land of Lisp на 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#.)


person J Cooper    schedule 03.03.2011    source источник
comment
В F# идиоматично использовать некоторые изменяемые переменные на уровне класса или модуля. Но я так понимаю, вы, вероятно, заинтересованы в использовании монады состояния из педагогических соображений?   -  person wmeyer    schedule 03.03.2011
comment
@wmeyer, ты прав. Хотя, если вы опубликуете ответ с идиоматической версией, я, тем не менее, проголосую за него, ради других, которые могут захотеть узнать, как это сделать правильно для F#.   -  person J Cooper    schedule 03.03.2011
comment
У Programming F# Криса Смита есть раздел на эту тему. Вы можете увидеть большую часть (но не весь) раздел в предварительном просмотре на Google Книги. (Я пишу это как комментарий вместо ответа, потому что это не полный ответ, а просто ссылка.)   -  person wmeyer    schedule 03.03.2011


Ответы (2)


Если вы хотите использовать монаду состояния для передачи инвентаря игрока и состояния мира через функцию 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")
person kvb    schedule 04.03.2011
comment
Вау, это проще, чем я думал! Спасибо! - person J Cooper; 04.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 может просто вернуть некоторый объект, представляющий запрос на выбор предмета. Тогда у мира будет некий движок, который оценивает эти запросы (собранные от всех игроков) и вычисляет новое состояние мира.

person Tomas Petricek    schedule 03.03.2011
comment
Но подход с использованием монад состояния чист, разве это не имеет значения? Тем не менее, в конце концов я, вероятно, выберу ваш метод, так как он самый идиоматический. Хотя мне кажется, что я пишу на C#, а не на F# :) - person J Cooper; 04.03.2011
comment
@J Cooper: монадный подход заставляет вас писать программу последовательно, поэтому это важно в Haskell. Вам действительно не нужно это в F #. Как и в случае с изменчивостью, это делает состояние неявным, поэтому, если вы переупорядочиваете строки, которые не зависят друг от друга (напрямую), вы можете получить другое поведение программы (из-за состояния). - person Tomas Petricek; 04.03.2011
comment
Вы можете написать программу более функционально, если сделаете все объекты неизменяемыми, а все операции создадут новое состояние объекта или мира. Тогда вы, вероятно, захотите использовать немного другую архитектуру. - person Tomas Petricek; 04.03.2011
comment
Кроме того, вы можете извлечь выгоду из функционального стиля, даже если вы используете изменяемые объекты — при реализации некоторых вычислений или алгоритмов обработки функциональный стиль поможет. - person Tomas Petricek; 04.03.2011
comment
Как бы вы построили новое состояние мира? Представьте, что в мире есть несколько игроков, и вам нужно изменить какое-то состояние одного из игроков. Вы бы вернули новый мир со списком игроков, созданным путем замены 1 игрока в старом списке? Это кажется очень неэффективным. - person chrisortman; 01.08.2013