2013-08-13 32 views
7

Tôi đang cố phân tích tệp bằng hàm parseFile được tìm thấy trong gói haskell-src-exts. Tôi đang cố gắng để làm việc với đầu ra của parseFile đó là tất nhiên IO, nhưng tôi không thể tìm ra cách để có được xung quanh IO. Tôi tìm thấy một chức năng liftIO nhưng tôi không chắc chắn nếu đó là giải pháp trong tình huống này. Đây là mã bên dưới.Haskell: Bị mắc kẹt trong IO monad

import Language.Haskell.Exts.Syntax 
import Language.Haskell.Exts 
import Data.Map hiding (foldr, map) 
import Control.Monad.Trans 

increment :: Ord a => a -> Map a Int -> Map a Int 
increment a = insertWith (+) a 1 

fromName :: Name -> String 
fromName (Ident s) = s 
fromName (Symbol st) = st 

fromQName :: QName -> String 
fromQName (Qual _ fn) = fromName fn 
fromQName (UnQual n) = fromName n 

fromLiteral :: Literal -> String 
fromLiteral (Int int) = show int 

fromQOp :: QOp -> String 
fromQOp (QVarOp qn) = fromQName qn 

vars :: Exp -> Map String Int 
vars (List (x:xs)) = vars x 
vars (Lambda _ _ e1) = vars e1 
vars (EnumFrom e1) = vars e1 
vars (App e1 e2) = unionWith (+) (vars e1) (vars e2) 
vars (Let _ e1) = vars e1 
vars (NegApp e1) = vars e1 
vars (Var qn) = increment (fromQName qn) empty 
vars (Lit l) = increment (fromLiteral l) empty 
vars (Paren e1) = vars e1 
vars (InfixApp exp1 qop exp2) = increment (fromQOp qop) $ unionWith (+) (vars exp1) (vars exp2) 



match :: [Match] -> Map String Int 
match rhss = foldr (unionWith (+)) empty (map (\(Match a b c d e f) -> rHs e) rhss) 

rHS :: GuardedRhs -> Map String Int 
rHS (GuardedRhs _ _ e1) = vars e1 

rHs':: [GuardedRhs] -> Map String Int 
rHs' gr = foldr (unionWith (+)) empty (map (\(GuardedRhs a b c) -> vars c) gr) 

rHs :: Rhs -> Map String Int 
rHs (GuardedRhss gr) = rHs' gr 
rHs (UnGuardedRhs e1) = vars e1 

decl :: [Decl] -> Map String Int 
decl decls = foldr (unionWith (+)) empty (map fun decls) 
    where fun (FunBind f) = match f 
      fun _ = empty 

pMod' :: (ParseResult Module) -> Map String Int 
pMod' (ParseOk (Module _ _ _ _ _ _ dEcl)) = decl dEcl 

pMod :: FilePath -> Map String Int 
pMod = pMod' . liftIO . parseFile 

Tôi chỉ muốn có thể sử dụng chức năng pMod 'trên đầu ra của parseFile. Lưu ý rằng tất cả các loại và các nhà xây dựng dữ liệu có thể được tìm thấy tại http://hackage.haskell.org/packages/archive/haskell-src-exts/1.13.5/doc/html/Language-Haskell-Exts-Syntax.html nếu điều đó giúp. Cảm ơn trước!

Trả lời

16

Khi bên trong IO, không có lối thoát.

Sử dụng fmap:

-- parseFile :: FilePath -> IO (ParseResult Module) 
-- pMod' :: (ParseResult Module) -> Map String Int 
-- fmap :: Functor f => (a -> b) -> f a -> f b 

-- fmap pMod' (parseFile filePath) :: IO (Map String Int) 

pMod :: FilePath -> IO (Map String Int) 
pMod = fmap pMod' . parseFile 

(Ngoài :) Như đã giải thích trong great answer by Levi Pearson, cũng có

Prelude Control.Monad> :t liftM 
liftM :: (Monad m) => (a1 -> r) -> m a1 -> m r 

Nhưng điều đó không có ma thuật đen hoặc. Xem xét:

Prelude Control.Monad> let g f = (>>= return . f) 
Prelude Control.Monad> :t g 
g :: (Monad m) => (a -> b) -> m a -> m b 

Vì vậy, chức năng của bạn cũng có thể được viết như

pMod fpath = fmap pMod' . parseFile $ fpath 
    = liftM pMod' . parseFile $ fpath 
    = (>>= return . pMod') . parseFile $ fpath -- pushing it... 
    = parseFile fpath >>= return . pMod'   -- that's better 

pMod :: FilePath -> IO (Map String Int) 
pMod fpath = do 
    resMod <- parseFile fpath 
    return $ pMod' resMod 

bất cứ điều gì bạn thấy trực quan hơn (hãy nhớ, (.) có ưu tiên cao nhất, ngay dưới ứng dụng chức năng).

Ngẫu nhiên, >>= return . f bit là cách liftM thực sự được triển khai, chỉ trong do -notation; và nó thực sự cho thấy sự tương đương của fmapliftM, bởi vì đối với bất kỳ đơn nguyên nó nên cho rằng:

fmap f m = m >>= (return . f) 
+0

Cảm ơn bạn rất nhiều, điều này dường như đang hoạt động rất tốt! – user2548080

14

Để cung cấp một câu trả lời tổng quát hơn Will (đó là chắc chắn đúng và to-the-điểm), bạn thường hoạt động 'thang máy' thành một Monad thay vì lấy giá trị ra trong số chúng để áp dụng các hàm thuần túy cho các giá trị đơn nguyên.

Điều đó xảy ra là Monad s (theo lý thuyết) là một loại cụ thể của Functor. Functor mô tả lớp của các loại biểu thị ánh xạ của các đối tượng và hoạt động vào một ngữ cảnh khác. Một kiểu dữ liệu là một thể hiện của Functor ánh xạ các đối tượng vào ngữ cảnh của nó thông qua các nhà xây dựng dữ liệu của nó và nó ánh xạ các hoạt động vào ngữ cảnh của nó thông qua hàm fmap. Để thực hiện một hàm functor thực, fmap phải hoạt động theo cách nâng hàm nhận dạng vào ngữ cảnh functor không thay đổi các giá trị trong ngữ cảnh functor, và nâng hai hàm được tạo ra với nhau tạo ra cùng một hoạt động trong ngữ cảnh functor như nâng các hàm một cách riêng biệt và sau đó soạn chúng trong ngữ cảnh functor.

Nhiều, nhiều kiểu dữ liệu Haskell tự động tạo thành functors và fmap cung cấp giao diện phổ quát để nâng chức năng để chúng áp dụng 'đồng đều' trên dữ liệu được rút ngắn mà không đáng lo ngại về dạng cá thể Functor cụ thể.Một vài ví dụ tuyệt vời về điều này là loại danh sách và loại Maybe; fmap của hàm trong ngữ cảnh danh sách giống hệt như thao tác quen thuộc map trên danh sách và fmap của hàm thành ngữ cảnh Maybe sẽ áp dụng hàm bình thường cho giá trị Just a và không làm gì cho giá trị Nothing, cho phép bạn thực hiện hoạt động trên nó mà không đáng lo ngại về nó.

Có nói tất cả những gì, bởi một điều không minh bạch của lịch sử Prelude Haskell hiện không đòi hỏi Monad trường cũng có một trường hợp Functor, vì vậy Monad cung cấp một gia đình của các chức năng đó cũng nhấc hoạt động vào bối cảnh monadic. Các hoạt động liftM làm điều tương tự mà fmap không cho Monad trường hợp cũng là Functor trường hợp (như họ phải). Nhưng fmapliftM chỉ nâng các hàm đối số đơn. Monad trợ giúp một cách gia đình các hàm liftM2 - liftM5 nâng các hàm đa đối số thành ngữ cảnh đơn thuần theo cùng một cách.

Cuối cùng, bạn hỏi về liftIO, mà mang lại ý tưởng có liên quan của đơn nguyên biến, trong đó nhiều Monad trường hợp được kết hợp trong một loại dữ liệu duy nhất bằng cách áp dụng các ánh xạ đơn nguyên các giá trị đã-monadic, tạo thành một loại ngăn xếp ánh xạ đơn sắc trên một kiểu thuần túy cơ bản. Thư viện mtl cung cấp một triển khai cho ý tưởng chung này và trong mô-đun Control.Monad.Trans, nó định nghĩa hai lớp, MonadTrans tMonad m => MonadIO m. Lớp MonadTrans cung cấp một hàm duy nhất, lift, cho phép truy cập vào các hoạt động trong "lớp" đơn cao hơn tiếp theo trong ngăn xếp, tức là (MonadTrans t, Monad m) => m a -> t m a. Lớp MonadIO cung cấp một hàm duy nhất, liftIO, cung cấp quyền truy cập vào hoạt động đơn lẻ IO từ bất kỳ "lớp" nào trong ngăn xếp, tức là IO a -> m a. Những làm cho làm việc với ngăn xếp biến áp monad thuận tiện hơn nhiều với chi phí phải cung cấp rất nhiều tờ khai thể hiện biến áp khi mới Monad trường hợp được giới thiệu vào một ngăn xếp.

+0

Cảm ơn bạn đã giải thích chi tiết! Tôi cảm thấy như tôi đã hiểu các monads và functors nhiều hơn một chút bây giờ (không phải là tôi đã hiểu họ trước đây). – user2548080

+3

Một phần lý do tôi trả lời câu hỏi ở đây là giúp củng cố các khái niệm này trong tâm trí của tôi để chúng trở nên tự nhiên hơn trong khi lập trình! Sau khi bạn nghĩ về cách các lớp kiểu khác nhau làm việc cùng nhau, mã Haskell thực sự mờ đục trở nên đột nhiên trong suốt. Chúc may mắn! –

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