Class set method in Haskell using State-Monad

2019-09-01 16:32发布

问题:

I've recently had a look at Haskell's Monad - State. I've been able to create functions that operate with this Monad, but I'm trying to encapsulate the behavior into a class, basically I'm trying to replicate in Haskell something like this:

class A {
  public:
  int x;
  void setX(int newX) {
      x = newX;
  }
  void getX() {
      return x;
  }
}

I would be very grateful if anyone can help with this. Thanks!

回答1:

I would start off by noting that Haskell, to say the least, does not encourage traditional OO-style development; instead, it has features and characteristics that lend themselves well to the sort of 'pure functional' manipulation that you won't really find in many other languages; the short on this is that trying to 'bring over' concepts from other (traditional languages) can often be a very bad idea.

but I'm trying to encapsulate the behavior into a class

Hence, my first major question that comes to mind is why? Surely you must want to do something with this (traditional OO concept of a) class?

If an approximate answer to this question is: "I'd like to model some sort of data construct", then you'd be better off working with something like

data A = A { xval :: Int }

> let obj1 = A 5
> xval obj1
5
> let obj2 = obj1 { xval = 10 }
> xval obj2
10

Which demonstrates pure, immutable data structures, along with 'getter' functions and destructive updates (utilizing record syntax). This way, you'd do whatever work you need to do as some combination of functions mapping these 'data constructs' to new data constructs, as appropriate.

Now, if you absolutely needed some sort of model of State (and indeed, answering this question requires a bit of experience in knowing exactly what local versus global state is), only then would you delve into using the State Monad, with something like:

module StateGame where

import Control.Monad.State

-- Example use of State monad
-- Passes a string of dictionary {a,b,c}
-- Game is to produce a number from the string.
-- By default the game is off, a C toggles the
-- game on and off. A 'a' gives +1 and a b gives -1.
-- E.g 
-- 'ab'    = 0
-- 'ca'    = 1
-- 'cabca' = 0
-- State = game is on or off & current score
--       = (Bool, Int)

type GameValue = Int
type GameState = (Bool, Int)

playGame :: String -> State GameState GameValue
playGame []     = do
    (_, score) <- get
    return score

playGame (x:xs) = do
    (on, score) <- get
    case x of
         'a' | on -> put (on, score + 1)
         'b' | on -> put (on, score - 1)
         'c'      -> put (not on, score)
         _        -> put (on, score)
    playGame xs

startState = (False, 0)

main = print $ evalState (playGame "abcaaacbbcabbab") startState

(shamelessly lifted from this tutorial). Note the use of the analogous 'pure immutable data structures' within the context of a state monad, in addition to 'put' and 'get' monadic functions, which facilitate access to the state contained within the State Monad.

Ultimately, I'd suggest you ask yourself: what is it that you really want to accomplish with this model of an (OO) class? Haskell is not your typical OO-language, and trying to map concepts over 1-to-1 will only frustrate you in the short (and possibly long) term. This should be a standard mantra, but I'd highly recommend learning from the book Real World Haskell, where the authors are able to delve into far more detailed 'motivation' for picking any one tool or abstraction over another. If you were adamant, you could model traditional OO constructs in Haskell, but I wouldn't suggest going about doing this - unless you have a really good reason for doing so.



回答2:

It takes a bit of permuting to transform imperative code into a purely functional context.

A setter mutates an object. We're not allowed to do that directly in Haskell because of laziness and purity.

Perhaps, if we transcribe the State monad to another language it'll be more apparent. Your code is in C++, but because I at least want garbage collection I'll use java here.

Since Java never got around to defining anonymous functions, first, we'll define an interface for pure functions.

public interface Function<A,B> {
    B apply(A a);
}

Then we can make up a pure immutable pair type.

public final class Pair<A,B> {
    private final A a;
    private final B b;
    public Pair(A a, B b) {
        this.a = a;
        this.b = b;
    }
    public A getFst() { return a; }
    public B getSnd() { return b; }
    public static <A,B> Pair<A,B> make(A a, B b) {
        return new Pair<A,B>(a, b);
    }
    public String toString() {
        return "(" + a + ", " + b + ")";
    }
}

With those in hand, we can actually define the State monad:

public abstract class State<S,A> implements Function<S, Pair<A, S> > {

    // pure takes a value a and yields a state action, that takes a state s, leaves it untouched, and returns your a paired with it. 
    public static <S,A> State<S,A> pure(final A a) {
        return new State<S,A>() {
            public Pair<A,S> apply(S s) {
                return new Pair<A,S>(a, s);
            }
        };
    }
    // we can also read the state as a state action.
    public static <S> State<S,S> get() {
        return new State<S,S>() {
            public Pair<S,S> apply(S, s) {
                return new Pair<S,S>(s, s);
            }
        } 
    }

    // we can compose state actions
    public <B> State<S,B> bind(final Function<A, State<S,B>> f) {
        return new State<S,B>() {
            public Pair<B,S> apply(S s0) {
                Pair<A,S> p = State.this.apply(s0);
                return f.apply(p.getFst()).apply(p.getSnd());
            }
        };
    }

    // we can also read the state as a state action.
    public static <S> State<S,S> put(final S newS) {
        return new State<S,S>() {
            public Pair<S,S> apply(S, s) {
                return new Pair<S,S>(s, newS);
            }
        } 
    }
}

Now, there does exist a notion of a getter and a setter inside of the state monad. These are called lenses. The basic presentation in Java would look like:

public abstract class Lens[A,B] {
    public abstract B get(A a);
    public abstract A set(B b, A a);
    // .. followed by a whole bunch of concrete methods.
}

The idea is that a lens provides access to a getter that knows how to extract a B from an A, and a setter that knows how to take a B and some old A, and replace part of the A, yielding a new A. It can't mutate the old one, but it can construct a new one with one of the fields replaced.

I gave a talk on these at a recent Boston Area Scala Enthusiasts meeting. You can watch the presentation here.

To come back into Haskell, rather than talk about things in an imperative setting. We can import

import Data.Lens.Lazy

from comonad-transformers or one of the other lens libraries mentioned here. That link provides the laws that must be satisfied to be a valid lens.

And then what you are looking for is some data type like:

data A = A { x_ :: Int }

with a lens

x :: Lens A Int
x = lens x_ (\b a -> a { x_ = b })

Now you can write code that looks like

postIncrement :: State A Int
postIncrement = do
    old_x <- access x
    x ~= (old_x + 1)
    return old_x

using the combinators from Data.Lens.Lazy.

The other lens libraries mentioned above provide similar combinators.



回答3:

First of all, I agree with Raeez that this probably the wrong way to go, unless you really know why! If you want to increase some value by 42 (say), why not write a function that does that for you?

It's quite a change from the traditional OO mindset where you have boxes with values in them and you take them out, manipulate them and put them back in. I would say that until you start noticing the pattern "Hey! I always take some value as an argument, and at the end return it slightly modified, tupled with some other value and all the plumbing is getting messy!" you don't really need the State monad. Part of the fun of (learning) Haskell is finding new ways to get around the stateful OO thinking!

That said, if you absolutely want a box with an x of type Int in it, you could try making your own get and put variants, something like this:

import Control.Monad.State

data Point = Point { x :: Int, y :: Int } deriving Show

getX :: State Point Int
getX = get >>= return . x

putX :: Int -> State Point ()
putX newVal = do
    pt <- get
    put (pt { x = newVal })

increaseX :: State Point ()
increaseX = do
    x <- getX
    putX (x + 1)

test :: State Point Int
test = do
    x1 <- getX
    increaseX
    x2 <- getX
    putX 7
    x3 <- getX
    return $ x1 + x2 + x3

Now, if you evaluate runState test (Point 2 9) in ghci, you'll get back (12,Point {x = 7, y = 9}) (since 12 = 2 + (2+1) + 7 and the x in the state gets set to 7 at the end). If you don't care about the returned point, you can use evalState and you'll get just the 12.

There's also more advanced things to do here like abstracting out Point with a typeclass in case you have multiple datastructures which have something which behaves like x but that's better left for another question, in my opinion!