从C++到哈斯克尔类和状态
From C ++ to Haskell Classes and States
我必须转换这个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();
}
到 Haskell 代码中,并使用 State(或 StateT(Monad 模拟main()
。
到目前为止,我所做的是:
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)
测试正在main()
.
现在关于我遇到的问题:当我使用 lift 时,它可以正常工作,因为该功能变为 StateT
FieldsB
型,但是当我尝试在没有 lift 的情况下使用setX_A
时,会出现一个问题
*** Type : StateT FieldsA IO ()
*** Does not match : StateT FieldsA (StateT FieldsB IO) ()
如果我将setX_A
的类型更改为第二种,那么当我将其与 lift 一起使用时,它将不起作用(因为 B 类是从 A 派生的(。
首先,感谢您提供大量细节,它使您更容易理解您的问题!
现在,您在这里采用的方法可能并不理想。它为每个对象引入了一个新的StateT
,这是导致您遇到的许多困难的原因,添加更多对象会使事情逐渐变得更糟。同样使问题复杂化的是,Haskell没有内置的子类型概念,并且使用类型类上下文模仿它将...工作,有点笨拙,不是最好的。
虽然我相信你意识到这是非常命令式的代码,并且直接将其翻译成Haskell有点愚蠢,但这就是任务,所以让我们谈谈更接近标准Haskell的方法。
命令式代码
状态单子风格:
暂时IO
放在一边,要在纯代码中执行类似操作,典型的方法是:
- 创建保存所有状态的数据类型
- 使用
get
和put
"修改"状态
对于输出,您可以在 IO
周围使用 StateT
,或者您可以在表示输出的状态数据中添加一个字段,保存 String
的列表,并在没有IO
的情况下执行整个操作。
这是最接近当前方法的"正确"方法,大致是@Rotsor建议的。
IO 单子样式
上面仍然要求在函数外部预先指定所有可变变量,方法是在状态数据中定义它们。与其以这种方式处理事情,您还可以更直接地模仿原始代码,并在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
这在概念上更接近原文,并且符合@augustss在关于该问题的评论中所建议的。
略有变化是将对象保留为简单值,但使用IORef
来保存当前版本。这两种方法之间的区别大致相当于,在 OOP 语言中,具有更改内部状态的 setter 方法的可变对象与具有可变引用它们的不可变对象。
对象
另一半困难在于 Haskell 中的继承建模。你使用的方法是最明显的方法,许多人跳到,但它有些有限。例如,您不能在任何需要超类型的上下文中真正使用对象;例如,如果一个函数有一个像(A a) => a -> a -> Bool
这样的类型,没有简单的方法可以将其应用于A
的两个不同的子类型。您必须将自己的强制转换实现到超类型。
这是一个替代翻译的草图,我认为在 Haskell 中使用它既更自然,又更准确地用于 OOP 风格。
首先,观察所有类方法如何将对象作为第一个参数。这表示 OOP 语言中隐含的"这个"或"自我"。我们可以通过将方法预先应用于对象的数据来保存步骤,以获取已经"绑定"到该对象的方法集合。然后我们可以将这些方法存储为数据类型:
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 ()
}
我们将使用它们来提供对超类型的强制转换,而不是使用类型类来提供方法:
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
我们可以使用更高级的技巧来避免为每个伪 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
为了构造新对象,我们将使用初始化内部数据的函数(相当于 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
}
请注意,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
我认为您的问题是您没有一种很好的方法来指定您正在操作的对象。为了解决这个问题,我建议使用单独的程序状态,将两个对象都括起来:
data MainState = MainState { objA :: FieldsA, objB :: FieldsB }
现在你的主函数monad可以看起来像这样:
type Main t = StateT MainState IO t
并且,要选择您正在使用的对象,您可以使用如下内容:
withObjA :: StateT FieldsA IO t -> Main t
withObjB :: StateT FieldsB IO t -> Main t
用法如下所示:
test :: Main ()
test = do
withObjA $ do
setX_A 2
printX_A
withObjB $ do
printX_A
setX_B 5
printX_B
更新:
以下是实现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 })
在这里,函数withPart
action
在part
上操作提升为在whole
上运行的操作,使用getPart
从整体中提取部分,setPart
更新整体的一部分。如果有人告诉我库函数做类似的事情,我将不胜感激。 withObjA
和withObjB
是通过将它们各自的访问器函数传递给withPart
来实现的。
- 莱克斯没有返回我想要的东西
- 斯塔克,堆栈,也可以在底部和顶部添加整数
- 皮克斯美元LNK2019:未解析的外部交易品种
- 东康斯泰克普/康斯蒂尼特/康斯特瓦尔在C++20 中允许吗?
- 康斯坦特还行,但不是康特克斯普尔?
- 快速计算变换/旋转马里克斯4x4
- 接口哈斯克尔和C++
- C++ 克鲁斯卡尔算法的实现
- 使用具有可定义状态的函子作为哈希函数unordered_set
- 将unique_ptr传递给斯特托克
- 斯波伊素数猜想普里梅祖克
- 如何获得最后一个斯特托克
- 克鲁斯卡尔的算法解释
- 使用角落哈里斯知道角落的数字
- 克鲁斯卡尔算法用矢量转换
- 常量与变量上的常量与康特克斯PR
- 斯芬克斯在没有数据库的情况下工作吗
- 如何计算哈斯克尔中的正弦函数
- "True Polymorphism"的例子?(最好使用哈斯克尔)
- 从C++到哈斯克尔类和状态