I'm wondering how to create two encapsulated types that interact with each other, without exposing the internal implementation to other modules.
As an example, consider my two modules, Vector.hs and Matrix.hs, that wrap Linear.V4 (a 4-element vector) and Linear.M44 (a 4x4 element matrix). I wish to write a function that multiplies a Matrix by a Vector, returning a Vector, and using the wrapped data types to perform the operation.
In this example, in Vector.hs I have:
-- Vector.hs
module Vector (Vector, vector) where
import Linear (V4 (V4))
newtype Vector = Vector (V4 Double) deriving (Eq, Show, Read)
vector :: (Double, Double, Double, Double) -> Vector
vector (x, y, z, w) = Vector (V4 x y z w)
Note that I'm only exporting the new type and the factory function - the data constructor Vector is not exported. As far as I understand, this hides the internal implementation of Vector (i.e. that it's really a V4).
And in Matrix.hs I have something similar:
-- Matrix.hs
module Matrix (Matrix, vector)
import Linear (V4 (V4), M44)
type Row = (Double, Double, Double, Double)
newtype Matrix = Matrix (M44 Double) deriving (Eq, Show, Read)
matrix :: (Row, Row, Row, Row) -> Matrix
--matrix ((a00, a01, a02, a03), (a10, ... ) = Matrix (V4 (V4 a00 a01 a02 a03) (V4 a10 ...))
These two modules can be used by client code pretty effectively - the client code is not aware that they are implemented with Linear data structures, and there's apparently no way for client code to exploit that (is that true?).
The problem arises when those two types need to interact with each other at the level of their wrapped types - in this particular case, multiplying a Matrix by a Vector yields a new Vector. However to implement this operation, the code performing it needs access to the internal implementation of both Vector and Matrix, in order to do something like this:
-- Matrix.hs
-- ... include earlier definitions
{-# LANGUAGE FlexibleInstances #-}
import qualified Linear.Matrix ((!*))
class MatrixMultiplication a b c | a b -> c where
infixl 7 |*| -- set same precedence and associativity as *
(|*|) :: a -> b -> c
instance MatrixMultiplication Matrix Vector Vector where
(|*|) (Matrix a) q0 =
let (V4 x y z w) = a Linear.Matrix.!* _impl q0
in vector (x, y, z, w)
To do this I need a function, _impl, that allows the Matrix module to get at the internals of Vector:
-- Vector.hs
-- ... include earlier definitions
_impl :: Vector -> V4 Double
_impl (Vector q) = q
-- also added to export list
So after carefully hiding the internals of Vector (by not exporting the data constructor function of Vector), it seems I must expose them with a different exported function, and the only defence I have is some documentation telling client code they shouldn't be using it. This seems unfortunate - I've effectively just renamed the data constructor.
In a language like C++, the friend keyword could be used to provide a function access to the private data member of one of the types. Is there a similar concept in Haskell?
I suppose I could implement both Vector and Matrix in the same module. They'd then have access to each others' data constructors. However this isn't really a satisfying solution as it just works around the issue.
Is there a better approach?