如何读取haskell字符串

时间:2017-11-04 20:23:13

标签: haskell io

我是Haskell的初学者,虽然熟悉Python,F#,Java,C#和C ++等语言的功能范例(以有限的方式)。

一直在逃避我的是哈克尔的IO。我尝试过几次,甚至在尝试绕过它的过程中学习了C#和F#。

更具体地说,我指的是不使用符号来获取IO,使用do notation,IO变得微不足道。这可能是不好的做法,但在业余时间,我喜欢看看我是否可以用一个连续的表达来完成任务。这是一个很糟糕的做法,很有趣。

这样的表达通常是那种(在伪haskell中):

main = getStdinContentsAsString 
           >>= ParseStringToDataStructureNeeded
           >>= DoSomeComputations 
           >>= ConvertToString 
           >>= putStrLn

我对最后四部分没有任何问题。我学习F#的原因之一就是看看是否有什么东西我没有把我的脑袋放在IO之外,但只要我有方便的F#的Console.ReadLine()它返回一个普通的老字符串它基本上是一帆风顺。

这让我回到了另一个在haskell,再次被IO机制停止了。

我已经管理(在这里使用另一个问题)从控制台读取一个int,并打印" Hello World!"很多次

main = (readLn :: IO Int) >>= \n -> mapM_ putStrLn $ replicate n "Hello World!"

我想至少得到一些"一般用途"方法只是读取stdin的整个内容(可能是多行,所以getContents需要是选择的函数)作为一个字符串,然后我可以使用其他函数处理字符串,如unlines,然后map。

我已经尝试过的一些事情:

正如我所说,getContents将是我所需要的(除非有一些相当于它)。

使用逻辑,因为

getContents :: IO String

然后我需要一些带有IO String的东西并返回一个旧的String。这是(据我所知)

unsafePerformIO :: IO a -> a

然而由于某些原因,ghc不高兴:

* Couldn't match type `[Char]' with `IO (IO b)'
  Expected type: String -> IO b
    Actual type: IO (IO b) -> IO b
* In the second argument of `(>>=)', namely `unsafePerformIO'
  In the expression: getContents >>= unsafePerformIO

我尝试的另一件事:这没有问题;

main = getContents >>= putStrLn

即使getContents返回的类型是IO操作而不是putStrLn想要的本身字符串

getContents :: IO String
putStrLn    :: String -> IO ()

不知何故,该动作被神奇地执行,结果字符串被传递给put函数。

但是当我尝试添加内容时,就像简单地追加"你好"在打印之前输入:

main = getContents >>= (++ " hello") >>= putStrLn

我突然遇到类型不匹配的问题:

Couldn't match type `[]' with `IO'
  Expected type: String -> IO Char
    Actual type: [Char] -> [Char]
* In the second argument of `(>>=)', namely `(++ " hello")'
  In the first argument of `(>>=)', namely
    `getContents >>= (++ " hello")'
  In the expression: getContents >>= (++ " hello") >>= putStrLn

不知何故,IO行动不再执行(或者我可能不理解这一点)。

我还尝试了很多内容,包括getLinereadLngetContentsunsafePerformIOreadfmap的组合果。

这只是一个非常基本的例子,但它完全说明了我现在几次放弃haskell的问题(可能我不是唯一一个),尽管我想要抓住它的顽固性,并学习什么是函数编程语言让我回来。

总结:

  1. 我有没有得到的东西?(99%是)

  2. 如果是,那又是什么?

  3. 我应该如何阅读整个标准输入并在一个连续的表达式中处理它?(如果我只需要一行,我想无论解决方案是什么,它都适用于getLine正弦它基本上是getContents的姐妹)

  4. 提前致谢!

3 个答案:

答案 0 :(得分:11)

您不考虑的主要内容似乎是>>=

的类型
(>>=) :: IO a -> (a -> IO b) -> IO b

换句话说,您无需将IO String“展开”到String; >>=运算符已将普通String交给右侧操作数(函数):

getContents >>= (\s -> ...)
--                ^ s :: String

getContents >>= (++ " hello")失败的原因是>>=要求该函数返回IO ...值,但(++ "hello") :: String -> String

您可以通过将return :: a -> IO a添加到混音中来解决此问题:

getContents >>= (return . (++ "hello"))

整个表达式的类型为IO String。它将在执行时从stdin读取数据,将"hello"附加到其中,然后返回结果字符串。

因此,

getContents >>= (return . (++ "hello")) >>= putStrLn

应该可以正常工作。但它比必要的更复杂。从概念上讲,returnIO中包含一个值,>>=再次展开它(有点)。

我们可以融合右侧的return / >>=位:

getContents >>= (\s -> putStrLn (s ++ "hello"))

即。而不是getContents :: IO String,向其添加"hello"以形成新的IO String操作,然后将putStrLn :: String -> IO ()附加到其中,我们将putStrLn包裹起来以创建新的String -> IO () {1}}函数(在将事物移交给"hello"之前将putStrLn附加到其参数上。)

现在,如果我们愿意,我们可以通过标准的免费技巧摆脱s

getContents >>= (putStrLn . (++ "hello"))

关于IO的一般说明:要记住的是IO ...是普通的Haskell类型。这里没有神奇的事情发生。 >>=不执行任何操作;它只是组合了IO something类型的值和一个函数来构造IO somethingelse类型的新值。

您可以将Haskell视为纯元语言,将构造命令性程序(即执行指令列表)构建为内存中的数据结构。实际执行的唯一事情是绑定到Main.main的值。也就是说,它就像命令式运行时运行您的Haskell代码以在main :: IO ()中生成纯值。然后,该值的内容将作为命令性指令执行:

main :: IO ()
main =
    putChar 'H' >>
    putChar 'i' >>
    putChar '\n'

main绑定到表示命令式程序print 'H'; print 'i'; print newline的数据结构。运行Haskell程序构建此数据结构,然后运行时执行它。

此模型不完整:命令式运行时可以回调Haskell代码。 >>=可用于在命令式代码中“嵌入”Haskell函数,然后可以(在运行时)检查值,决定下一步做什么,等等。但所有这些都以纯Haskell代码的形式发生;只有IO f内的x >>= f返回的f值(<?xml version="1.0" encoding="utf-8" ?> <MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:MSCommunity_Xamarin" x:Class="App1.MainPage"> <MasterDetailPage.Master> <ContentPage Title="Menu" BackgroundColor="#e8e8e8"> <StackLayout Orientation="Vertical"> <StackLayout BackgroundColor="#e74c3c" HeightRequest="75"> <Label Text="Menu dawgii" FontSize="20" VerticalOptions="CenterAndExpand" TextColor="White" HorizontalOptions="Center"/> </StackLayout> <ListView x:Name="navigationDrawerList" RowHeight="60" SeparatorVisibility="None" BackgroundColor="#e8e8e8" ItemSelected="OnMenuItemSelected"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <!-- Main design for our menu items --> <StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal" Padding="20,10,0,10" Spacing="20"> <Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" VerticalOptions="Center" /> <Label Text="{Binding Title}" FontSize="Medium" VerticalOptions="Center" TextColor="Black"/> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage> </MasterDetailPage.Master> <MasterDetailPage.Detail> <NavigationPage> </NavigationPage> </MasterDetailPage.Detail> </MasterDetailPage> 本身没有副作用)。

答案 1 :(得分:4)

  1.   

    我有没有得到的东西?(99%是的)

  2.   

    如果是,那又是什么?

    IO StringString在概念上完全不同 。前者就像一个烹饪食谱,后者就像一顿饭 在你觉得自己是专家Haskeller之前,你最好忘记unsafePerformIO这样的事情。这是普通Haskell代码中你永远不需要的东西,只有FFI绑定才能使用C代码或进行最后的优化。

  3. 我应该如何阅读整个标准输入并以一个连续的表达式处理它?<​​/ p>

    main = getContents >>= putStrLn . (++ " hello")
    

    请注意,此处只有两个IO操作:getContentsputStrLn。所以你只需要一个bind operator来获取从一个动作到另一个动作的信息 在这两者之间,你有纯(++ " hello")。这不需要任何monadic绑定,只需要function composition来传递信息 如果你发现信息流的混合方向很难看,你也可以使用翻转的绑定:

    main = putStrLn . (++ " hello") =<< getContents
    

    或者,你可以使用monadic绑定,但你首先需要masquerade the pure function as an IO action(一个不利用任何副作用可能性的动作):

    main = getContents >>= pure . (++ " hello") >>= putStrLn
    

    或者您可以,而不是“在其打印的内容之后”将putStrLn转换为前置" hello",而不是“transform getContents前置" hello"它取出的东西“:

    main = (++ " hello")<$>getContents >>= putStrLn
    

    所有这些都与monad法律相同。

答案 2 :(得分:3)

  
      
  1. 有没有我得到的东西?(99%是的)
  2.   

是。 >>=右侧的功能是功能,其签名为a -> m bm为monad。

一个比喻:生日礼物

帮助我理解monad(以及带有绑定函数>>=的函数)的东西是而不是来考虑IO

您还可以看到monad(注意IO只是众多monad中的一个)作为集合。例如Maybe a,这也是一个monad。

您可以将Maybe a视为某种“盒子”。该框可以有一个对象(如果是Just x),或者可以是一个“空框”(而不是Nothing)。

现在,绑定运算符>>=在左侧有一个这样的框,在另一侧有一个函数f :: a -> Maybe b。想象一下,f是一个人,现在是他们的生日。他/她收到该框的内容,并且必须将另一个礼物传递给日历上的下一个人。因此绑定操作符>>=打开框,将其传递给此人,并期望可以进一步处理新的礼物。

所以要简短说明:你必须返回一个新的。现在(++ " hello")将字符串作为输入,但将该内容放入新框中(因此没有派对:()。

然而,您可以自己将其包装在一个盒子里。为了做到这一点,有return函数(这是一个函数,不是一个关键字)。所以你可以把它写成:

getContents >>= return . (++ " hello") >>= putStrLn

请注意,函数不必提供相同的“present”,但“box”(monad)的类型必须相同。例如,putStrLn的类型为String -> IO ()。所以putStrLn是一个人,你可以用一个字符串作为现在,并且该函数将返回一个包含()实例的框(或者一个空框,顺便说一下只有一个()的值:())。

我们可以简单地通过一个函数进行字符串处理,例如:

getContents >>= putStrLn . (++ " hello")

所以,如果你写一段代码,如:

a >>= b >>= c >>= d >>= e

这意味着a将构建第一个框。绑定操作符将打开该框并将内容处理为b。基于内容b将构造一个新框(其中包含一种他/她知道c喜欢的对象类型)。绑定操作符打开框并将内容传递给c,依此类推。

这与I / O

有什么关系

I / O功能可以被视为生日故事中的人物。他们返回一个“IO盒子”,以便与其中的内容对话。绑定操作符将打开“IO框”(实际上>>=是知道如何打开IO框的少数函数之一),并将内容传递给下一个函数。通过这样做,它将强制执行命令,因为如果前一个人没有首先检查他们的礼物并构建一个新礼物,则下一个人不能处理礼物。

Maybe重新访问

I / O是非常难以理解的I / O monad,因为它连接到计算机系统。更容易理解的是Maybe monad。正如我们已经讨论过的,Maybe具有以下构造函数:

data Maybe a = Nothing | Just a

现在我们将Nothing想象为:

  +---+
 /   /|
+---+ +
|   |/
+---+

Just x as:

  +---+
 /   /|
+---+ +
| x |/
+---+

包含内容的方框。当然,您可能知道如何打开Just x构造函数并获取x值。但想象一下,我们不允许这样做。只允许>>=运算符打开Maybe框。

然后我们可以构建一个monad:

instance Monad Maybe where
    return x = Just x

    (>>=) Nothing _ = Nothing
    (>>=) (Just x) f = f x

我们看到的是return函数,它将对象包装到Maybe“框中”,因此使用return,我们可以创建“呈现”。

绑定操作符将检查左侧的当前位置。如果结果是框中没有任何内容,则不会打扰右侧的人,只需返回Nothing

如果该框包含x,则会将该对象提供给f,但希望f构建一个新礼物。 >>=因此打开了现在。他/她是“专业的生日礼物揭幕战”。

所以现在我们可以写出类似的东西:

Just 2 >>= Just . (+5) >>= Just . (*6)

它将返回Just 42。为什么?我们从包含2的礼物开始。 >>=展开现在,并将内容处理为Just . (+2),因此我们会评估(Just . (+2)) 2。请注意Just的{​​{1}}已消失。现在我们对此进行评估,因此链中的第二项处理系统的Just 2

这个包再次打开,猜猜它包含一个Just 7,现在7被处理到最后一个函数7,所以最后一个函数将它的当前乘以{ {1}}并将其再次包装成一个盒子。

如果你写了:

Just . (*6)

这会失败。为什么?因为显然第二个功能是心情不好,忘了把他/她的礼物装进盒子里。