另一个愚蠢的问题=)我有一个Handle
字段的自定义数据类型:
import System.IO
data CustomType = CustomType {
file::Handle
}
如何设置file
字段?我试图使用这个明显的代码:
let someFile = openFile fileName AppendMode
let object = CustomType {
file=someFile
}
但openFile
的类型为openFile :: FilePath -> IOMode -> IO Handle
,因此我收到了错误
Couldn't match expected type `Handle' with actual type `IO Handle'
那么如何在此字段中存储Handle
个对象?
UPD
我也在尝试这个
data CustomType = CustomType {
file::IO Handle
}
但是当我使用hPutStrLn
函数
let object = CustomType {
file=someFile
}
hPutStrLn (file object)
错误信息是:
Couldn't match expected type `Handle' with actual type `IO Handle'
In the return type of a call of `file'
In the first argument of `TO.hPutStrLn', namely `(file object)'
In a stmt of a 'do' block:
TO.hPutStrLn (file object) text
答案 0 :(得分:2)
所以你创建了这样的类型:
data CustomType = CustomType {
file::Handle
}
现在在ghci中试试这个:
ghci> let a = openFile "someFile.txt" AppendMode
ghci> :t a
a :: IO Handle
因此Handle
包含IO
类型。您可以使用Monad绑定运算符
从中提取Handle
。
ghci> let b = a >>= \handle -> return $ CustomType handle
return
函数会将CustomType
再次包裹在IO
monad中。您可以在ghci中再次验证这一点:
ghci> :t b
b :: IO CustomType
bind
或>>=
的类型如下:
ghic> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
尝试将m
替换为IO
,然后获得:
(>>=) :: IO a -> (a -> IO b) -> IO b
这就是为什么你必须在return
运算符中使用bind
函数,以便进行类型检查。
答案 1 :(得分:2)
我不完全确定你想要什么。如果您不理解涉及IO
类型不匹配的类型错误,您应该首先阅读Haskell中IO的介绍。无论如何,这是有效的代码:
import System.IO
data CustomType = CustomType {
file :: Handle
}
fileName :: FilePath
fileName = "foo"
process :: IO ()
process = do
someFile <- openFile fileName AppendMode
let object = CustomType { file = someFile }
hPutStrLn (file object) "abc"
hClose (file object)
如果你想在GHCi中输入命令,你可以单独输入do
- process
- 阻止的每一行,如下所示:
GHCi> someFile <- openFile fileName AppendMode
GHCi> let object = CustomType { file = someFile }
GHCi> hPutStrLn (file object) "abc"
GHCi> hClose (file object)
答案 2 :(得分:1)
所以你的困惑似乎是如何在IO monad中使用值。关于IO monad的事情是它的传染性,所以每当你想用IO值做某事时你都会得到一个IO结果。这可能听起来像是屁股上的痛苦,但实际上它实际上相当不错,因为它使程序的各个部分保持纯净,并让您完全控制何时执行操作。而不是试图摆脱IO monad,你必须学会采用haskell方式将函数应用于monadic值。每个monad都是一个仿函数,因此您可以使用fmap将纯函数应用于monadic值。 monad给出的额外功能是它允许你一起加入上下文。因此,如果你有一个IO a
和一个IO b
,你可以将IO加入到IO (a, b)
。我们可以利用这些知识来解决您的问题:
openFile有签名:
openFile :: FilePath -> IOMode -> IO Handle
如上所述,没有办法*从Handle中删除IO,所以你唯一能做的就是把你的类型放在IO monad中。例如,您可以使用fmap将此对象应用于IO Handle
:
createMyObject :: FilePath -> IO CustomType
createMyObject fp = CustomType `fmap` openFile fp AppendMode
现在你有了你的对象,但它在IO monad中,那么你如何使用它?应用程序的最外层始终位于IO monad中。所以你的主要功能应该有IO ()
这样的签名。在main函数中,您可以使用其他IO值,例如使用do表示它们是纯粹的。 (<-)
关键字有点像我们上面谈到的加入。它将来自另一个IO的值绘制到当前IO中:
main :: IO ()
main = do
myObjectPure <- createMyObject "someFilePath.txt"
let myHandle :: Handle -- No IO!
myHandle = file myObjectPure
-- Now you can use the functions that takes a pure handle:
hPutStrLn myHandler "Yay"
顺便说一句,你可能不应该直接使用Handle,因为忘记关闭它会很容易。最好使用类似withFile
的东西,它会在完成后关闭句柄。
* 实际上有一种方法,但你不需要知道它,因为你不太可能解决实际需要它的问题,并且它很容易被新人滥用。< / em>的