解开一个Monad

6
给定以下程序,我在处理单子时遇到了问题。
module Main 
where
import System.Environment
import System.Directory
import System.IO
import Text.CSV

--------------------------------------------------

exister :: String -> IO Bool
exister path = do
  fileexist <- doesFileExist path 
  direxist  <- doesDirectoryExist path
  return (fileexist || direxist )

--------------------------------------------------
slurp :: String -> IO String 
slurp path = do
  withFile path ReadMode (\handle -> do
                             contents <- hGetContents handle
                             last contents `seq` return contents )
--------------------------------------------------    
main :: IO ()
main = do
  [csv_filename] <- getArgs
  putStrLn (show csv_filename)
  csv_raw <- slurp csv_filename
  let csv_data = parseCSV csv_filename csv_raw

  printCSV csv_data -- unable to compile. 

csv_data是一种“Either(parseerror) CSV”类型,而printCSV只接受CSV数据。
这是工作版本和损坏版本之间的差异。
***************
*** 27,30 ****
    csv_raw <- slurp csv_filename
    let csv_data = parseCSV csv_filename csv_raw

!   printCSV csv_data -- unable to compile. 
\ No newline at end of file
--- 27,35 ----
    csv_raw <- slurp csv_filename
    let csv_data = parseCSV csv_filename csv_raw

!   case csv_data of 
!     Left error -> putStrLn $ show error
!     Right csv_data -> putStrLn $ printCSV csv_data
!     
!   putStrLn "done"
!       

参考资料:http://hackage.haskell.org/packages/archive/csv/0.1.2/doc/html/Text-CSV.html

这个问题似乎与单子无关。 :) - Rotsor
@Rotsor 我本来只是想回复一条评论,但结果变成了一个答案。这个问题有点关于单子,但又不完全是。 - Dan Burton
1
保罗,问题不在于printCSV :: CSV -> String这个函数名不够好吗?它是一个纯函数,而不是一个IO类型的函数。如果我在最后一行写putStrLn $ printCSV csv_data,那么它就可以编译了。 - applicative
@applicative:是的,我被这个糟糕的名字踢了。 :-/ - Paul Nathan
3个回答

17

关于单子:

是的,Either a 是一个单子。所以简化问题,你基本上在问这个:

main = print $ magicMonadUnwrap v

v :: Either String Int
v = Right 3

magicMonadUnwrap :: (Monad m) => m a -> a
magicMonadUnwrap = undefined
你如何定义 magicMonadUnwrap?嗯,你知道,每个单子都不一样。每一个需要它自己的展开器。许多展开器中都有“run”这个词,例如 runSTrunCont 或者 runEval。然而,对于一些单子来说,可能不能安全地展开它们(因此需要不同的展开器)。
对于列表的一个实现可以是 head。但如果列表为空呢?Maybe 的一个展开器是 fromJust,但如果它是 Nothing 呢?
类似地,Either 单子的展开器可能会是这样的:
fromRight :: Either a b -> b
fromRight (Right x) = x

但是这个解包器并不安全:假设你有一个Left值怎么办?(在您的情况下,Left通常表示错误状态,例如解析错误)。因此,处理Either值的最佳方法是使用either函数,或者使用匹配RightLeft的case语句,就像Daniel Wagner所演示的那样。

tl;dr:不存在magicMonadUnwrap。如果您在相同的monad内部,则可以使用<-,但要真正从monad中提取值...好吧...这取决于您正在处理哪种monad。


有一个 TH,有人发布了一个通用的 Monad m => m a -> a 实现。链接:http://blog.sigfpe.com/2009/01/rewriting-monadic-expressions-with.html - alternative
1
@monadic:这种技术很聪明,但它有一个问题。它只允许你在模板Haskell拼接中使用extract,然后将其解释为某个单子。它基本上只是给你一个花哨的(>>=),这是可以预料的,因为extract只是被join替换了。 - John L
@丹:有趣。我明白你的观点,当我看到正在发生的事情时,它在我的脑海中更加连贯。 - Paul Nathan

7
使用case
main = do
    ...
    case csv_data of
        Left  err -> {- whatever you're going to do with an error -- print it, throw it as an exception, etc. -}
        Right csv -> printCSV csv
either 函数语法更短,但本质相同。
main = do
    ...
    either ({- error condition function -}) printCSV csv_data

3

你必须忘记你所学到的。

尤达大师。

不要考虑或搜索从以效果为中心(通常是单子)的上下文中“释放”、“解放”、“释放”、“解开”或“提取”普通 Haskell 值的方法,而是学习如何使用 Haskell 的更具特色的功能之一 - 函数是 头等值

  • you can use functions like values of other types e.g. like Bool, Char, Int, Integer etc:

    arithOps :: [(String, Int -> Int -> Int)]
    arithOps =  zip ["PLUS","MINUS", "MULT", "QUOT", "REM"]
                    [(+), (-), (*), quot, rem] 
    

对于你的目的,更重要的是函数也可以被用作参数,例如:

map          :: (a -> b) -> [a] -> [b]
map f xs     =  [ f x | x <- xs ]

filter       :: (a -> Bool) -> [a] -> [a]
filter p xs  =  [ x | x <- xs, p x ]

这些高阶函数甚至可以在具有效果的上下文中使用,例如:

import Control.Monad

liftM  :: Monad m => (a -> b)           -> (m a -> m b)
liftM2 :: Monad m => (a -> b -> c)      -> (m a -> m b -> m c)
liftM3 :: Monad m => (a -> b -> c -> d) -> (m a -> m b -> m c -> m d)

...等等,这些函数可以帮助您提升您的常规Haskell函数:

do     .
       .
       .
   val <- liftM3 calculate this_M that_M other_M
       .
       .
       .

当然,直接方法也可行:
do     .
       .
       .
   x <- this_M
   y <- that_M
   z <- other_M
   let val =  calculate x y z
       .
       .
       .

随着你的技能不断发展,你会发现自己将越来越多的代码委托给普通函数,并将效果留给一组以函子、应用、单子、箭头等方式定义的实体,这样你就可以逐步掌握 Haskell。
你还不信服?那么,这里是一个简短的说明,介绍了 Haskell 中如何处理效果 - 这里也有一个更长的描述,介绍了 Haskell 如何达到单子界面。或者,你可以看看Standard MLOCaml和其他类似的语言 - 谁知道呢,也许你会更喜欢使用它们...

一切都会因为 do 变得更好。 :) - Will Ness
包括 shC 吗?;-) - atravers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接