Ответ 1
Поскольку ссылок нет, только значения в Haskell, я ожидаю, что это бесконечная рекурсия.
Тем не менее, возможно:
data Location = Location String Item
data Item = Item String Location
locationName (Location s _) = s
getItem (Location _ i) = i
itemName (Item s _) = s
getLocation (Item _ l) = l
getItemNameAtLocation :: Location -> String
getItemNameAtLocation = itemName . getItem
getLocationNameOfItem :: Item -> String
getLocationNameOfItem = locationName . getLocation
mkItemLocation :: ItemName -> LocationName -> (Item, Location)
mkItemLocation i l = let it = Item i $ Location l $ it in (it, getLocation it)
main = do
let it = Item "Toothbrush" $ Location "Bathroom" $ it
loc1 = getLocation it
loc2 = Location "Quantum bathroom" $ it
print $ getLocationNameOfItem it
print $ getItemNameAtLocation loc1
print $ getItemNameAtLocation loc2
print $ locationName loc2
Однако это не приводит к соблюдению ваших правил, так как теперь есть два места, которые утверждают, что владеют зубной щеткой. Если вы не экспортируете конструкторы, вы все равно можете применить это:
module ItemLocation (mkItemLocation, Item, Location,
getLocation, locationName,
getItem, itemName) where
-- see above for Item, Location and others
type ItemName = String
type LocationName = String
mkItemLocation :: ItemName -> LocationName -> (Item, Location)
mkItemLocation i l = let it = Item i $ Location l $ it in (it, getLocation it)
main = do
let (it, loc) = mkItemLocation "Toothbrush" "Bathroom"
print $ getLocationNameOfItem it
print $ getItemNameAtLocation loc
Тем не менее, ничто не мешает вам использовать mkItemLocation "Toothbrush" "Another quantum room"
. Но на данный момент вы не сказали, как бы вы идентифицировали отдельные элементы или местоположения (возможно, по имени).
Обратите внимание, что вы, вероятно, захотите использовать data Location = Location String (Maybe Item)
. При этом не совсем ясно, как вы хотите манипулировать местоположением или элементом, и как эти манипуляции должны отражать остальную часть ваших местоположений. В зависимости от того, что вы действительно хотите сделать, вы можете использовать State
вместе с двумя Map
.
Хорошо, это выше показывает, как вы могли бы работать с рекурсивными типами данных. Как можно приблизиться к вашей проблеме? Попробуем создать интерфейс:
data Magic
-- | initial empty magic
empty :: Magic
-- | turns the magic type into a list of (Location, Item)
-- every Location and Item is unique
assoc :: Magic -> [(Location, Item)]
-- | adds the given Location and Item and puts them into relation
-- If either Location or Item already exist, they're going to be
-- removed (together with their counterpart) beforehand
insert :: Location -> Item -> Magic -> Magic
Теперь это можно обобщить. Вместо Location
и Item
мы можем поддерживать a
и b
. Мы получаем:
module DualMap (DualMap, empty, assocLeft,
assocRight, flipMap, insert,
removeLeft, removeRight) where
import Data.Map (Map)
import qualified Data.Map as M
data DualMap a b = DualMap (Map a b) (Map b a) deriving (Eq, Show)
empty :: DualMap a b
empty = DualMap (M.empty) (M.empty)
flipMap :: DualMap a b -> DualMap b a
flipMap (DualMap ls rs) = DualMap rs ls
assocLeft :: DualMap a b -> [(a, b)]
assocLeft (DualMap ls _) = M.toList ls
assocRight :: DualMap a b -> [(b, a)]
assocRight = assocLeft . flipMap
insert :: (Ord a, Ord b) => a -> b -> DualMap a b -> DualMap a b
insert loc item m = DualMap (M.insert loc item ls) (M.insert item loc is)
where (DualMap ls is) = removeLeft loc m
removeLeft :: (Ord a, Ord b) => a -> DualMap a b -> DualMap a b
removeLeft l [email protected](DualMap ls rs) =
case M.lookup l ls of
Just r -> DualMap (M.delete l ls) (M.delete r rs)
Nothing -> m
removeRight :: (Ord a, Ord b) => b -> DualMap a b -> DualMap a b
removeRight r [email protected](DualMap ls rs) =
case M.lookup r rs of
Just l -> DualMap (M.delete l ls) (M.delete r rs)
Nothing -> m
Обратите внимание, что вы не должны экспортировать конструктор DataMap
. removeRight
и removeLeft
будут гарантировать, что если вы выберете левое значение, правое значение также будет удалено. Заметим, что в нашем случае использование одного из них достаточно, так как insert
сохраняет оба значения симметрично.
Для этого требуется наличие действительных экземпляров Ord
для Location
и Item
, которые должны быть основаны на их уникальном атрибуте (в данном случае их имени). Если у вас уже есть экземпляр Ord
или Eq
, который не использует только имя, используйте обертку newtype
с соответствующим экземпляром.