2012-11-17 11 views
15

Gần đây tôi đã phát hiện gói ống kính trên Hackage và đã cố gắng tận dụng nó trong một dự án thử nghiệm nhỏ có thể biến thành MUD/MUSH máy chủ một ngày rất xa nếu tôi tiếp tục làm việc trên nó.Làm cách nào để xử lý kết quả Có thể có tại trong Control.Lens.Indexed mà không có một ví dụ Monoid

Đây là một phiên bản thu nhỏ của mã của tôi minh họa cho các vấn đề tôi phải đối mặt ngay bây giờ với ít ống kính sử dụng để truy cập vào container Key/Value (Data.Map.Strict trong trường hợp của tôi)

{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, TemplateHaskell #-} 
module World where 
import Control.Applicative ((<$>),(<*>), pure) 
import Control.Lens 
import Data.Map.Strict (Map) 
import qualified Data.Map.Strict as DM 
import Data.Maybe 
import Data.UUID 
import Data.Text (Text) 
import qualified Data.Text as T 
import System.Random (Random, randomIO) 

newtype RoomId = RoomId UUID deriving (Eq, Ord, Show, Read, Random) 
newtype PlayerId = PlayerId UUID deriving (Eq, Ord, Show, Read, Random) 

data Room = 
    Room { _roomId :: RoomId 
     , _roomName :: Text 
     , _roomDescription :: Text 
     , _roomPlayers :: [PlayerId] 
     } deriving (Eq, Ord, Show, Read) 

makeLenses ''Room 

data Player = 
    Player { _playerId :: PlayerId 
     , _playerDisplayName :: Text 
     , _playerLocation :: RoomId 
     } deriving (Eq, Ord, Show, Read) 

makeLenses ''Player 

data World = 
    World { _worldRooms :: Map RoomId Room 
     , _worldPlayers :: Map PlayerId Player 
     } deriving (Eq, Ord, Show, Read) 

makeLenses ''World 

mkWorld :: IO World 
mkWorld = do 
    r1 <- Room <$> randomIO <*> (pure "The Singularity") <*> (pure "You are standing in the only place in the whole world") <*> (pure []) 
    p1 <- Player <$> randomIO <*> (pure "testplayer1") <*> (pure $ r1^.roomId) 
    let rooms = at (r1^.roomId) ?~ (set roomPlayers [p1^.playerId] r1) $ DM.empty 
     players = at (p1^.playerId) ?~ p1 $ DM.empty in do 
    return $ World rooms players 

viewPlayerLocation :: World -> PlayerId -> RoomId 
viewPlayerLocation world playerId= 
    view (worldPlayers.at playerId.traverse.playerLocation) world 

Từ phòng , người chơi và các đối tượng tương tự được tham chiếu trên tất cả các mã tôi lưu trữ chúng trong loại trạng thái thế giới của tôi như là bản đồ của các Id (newtyped UUID) cho các đối tượng dữ liệu của chúng.

Để tìm những người có ống kính, tôi cần xử lý Có thể được trả về bởi ống kính tại (trong trường hợp khóa không có trong bản đồ thì không có gì) bằng cách nào đó. Trong dòng cuối cùng của tôi, tôi đã cố gắng để làm điều này thông qua đi qua mà không typecheck miễn là kết quả cuối cùng là một ví dụ của Monoid nhưng điều này không phải là trường hợp chung. Ngay tại đây không phải vì playerLocation trả về một RoomId mà không có Monoid instance.

No instance for (Data.Monoid.Monoid RoomId) 
    arising from a use of `traverse' 
Possible fix: 
    add an instance declaration for (Data.Monoid.Monoid RoomId) 
In the first argument of `(.)', namely `traverse' 
In the second argument of `(.)', namely `traverse . playerLocation' 
In the second argument of `(.)', namely 
    `at playerId . traverse . playerLocation' 

Kể từ khi monoid là yêu cầu của traverse chỉ vì traverse khái quát để đựng các kích cỡ lớn hơn một bây giờ tôi đã tự hỏi nếu có một cách tốt hơn để xử lý này mà không đòi hỏi các trường hợp monoid ngữ nghĩa vô nghĩa trên tất cả các loại có thể chứa trong một đối tượng của tôi mà tôi muốn lưu trữ trên bản đồ.

Hoặc có thể tôi đã hiểu sai vấn đề ở đây hoàn toàn và tôi cần sử dụng một bit hoàn toàn khác so với gói thấu kính khá lớn?

+0

Điều gì về việc sử dụng các đơn nguyên 'First' hoặc' Last' từ 'Data.Monoid'? –

Trả lời

21

Nếu bạn có một Traversal và bạn muốn có được một Maybe cho phần tử đầu tiên, bạn chỉ có thể sử dụng headOf thay vì view, ví dụ:

viewPlayerLocation :: World -> PlayerId -> Maybe RoomId 
viewPlayerLocation world playerId = 
    headOf (worldPlayers.at playerId.traverse.playerLocation) world 

Phiên bản infix của headOf được gọi ^?. Bạn cũng có thể sử dụng toListOf để nhận danh sách tất cả các phần tử và các chức năng khác tùy thuộc vào việc bạn muốn làm. Xem tài liệu Control.Lens.Fold.

Một heuristic, nhanh chóng mà module để tìm kiếm các chức năng của bạn trong:

  • Một Getter là một cái nhìn chỉ đọc của đúng một giá trị
  • Một Lens được một cái nhìn đọc-ghi của đúng một giá trị
  • một Traversal là một cái nhìn đọc-ghi của zero-hoặc-nhiều giá trị
  • một Fold được một cái nhìn chỉ đọc của zero-hoặc-nhiều giá trị
  • A Setter là chế độ xem chỉ (chỉ, sửa đổi) của các giá trị bằng không hoặc nhiều hơn (có thể là vô số giá trị, trên thực tế)
  • An Iso là, một đồng vị - Lens có thể đi trong hai hướng
  • có lẽ bạn biết khi nào bạn đang sử dụng một chức năng Indexed, vì vậy bạn có thể tìm trong các Indexed mô-đun tương ứng

Hãy suy nghĩ về những gì bạn đang cố gắng làm và những gì các module chung nhất để đưa nó sẽ là. :-) Trong trường hợp này, bạn có Traversal, nhưng bạn chỉ đang cố xem, không sửa đổi, do đó chức năng bạn muốn nằm trong .Fold. Nếu bạn cũng có bảo đảm rằng nó đang đề cập đến chính xác một giá trị, nó sẽ là .Getter.

+0

Cảm ơn, headOf dường như cung cấp chính xác những gì tôi đang tìm kiếm. Và có, nó là loại khó khăn để tìm ra mô-đun có thể giữ chức năng tôi cần. Heuristic của bạn sẽ có ích cho điều đó. –

1

Câu trả lời ngắn gọn: gói thấu kính không phải là ma thuật.

Nếu không nói với tôi những gì các lỗi hoặc mặc định là, bạn muốn thực hiện:

viewPlayerLocation :: Thế giới -> playerid -> RoomId

Bạn biết hai điều, đó

Để tìm những người có ống kính tôi cần xử lý Có thể được trả lại bằng ống kính tại

traverse mà typecheck miễn là kết quả cuối cùng là một thể hiện của monoid

Với Monoid bạn nhận được mempty :: Monoid m => m như là mặc định khi tra cứu thất bại.

Điều gì có thể không thành công: PlayerId không thể ở trong số _worldPlayers_playerLocation không thể ở trong số _worldRooms.

Vậy mã của bạn nên làm gì nếu tra cứu không thành công? Đây có phải là "không thể"? Nếu vậy, hãy sử dụng fromMaybe (error "impossible") :: Maybe a -> a để gặp sự cố.

Nếu có thể tìm kiếm thất bại thì có mặc định sane không? Có lẽ trả lại Maybe RoomId và để người gọi quyết định?

+0

Có, tôi đã đề cập đến điều đó, tôi muốn nó trả về một RoomID có thể nếu có thể trong trường hợp đó, tuyên truyền giá trị Có thể ra ngoài. Đối với một setter tôi sẽ được okay với chỉ là không làm gì trong trường hợp mục nhập bản đồ với các phù hợp với Id không tồn tại nhưng một số loại báo cáo lỗi sẽ được ưa thích. –

1

^?! để giải phóng bạn khỏi gọi fromMaybe.

Các vấn đề liên quan