我最近看了一下Haskell的Monad - State。我已经能够创建在Monad上运行的函数,但我正在尝试将行为封装到一个类中,基本上我正在尝试在Haskell中复制类似如下的内容:
class A {
public:
int x;
void setX(int newX) {
x = newX;
}
void getX() {
return x;
}
}如果有人能帮上忙,我将不胜感激。谢谢!
发布于 2011-06-01 23:43:57
我首先要指出的是,Haskell,至少可以说,并不鼓励传统的OO风格的开发;相反,它的特性和特征非常适合于你在许多其他语言中找不到的那种“纯函数式”操作;缺点是,试图从其他(传统语言)中“引入”概念通常是一个非常糟糕的想法。
,但我正在尝试将行为封装到一个类中
因此,我想到的第一个主要问题是为什么?你肯定想对这个(传统的面向对象概念)类做点什么吧?
如果这个问题的大致答案是:“我想对某种数据结构建模”,那么您最好使用下面这样的东西
data A = A { xval :: Int }
> let obj1 = A 5
> xval obj1
5
> let obj2 = obj1 { xval = 10 }
> xval obj2
10它演示了纯的、不可变的数据结构,以及'getter‘函数和破坏性更新(利用记录语法)。这样,你就可以做任何你需要做的工作,将这些“数据结构”映射到新的数据结构。
现在,如果您确实需要某种类型的状态模型(实际上,回答这个问题需要一点经验来准确地知道什么是局部状态与全局状态是),只有到那时您才会深入使用State Monad,如下所示:
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(厚颜无耻地从this tutorial上搬过来)。请注意,除了“put”和“get”一元函数之外,还在状态monad的上下文中使用了类似的“纯不可变数据结构”,这有助于访问state Monad中包含的状态。
最后,我建议你问问自己:你真正想要用这个(OO)类模型实现什么?Haskell不是典型的面向对象语言,试图一对一地映射概念只会在短期(也可能是长期)让您感到沮丧。这应该是一个标准的咒语,但我强烈建议您从Real World Haskell一书中学习,在这本书中,作者能够深入研究更详细的“动机”,以选择任何一个工具或抽象。如果您是坚定的,您可以在Haskell中对传统的OO构造进行建模,但是我不建议这样做--除非您有一个非常好的理由这样做。
发布于 2011-06-02 02:57:32
将命令式代码转换为纯函数上下文需要进行一些置换。
setter使对象发生变化。由于惰性和纯洁性,我们不允许直接在Haskell中这样做。
也许,如果我们将State monad转录成另一种语言,它会更明显。你的代码是用C++编写的,但是因为我至少想要垃圾回收,所以我在这里使用java。
由于Java从未定义过匿名函数,因此首先,我们将为纯函数定义一个接口。
public interface Function<A,B> {
B apply(A a);
}然后我们可以构造一个纯的不可变的对类型。
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 + ")";
}
}有了这些,我们实际上可以定义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);
}
}
}
}现在,在state monad中确实存在getter和setter的概念。这就是所谓的透镜。Java的基本表示形式如下所示:
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.
}这个想法是,透镜提供了访问getter和setter的途径,getter知道如何从A中提取B,setter知道如何获取B和一些旧的A,并替换部分A,从而产生新的A。它不能改变旧的A,但它可以构造一个新的字段,其中一个字段被替换。
我在最近的波士顿地区Scala爱好者会议上做了一次关于这些的演讲。你可以使用watch the presentation here。
回到Haskell,而不是在命令式的环境中谈论事情。我们可以导入
import Data.Lens.Lazy来自comonad-transformers或one of the other lens libraries mentioned here。这种联系提供了必须满足的定律才能成为有效的透镜。
然后,您需要的是某种数据类型,例如:
data A = A { x_ :: Int }带着镜头
x :: Lens A Int
x = lens x_ (\b a -> a { x_ = b })现在,您可以编写如下代码
postIncrement :: State A Int
postIncrement = do
old_x <- access x
x ~= (old_x + 1)
return old_x使用Data.Lens.Lazy中的组合子。
上面提到的其他镜片库提供了类似的组合器。
发布于 2011-06-02 02:15:04
首先,我同意Raeez的观点,这可能是错误的做法,除非你真的知道为什么!如果你想将一些值增加42 (比方说),为什么不写一个函数来帮你做到这一点?
这与传统的OO思维模式有很大的不同,在传统的OO思维模式中,你有一个盒子,里面有值,然后你把它们拿出来,操纵它们,再把它们放回去。我会说,直到你开始注意到这样的模式:“嘿!我总是将一些值作为参数,并在结束时返回稍微修改过的值,与其他值组合在一起,所有的管道都变得混乱!”您并不真正需要State monad。(学习) Haskell的乐趣之一是找到新的方法来绕过有状态的OO思维!
也就是说,如果您绝对想要一个包含Int类型的x的盒子,您可以尝试制作自己的get和put变体,如下所示:
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现在,如果在ghci中计算runState test (Point 2 9),您将得到(12,Point {x = 7, y = 9}) (因为12 =2+ (2+1) +7,并且状态中的x在最后被设置为7)。如果你不关心返回点,你可以使用evalState,你只会得到12。
这里还有更高级的事情要做,比如用一个类型类抽象出Point,以防你有多个数据结构具有类似x的行为,但在我看来,这最好留给另一个问题来解决!
https://stackoverflow.com/questions/6203251
复制相似问题