I have to transform this C++ code
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();
}
into Haskell code, and simulate main()
using State (or StateT) Monad.
What I have done so far is this:
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)
test being main()
.
Now about the problem I have: When I use lift, it works ok, because the function becomes of type StateT
FieldsB
, but when I try to use setX_A
without lift there is a problem
*** Type : StateT FieldsA IO ()
*** Does not match : StateT FieldsA (StateT FieldsB IO) ()
If I change the type of setX_A
to the second one, then It won't work when I use it with lift (because class B is derived from A).
Haskell classes are roughly similar to a Java interface. Like an interface declaration, a Haskell class declaration defines a protocol for using an object rather than defining an object itself.
Ord is a subclass of Eq that is used for data types that have a total ordering (every value can be compared with another).
A polymorphic function is called overloaded if its type contains one or more class constraints. (+) :: Num a ⇒ a -> a -> a. For any numeric type a, (+) takes two values of type a and returns a value of type a.
An instance of a class is an individual object which belongs to that class. In Haskell, the class system is (roughly speaking) a way to group similar types. (This is the reason we call them "type classes"). An instance of a class is an individual type which belongs to that class.
First of all, thank you for the amount of detail given, it makes it much easier to understand your problem!
Now, the approach you're taking here is probably not ideal. It introduces a new StateT
for each object, which is what's causing a lot of the difficulties you're experiencing, and adding more objects will make things progressively worse. Also complicating matters is that Haskell doesn't have a built-in notion of subtyping, and imitating it with type class contexts will... work, sort of, it's clumsy and not the best.
While I'm sure you realize this is very imperative-style code and translating it to Haskell directly is a bit silly, that's the assignment, so let's talk about ways to do this that are a bit closer to standard Haskell.
Setting IO
aside for now, to do something like this in pure code the typical approach would be something like:
get
and put
For output, you can use StateT
around IO
, or you could add a field to the state data representing the output, holding a list of String
s, and do the whole thing without IO
.
This is closest to the "right" way to do your current approach, and is roughly what @Rotsor suggests.
The above still requires that all mutable variables be specified beforehand, outside the function, by defining them in the state data. Rather than juggle things this way, you could also imitate the original code more directly and use real, honest-to-god mutable state in IO
. Using just A
as an example, you'd have something like this:
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
This is conceptually much closer to the original, and is along the lines of what @augustss suggested in the comments on the question.
A slight variation is to keep the object as a simple value, but use an IORef
to hold the current version. The difference between the two approaches is roughly equivalent to, in an OOP language, a mutable object with setter methods that change internal state vs. immutable objects with mutable references to them.
The other half of the difficulty is in modeling inheritance in Haskell. The approach you're using is the most obvious one that many people jump to, but it's somewhat limited. For instance, you can't truly use objects in any context where a supertype is expected; e.g., if a function has a type like (A a) => a -> a -> Bool
, there's no simple way to apply it to two different subtypes of A
. You'd have to implement your own casting to the supertype.
Here's a sketch of an alternate translation that I would argue is both more natural to use in Haskell, and more accurate to OOP style.
First, observe how all the class methods take the object as a first argument. This represents the implicit "this" or "self" in OOP languages. We can save a step by pre-applying the methods to the object's data, to get a collection of methods already "bound" to that object. We can then store those methods as a data type:
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 ()
}
Instead of using type classes to provide methods, we'll use them to provide casting to a supertype:
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
There are more advanced tricks we could use to avoid making a type class for each pseudo-OOP "class", but I'm keeping things simple here.
Notice that I prefixed the object fields above with underscores. That's because those are specific to the type; now we can define the "real" methods for any type that can be cast to the one we need:
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
To construct new objects, we'll use functions that initialize the internal data--equivalent to private members in an OOP language--and create the type representing the object:
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
}
Note that newB
calls newA
and gets the data type holding its member functions. It can't access the "private" members of A
directly, but it could replace any of A
's functions if it wanted to.
Now we can use these in a way that's almost identical, both in style and meaning, to your original, e.g.:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With