1

I wanted to try something new, so I've decided to wrap my head around Haskell. Coming from a mainly C#/Java background, I have a question about updating various values. I've managed to narrow it down to a single problem. Consider the following function:

appendHistory :: State -> Action -> State
appendHistory (State gs p1 p2 h) a = State gs p1 p2 (a : h)

This basically appends the action to the state's history. gs, p1 and p2 are rather irrelevant in this case, since we're trying to update h. For the sake of copying the variables they need to be named (so I can use them on the right hand side of =). Is there any way I can write appendHistory, so that h gets updated without having to specify gs, p1 and p2 explicitly?

One option would be to let other functions handle retrieving and updating state. appendHistory now doesn't have to specify the other parameters. Not my faviorite, as we've just moved our problem to updateHistory. In Haskell:

appendHistory :: State -> Action -> State
appendHistory s a = updateHistory s (a : (history s))

history :: State -> History
history (State _ _ _ h) = h

updateHistory :: State -> History -> State
updateHistory (State s p1 p2 _) h = State s p1 p2 h

Another way would be to use records. But I've been told (/read) that name clashes might occur when using the same names. I think I'm going to have a lot of records with fieldname state, so I've been avoiding those for the moment.

I guess, all I want to do is (non-functionally):

void Update(State state, Action action) {
    state.history.append(action);
}

Is there a way to do this 'nicely' in Haskell, without having to write a 'getter' and a 'setter' for every data parameter?


For full reference:

type History = [Action]
type Deck = [Card]
type Hand = [Card]
type Graveyard = [Card]

data Card = Card -- Still to expand

data PlayerState = PlayerState Hand Deck Graveyard
data GameState = NotStarted | Playing | Finished

data State = State GameState PlayerState PlayerState History

Actions are just definitions on how to mutate State.

Caramiriel
  • 7,029
  • 3
  • 30
  • 50
  • 3
    This is exactly the sort of thing lenses are used for. – melpomene Apr 27 '18 at 11:39
  • 1
    I suggest you look into Record Syntax, as it allows you to do just that. I currently don't have time to write up a full answer, but you should also include the definition of `State` if you want a complete answer. – Mor A. Apr 27 '18 at 11:49
  • So that pushes me to records in both cases? I'll add a definition for `State`, although I would've thought that it wouldnt matter if it was either an `Integer` or something more complex. – Caramiriel Apr 27 '18 at 12:17
  • If you use lenses you don't have to use records, although it makes defining the lenses easier. – Mor A. Apr 27 '18 at 12:31
  • Alright, thanks for the pointers, both of you. Feel free to add an answer so I can accept it, or mark it as a duplicate if it is. – Caramiriel Apr 27 '18 at 12:43

1 Answers1

2

As melpomene commented, lenses are the modern standard answer to your question. You can define lenses manually

{-# LANGUAGE Rank2Types, TypeFamilies #-}
import Control.Lens

gameHistory :: Lens' State History
gameHistory = lens (\(State _ _ _ h) -> h)
                   (\(State gs p1 p2 _) h -> State gs p1 p2 h)

...or let Template Haskell do it for you

{-# LANGUAGE TemplateHaskell #-}
data State = State {
        _gameState :: GameState
      , _player₀State, _player₁State :: PlayerState
      , _gameHistory :: History }
makeLenses ''State

which automatically creates

gameState :: Lens' State GameState
player₀State :: Lens' State GameState
player₁State :: Lens' State GameState
gameHistory :: Lens' State History

Once you have such a lens, you can use the operators for tweaking State objects:

appendHistory :: Action -> State -> State -- more conventional argument order in Haskell
appendHistory a = gameHistory %~ (a:)
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319