Reputation: 1063
I'm trying to write a fairly straightforward processor emulator and am trying to separate the actual processor from its memory interface. For example, an old TRS-80 has 12K of ROM and can be configured with a total of 16, 32 or 48K of total RAM. My initial idea for the memory system was a type class:
class MemorySystem memInternals addrType wordType where
-- | General memory fetch. This always returns a 'wordType' value,
-- even if the system's memory internals are byte addressable.
mfetch :: addrType -- ^ The address from which the instruction will be fetched
-> memInternals -- ^ The memory system
-> wordType -- ^ The fetched word
-- | Fetch a block of words from the memory system
mfetchN :: ( Unbox wordType
) =>
addrType -- ^ The address from which the instruction will be fetched
-> Int -- ^ Number of words to fetch
-> memInternals -- ^ The memory system
-> Vector wordType -- ^ The words fetched
Clarification: the point of MemorySystem
is to allow an instruction decoder, e.g., a Z80 instruction decoder in the case of a TRS-80, to operate independently of its memory interface, such that all the decoder need do is call mfetch
for the next instruction and its operands. Hence, memInternals
is a type that implements an interface to a memory system specified partially by the type class functions.
How is the best way of constraining addrType
and wordType
while allowing memInterals
to be polymorphic, i.e., allowing memInternals
to represent different memory configurations. I'm finding myself adding contexts to functions that look like:
foo :: ( MemorySystem memSys Word16 Word8) =>
-> memSys
-> Word16
-> Word16
bar :: ( MemorySystem memSys Word16 Word8) =>
-> memSys
-> Word16
-> Word8
bar mem pc = ...
-- bar calls foo -> rigid type variable error...
which leads to a lot of rigid type variable errors in ghc.
Is there a better way of expressing a MemorySystem
that leads to the "right type" of polymorphism in memInternals
such that a family of memory interfaces can be properly represented (and acted upon via a type class or family)?
Upvotes: 1
Views: 181
Reputation: 1063
The record approach does work best, at least for proper separation of concerns. Here's what works for me:
data (Unbox wordType) => MemorySystem addrType wordType memSys =
MemorySystem
{ _mfetch :: addrType -> wordType
, _ mfetchN :: addrType -> Int -> Vector wordType
, _memInternals = memSys
}
(Note that memInternals
can be accessed from within mfetch
and mfetchN
, so no need to duplicate it as a parameter to those two functions.) It's then possible to create lenses for this record, e.g., using Control.Lens
and makeLenses
. It also permits defining types like:
import Data.Word
import Data.Int
type Z80addr = Word16
type Z80word = Word8
type Z80memory memConfig = MemorySystem Z80addr Z80word memConfig
that permit a polymorphic memory configuration. Many thanks to Chris for the suggestion -- I'm not sure why I got wrapped around the proverbial axle thinking in terms of type families.
Upvotes: 0
Reputation: 8153
To expand on my comment, you can try using associated type families. But it looks like your current use case is well served by the data type instead of the type class:
{-# LANGUAGE TypeFamilies, RankNTypes #-}
import Data.Vector.Unboxed
class MemorySystem memInternals where
type AddrType memInternals
type WordType memInternals
mfetch :: memInternals
-> AddrType memInternals
-> AddrType wordType
mfetchN :: Unbox wordType
=> memInternals
-> AddrType memInternals
-> Int
-> Vector wordType
-- The record type may be more flexible and appropriate here
data MemorySystem addrType wordType = MemorySystem {
mfetch :: addrType -> wordType
, mfetchN :: Unbox wordType => addrType -> Int -> Vector wordType
}
See how the type class has only functions that take the type parameter memInternals
only once and as the first argument? This seems equivalent to the record approach.
Upvotes: 1