epi*_*ose 13 c++ monads haskell
我必须转换这个C++代码
class A {
public:
int x_A;
void setX_A (int newx) {
x_A = newx;
}
void printX_A() {
printf("x_A is %d", x_A);
}
};
class B : public A {
public:
int x_B;
void setX_B (int newx) {
x_B = newx;
}
void printX_B() {
printf("x_B is %d", x_B);
}
};
main() {
A objA;
B objB;
objA.setX_A(2);
objA.printX_A();
objB.printX_A();
objB.setX_B(5);
objB.printX_B();
}
Run Code Online (Sandbox Code Playgroud)
进入Haskell代码,并main()
使用State(或StateT)Monad进行模拟.
到目前为止我所做的是:
import Control.Monad.State
import Control.Monad.Identity
-- Fields For A
data FieldsA = FieldsA {x_A::Int} deriving (Show)
-- A Class Constructor
constA :: Int -> FieldsA
constA = FieldsA
class A a where
getX_A :: StateT a IO Int
setX_A :: Int -> StateT a IO ()
printX_A :: StateT a IO ()
instance A FieldsA where
getX_A = get >>= return . x_A
setX_A newx = do
fa <- get
put (fa { x_A = newx })
printX_A = do
fa <- get
liftIO $ print fa
return ()
data FieldsB = FieldsB{ fa::FieldsA, x_B::Int } deriving (Show)
constB :: Int -> Int -> FieldsB
constB int1 int2 = FieldsB {fa = constA int1, x_B = int2}
class A b => B b where
getX_B :: StateT b IO Int
setX_B :: Int -> StateT b IO ()
printX_B :: StateT b IO ()
-- A Functions for Class B
instance A FieldsB where
getX_A = do
(FieldsB (FieldsA x_A) x_B) <- get
return (x_A)
setX_A newx = do
(FieldsB (FieldsA x_A) x_B) <- get
put (constB newx x_B)
printX_A = do
fb <- get
liftIO $ print fb
return ()
-- B specific Functions
instance B FieldsB where
getX_B = get >>= return . x_B
setX_B newx = do
fb <- get
put (fb { x_B = newx })
printX_B = do
fb <- get
liftIO $ print fb
return ()
test :: StateT FieldsA (StateT FieldsB IO ) ()
test = do
x <- get
setX_A 4
printX_A
--lift $ setX_A 99
--lift $ setX_B 99
--lift $ printX_A
--lift $ printX_B
--printX_A
return ()
go = evalStateT (evalStateT test (constA 1)) (constB 2 3)
--go = runIdentity $ evalStateT (evalStateT test (constA 1)) (constA 1)
Run Code Online (Sandbox Code Playgroud)
测试存在main()
.
现在关于我StateT
FieldsB
遇到的问题:当我使用电梯时,它工作正常,因为功能变得类型,但是当我尝试使用setX_A
没有电梯时出现问题
*** Type : StateT FieldsA IO ()
*** Does not match : StateT FieldsA (StateT FieldsB IO) ()
Run Code Online (Sandbox Code Playgroud)
如果我将类型更改为setX_A
第二个,那么当我使用它时它将无法工作(因为B类派生自A).
C. *_*ann 13
首先,感谢您提供的详细信息,让您更容易理解您的问题!
现在,你在这里采取的方法可能并不理想.它StateT
为每个对象引入了一个新的东西,这正是导致你遇到的许多困难的原因,添加更多的对象会使事情逐渐恶化.同样令事情变得复杂的是Haskell没有内置的子类型概念,并且用类型类上下文模仿它会......工作,有点,笨拙而不是最好.
虽然我确定你意识到这是非常命令式的代码并且直接将它转换为Haskell有点愚蠢,这就是赋值,所以让我们谈谈如何做到这一点,更接近标准的Haskell.
设置IO
放在一边,做这样的事情在纯代码典型的做法是这样的:
get
和"修改"状态put
对于输出,你可以使用StateT
around IO
,或者你可以在表示输出的状态数据中添加一个字段,保存一个String
s 的列表,并且不做任何事情IO
.
这最接近于采用当前方法的"正确"方式,大致与@Rotsor建议的方式相同.
上述内容仍然要求通过在状态数据中定义所有可变变量,在函数外部预先指定它们.你可以更直接地模仿原始代码,而不是以这种方式处理事情,并使用真实的,诚实的上帝可变状态IO
.仅A
作为示例,您将拥有以下内容:
data FieldsA = FieldsA { x_A :: IORef Int}
constA :: Int -> IO FieldsA
constA x = do xRef <- newIORef x
return $ FieldsA xRef
class A a where
getX_A :: a -> IO Int
setX_A :: a -> Int -> IO ()
printX_A :: a -> IO ()
instance A FieldsA where
getX_A = readIORef . x_A
setX_A = writeIORef . x_A
printX_A a = getX_A a >>= print
Run Code Online (Sandbox Code Playgroud)
这在概念上更接近于原始版本,并且与@augustss在该问题的评论中所建议的一致.
稍有不同的是将对象保持为简单值,但使用a IORef
来保存当前版本.两种方法之间的差异大致等同于在OOP语言中,具有setter方法的可变对象,这些方法通过对它们的可变引用来更改内部状态与不可变对象.
另一半困难在于在Haskell中建模继承.你正在使用的方法是许多人跳过的最明显的方法,但它有点受限.例如,在预期超类型的任何上下文中,您无法真正使用对象; 例如,如果函数具有类似的类型(A a) => a -> a -> Bool
,则没有简单的方法将其应用于两个不同的子类型A
.你必须为超类型实现自己的转换.
这是一个替代翻译的草图,我认为在Haskell中使用它更自然,对OOP风格更准确.
首先,观察所有类方法如何将对象作为第一个参数.这代表了OOP语言中隐含的"this"或"self".我们可以通过将方法预先应用于对象的数据来保存步骤,以获得已经"绑定"到该对象的方法的集合.然后我们可以将这些方法存储为数据类型:
data A = A { _getX_A :: IO Int
, _setX_A :: Int -> IO ()
, _printX_A :: IO ()
}
data B = B { _parent_B :: A
, _getX_B :: IO Int
, _setX_B :: Int -> IO ()
, _printX_B :: IO ()
}
Run Code Online (Sandbox Code Playgroud)
我们将使用它们来提供超类型的转换,而不是使用类型类来提供方法:
class CastA a where castA :: a -> A
class CastB b where castB :: b -> B
instance CastA A where castA = id
instance CastA B where castA = _parent_B
instance CastB B where castB = id
Run Code Online (Sandbox Code Playgroud)
我们可以使用更高级的技巧来避免为每个伪OOP"类"创建一个类型类,但我在这里保持简单.
请注意,我使用下划线为上面的对象字段添加了前缀.那是因为那些是特定的类型; 现在我们可以为任何可以转换为我们需要的类型定义"真实"方法:
getX_A x = _getX_A $ castA x
setX_A x = _setX_A $ castA x
printX_A x = _printX_A $ castA x
getX_B x = _getX_B $ castB x
setX_B x = _setX_B $ castB x
printX_B x = _printX_B $ castB x
Run Code Online (Sandbox Code Playgroud)
要构造新对象,我们将使用初始化内部数据的函数 - 相当于OOP语言中的私有成员 - 并创建表示对象的类型:
newA x = do xRef <- newIORef x
return $ A { _getX_A = readIORef xRef
, _setX_A = writeIORef xRef
, _printX_A = readIORef xRef >>= print
}
newB xA xB = do xRef <- newIORef xB
parent <- newA xA
return $ B { _parent_B = parent
, _getX_B = readIORef xRef
, _setX_B = writeIORef xRef
, _printX_B = readIORef xRef >>= print
}
Run Code Online (Sandbox Code Playgroud)
请注意,newB
调用newA
并获取包含其成员函数的数据类型.它无法直接访问"私有"成员A
,但如果愿意,它可以替换任何A
函数.
现在我们可以使用这些在风格和含义上几乎完全相同的方式,例如:
test :: IO ()
test = do a <- newA 1
b <- newB 2 3
printX_A a
printX_A b
setX_A a 4
printX_A a
printX_B b
Run Code Online (Sandbox Code Playgroud)
我认为你的问题是你没有一个很好的方法来指定你正在操作的对象.为了解决这个问题,我建议使用一个单独的程序状态,包含两个对象:
data MainState = MainState { objA :: FieldsA, objB :: FieldsB }
Run Code Online (Sandbox Code Playgroud)
现在您的主要功能monad可能如下所示:
type Main t = StateT MainState IO t
Run Code Online (Sandbox Code Playgroud)
并且,要选择您正在使用的对象,您可以使用以下内容:
withObjA :: StateT FieldsA IO t -> Main t
withObjB :: StateT FieldsB IO t -> Main t
Run Code Online (Sandbox Code Playgroud)
用法如下:
test :: Main ()
test = do
withObjA $ do
setX_A 2
printX_A
withObjB $ do
printX_A
setX_B 5
printX_B
Run Code Online (Sandbox Code Playgroud)
更新:
以下是如何实现withObjA
和withObjB
:
withPart :: Monad m => (whole -> part) -> (part -> whole -> whole) -> StateT part m t -> StateT whole m t
withPart getPart setPart action = do
whole <- get
(t, newPart) <- lift $ runStateT action (getPart whole)
put (setPart newPart whole)
return t
withObjA :: StateT FieldsA IO t -> Main t
withObjA = withPart objA (\objA mainState -> mainState { objA = objA })
withObjB :: StateT FieldsB IO t -> Main t
withObjB = withPart objB (\objB mainState -> mainState { objB = objB })
Run Code Online (Sandbox Code Playgroud)
这里,函数withPart
促进action
对a part
上的操作进行操作whole
,使用getPart
从整体中提取部分并setPart
更新整体的一部分.如果有人告诉我库函数做类似的事情,我将不胜感激.withObjA
并withObjB
通过将各自的访问者函数传递给它们来实现withPart
.