首先抱歉做了“我从哪里开始”的典型事情,但我完全迷失了。
我一直在阅读'了解你是一个伟大的好人'网站,感受现在感觉像是一个时代(差不多半个学期。我即将完成'输入和输出'一章,我仍然不知道如何编写多行程序。
我已经看过do语句了,你只能使用它来将IO动作连接到一个函数中,但我看不出我将如何编写一个真实的应用程序。
有人能指出我正确的方向。
我来自C背景,基本上我在本学期的uni中使用haskell作为我的一个模块,我想比较C ++和haskell(在很多方面)。我正在寻找创建一系列搜索和排序程序,以便我可以评论它们在各自语言中的容易程度与它们的速度。
然而,我真的开始放弃使用Haskell已经六周了,我仍然不知道如何编写一个完整的应用程序,我正在阅读的网站中的章节似乎得到了越来越长。
我基本上需要创建一个将存储在结构中的基本对象(我知道该怎么做),更多我正在努力的是,如何创建一个从某些文本文件中读取数据的程序,并首先使用该数据填充结构,然后继续处理它。由于haskell似乎拆分IO和其他操作,它不会让我在程序中写多行,我正在寻找这样的东西:
main = data <- getContent
let allLines = lines data
let myStructure = generateStruct allLines
sort/search/etc
print myStructure
我该如何解决这个问题?任何有助于我使用现实程序的好教程?
-A
答案 0 :(得分:10)
您提到了do
符号,现在是学习如何使用do
的时候了。考虑一下您的示例main
是IO
,您应该使用do语法或绑定:
main = do
dat <- getContent
let allLines = lines dat
myStructure = generateStruct allLines
sorted = mySort myStructure
searchResult = mySearch myStructure
print myStructure
print sorted
print searchResult
所以现在你有一个获得stdin
的main,通过[String]
将其转换为lines
,可能会将其解析为一个结构并对该结构进行排序和搜索。请注意,有趣的代码都是纯粹的 - mySort
,mySearch
,而generateStruct
不需要是IO(也不能是,在let绑定中)所以你实际上是正确使用纯粹而有效的代码。
我建议你看看bind是如何工作的(>>=
)以及如何将符号desugars绑定到bind中。 This SO question应该有所帮助。
答案 1 :(得分:4)
另见Neil Mitchell的Explaining Haskell IO without Monads。
答案 2 :(得分:4)
我将尝试从一个简化的例子开始。让我们说这就是我们想要做的事情:
我们还要说我们可以使用这些功能:
getContent :: IO [Int]
sort :: [Int] -> [Int]
reverse :: [Int] -> [Int]
show :: a -> String
putStrLn :: String -> IO ()
我们很清楚,我会对这些功能有所了解:
getContent
:我编写了这个函数,但是如果有这样的函数 将是它的签名(你可以使用getContent = return [3,7,2,1]
进行测试)。我确定你之前见过这样的签名,至少含糊地理解,因为它确实是IO,它的签名不能只是getContent :: [Int]
。sort
:这是Data.List模块中定义的函数,用法很简单:sort [3,1,2]
返回[1,2,3]
reverse
:也在Data.List模块中定义:reverse [1,3,2]
返回[2,3,1]
show
:不需要导入任何内容,只需使用它:show 11
返回字符串"11"
; show [1,2,3]
返回字符串"[1,2,3]"
等putStrLn :: Stiring -> ()
。好的,现在我们已经拥有创建程序所需的一切,现在的问题是将这些功能连接在一起。让我们从连接函数开始:
带有getContent :: IO [Int]
的 sort :: [Int] -> [Int]
我想如果你得到这个部分,你也可以很容易地得到其余部分。因此,问题是,由于getContent
返回IO [Int]
而不仅仅是[Int]
,因此您不能忽略或删除IO
部分并将其推入{{} 1}}。也就是说,这是无法连接这些功能所做的事情:
sort
这是
sort (getRidOfIO getContent)
操作拯救的地方。现在请注意,>>= :: m a -> (a -> m b) -> m b
,m
和a
是类型变量,因此如果我们将b
替换为m
,{{1} } IO
和a
[Int]
,我们得到了信号:
b
再次查看这些[Int]
和>>= :: IO [Int] -> ([Int] -> IO [Int]) -> IO [Int]
函数及其签名,并尝试考虑它们如何适合getContent
。我相信你会注意到你可以直接使用sort
作为>>=
的第一个参数。到目前为止,getContent
将执行>>=
>>=
并将其推送到作为第二个参数提供的函数中。但是第二个论点中的功能是什么?我们不能直接使用[Int]
,我们可以尝试的下一个最好的事情是
getContent
但是仍然有签名sort :: [Int] -> [Int]
,所以没有多大帮助。这是另一位英雄来到剧中的地方,
\listOfInts -> sort listOfInts
。
同样,[Int] -> [Int]
和return :: a -> m a
是类型变量,让我们替换它们,我们会得到
a
所以我们将m
和return :: [Int] -> IO [Int]
加在一起,我们会得到:
\listOfInts -> sort listOfInts
这正是我们想要作为return
的第二个参数。因此,最后使用我们的粘合剂连接\listOfInts -> return $ sort listOfInts :: [Int] -> IO [Int]
和>>=
:
getContent
与(使用sort
表示法)相同:
getContent >>= (\listOfInts -> return $ sort listOfInts)
那里,这是最可怕部分的结束。现在可能是其中一个 aha 时刻,试着考虑我们刚刚组成的连接的结果类型是什么。我会为你破坏它,......
的类型 do
再次do listOfInts <- getContent
return $ sort listOfInts
。
让我们总结一下:我们采用getContent >>= (\listOfInts -> return $ sort listOfInts)
类型的东西和IO [Int]
类型的东西,将这两个东西粘在一起,再次得到IO [Int]
类型的东西!
现在继续尝试完全相同的事情:获取我们刚刚创建的[Int] -> [Int]
对象,并将其IO [Int]
和IO [Int]
与>>=
粘合在一起。
我认为我写得太多了,但如果有什么不清楚或者你需要其他人的帮助,请告诉我。
到目前为止,我所描述的内容可能是这样的:
return
答案 3 :(得分:1)
如果这是一个从stdin
阅读并将结果写入stdout
的问题,而没有进一步的用户输入 - 正如您提到getContents
所暗示的那样 - 那么古代interact :: (String -> String) -> IO ()
或其他几个版本,例如只需要Data.ByteString.interact :: (ByteString -> ByteString) -> IO ()
或Data.Text.interact :: (Text -> Text) -> IO()
。 interact
基本上是'使用此函数创建一个小的unix工具'功能 - 它将正确类型的纯函数映射到可执行操作(即{{1}类型的值所有Haskell教程都应该在第三或第四页上提及它,并附有编译说明。
所以,如果你写
IO()
并使用main = interact arthur
arthur :: String -> String
arthur = reverse
进行编译,然后管道到ghc --make -O2 Reverse.hs -o reverse
的任何内容都将被理解为字符列表并反转出现。同样,无论你管道什么
./reverse
将在省略空行的情况下出现。更有趣的是,
main = interact (unlines . meredith . lines)
meredith :: [String] -> [String]
meredith = filter (not.null)
将使用换行符分隔的字符流,将其读取为main = interact ( unlines . map show . luther . map read . lines)
luther :: [Int] -> [Int]
luther = filter even
s,删除奇数字符,然后生成适当过滤的流。
Int
将打印换行符分隔数字的平方和。
在最后两种情况下,main = interact ( unlines . map show . emma . map read . lines)
emma :: [Int] -> Int
emma = sum . map square
where square x = x * x
和luther
内部'数据结构'是[Int],这是非常沉闷的,当然,应用它的函数很简单。重点是让emma
中的一种形式处理所有IO,从而获得像“填充结构”和“处理它”的图像。要使用interact
,您需要使用合成来使整个产生某种interact
函数。但即使在这里,就像在第一个例子String -> String
中那样,你在更像数学意义的东西中定义了一个真正的函数。类型arthur:: String -> String
和String
中的值与ByteString
或Bool
中的值一样纯。
在这个基本Int
类型的更复杂的情况下,首先,您的任务是思考您将要关注的函数的所需纯值如何映射到interact
值(此处,对于Int,它只是String
,对于show
,它只是unlines . map show
。 [Int]
知道用字符串“做”的内容。 - 然后找出如何定义一个纯映射从Strings或ByteString(它将包含你的'原始'数据)到你的主要函数作为参数的类型或类型的值。在这里,我只使用interact
生成map read . lines
。如果您正在处理一些更复杂的树状结构,那么您需要一个从[Int]
到[Int]
的函数。当然,更精细的功能就是解析器。
然后你可以去城里,在这种情况下:根本没有理由把自己想象为“编程”,“填充”和“处理”。这就是MyTree Int
所有酷炫设备的用武之地。您的职责是定义特定定义学科内的映射。在最后两种情况中,这些案例来自LYAH
到[Int]
以及[Int]
到[Int]
,但这里是一个类似的例子,来源于excellent, still incomplete, tutorial super-excellent Vector
package正在处理的初始数字结构是Int
Vector Int
这里再次{-# LANGUAGE BangPatterns #-}
import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.Vector.Unboxed as U
import System.Environment
main = L.interact (L.pack . (++"\n") . show . roman . parse)
where
parse :: L.ByteString -> U.Vector Int
parse bytestr = U.unfoldr step bytestr
step !s = case L.readInt s of
Nothing -> Nothing
Just (!k, !t) -> Just (k, L.tail t)
-- now the IO and stringy nonsense is out of the way
-- so we can calculate properly:
roman :: U.Vector Int -> Int
roman = U.sum
是愚蠢的,从Int的矢量到Int的任何函数,无论多么复杂,都可以取代它。写一个更好的roman
永远不会是“填充”“多行编程”“处理”等问题,当然我们这样说;这只是通过Data.Vector和其他地方的函数组合来定义真正函数的问题。天空是极限,请查看教程。