我是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行动不再执行(或者我可能不理解这一点)。
我还尝试了很多内容,包括getLine
,readLn
,getContents
,unsafePerformIO
,read
,fmap
的组合果。
这只是一个非常基本的例子,但它完全说明了我现在几次放弃haskell的问题(可能我不是唯一一个),尽管我想要抓住它的顽固性,并学习什么是函数编程语言让我回来。
总结:
我有没有得到的东西?(99%是)
如果是,那又是什么?
我应该如何阅读整个标准输入并在一个连续的表达式中处理它?(如果我只需要一行,我想无论解决方案是什么,它都适用于getLine正弦它基本上是getContents的姐妹)
提前致谢!
答案 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
应该可以正常工作。但它比必要的更复杂。从概念上讲,return
在IO
中包含一个值,>>=
再次展开它(有点)。
我们可以融合右侧的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)
我有没有得到的东西?(99%是的)
是
如果是,那又是什么?
IO String
与String
在概念上完全不同 。前者就像一个烹饪食谱,后者就像一顿饭
在你觉得自己是专家Haskeller之前,你最好忘记unsafePerformIO
这样的事情。这是普通Haskell代码中你永远不需要的东西,只有FFI绑定才能使用C代码或进行最后的优化。
我应该如何阅读整个标准输入并以一个连续的表达式处理它?</ p>
main = getContents >>= putStrLn . (++ " hello")
请注意,此处只有两个IO操作:getContents
和putStrLn
。所以你只需要一个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)
- 有没有我得到的东西?(99%是的)
醇>
是。 >>=
右侧的功能是功能,其签名为a -> m b
且m
为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功能可以被视为生日故事中的人物。他们返回一个“IO盒子”,以便与其中的内容对话。绑定操作符将打开“IO框”(实际上>>=
是知道如何打开IO框的少数函数之一),并将内容传递给下一个函数。通过这样做,它将强制执行命令,因为如果前一个人没有首先检查他们的礼物并构建一个新礼物,则下一个人不能处理礼物。
Maybe
重新访问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)
这会失败。为什么?因为显然第二个功能是心情不好,忘了把他/她的礼物装进盒子里。