最近,我开始学习Haskell,因为我想扩大我对函数式编程的了解,我必须说到目前为止我真的很喜欢它。我当前使用的资源是Pluralsight上的“ Haskell基础知识第1部分”课程。不幸的是,我在理解讲师关于以下代码的一句话时遇到了一些困难,希望你们能对此话题有所了解。
随附代码
helloWorld :: IO ()
helloWorld = putStrLn "Hello World"
main :: IO ()
main = do
helloWorld
helloWorld
helloWorld
Run Code Online (Sandbox Code Playgroud)
报价单
如果您在一个do块中多次执行相同的IO操作,则它将多次运行。因此,此程序将字符串“ Hello World”输出三遍。本示例有助于说明这putStrLn不是具有副作用的功能。我们只调用putStrLn一次函数来定义helloWorld变量。如果putStrLn有打印字符串的副作用,它将只打印一次,并且helloWorld在主do-block中重复的变量将没有任何效果。
在大多数其他编程语言中,这样的程序只会打印一次“ Hello World”,因为在putStrLn调用函数时会进行打印。这种微妙的区别通常会使初学者不胜其烦,因此请仔细考虑一下,并确保您了解为什么该程序会打印“ Hello World”三遍,以及如果该putStrLn函数作为副作用打印时只打印一次。
我不明白的
对我来说,字符串“ Hello World”被打印三遍似乎是很自然的。我认为helloWorld变量(或函数?)是一种稍后调用的回调。我不明白的是,如果putStrLn产生副作用,它将导致字符串仅打印一次。或者为什么只用其他编程语言只打印一次。
假设在C#代码中,我想它看起来像这样:
C# (提琴)
using System;
public class Program
{
public static void HelloWorld()
{
Console.WriteLine("Hello World");
}
public static void Main()
{
HelloWorld();
HelloWorld();
HelloWorld();
}
}
Run Code Online (Sandbox Code Playgroud)
我确信我会忽略一些很简单的东西,或者会误解他的术语。任何帮助将不胜感激。
编辑:
谢谢大家的答案!您的回答使我对这些概念有了更好的理解。我认为尚未完全点击,但以后我将重新讨论该主题,谢谢!
如果我们定义helloWorld为局部变量,可能更容易理解作者的含义:
main :: IO ()
main = do
let helloWorld = putStrLn "Hello World!"
helloWorld
helloWorld
helloWorld
Run Code Online (Sandbox Code Playgroud)
您可以将其与类似C#的伪代码进行比较:
void Main() {
var helloWorld = {
WriteLine("Hello World!")
}
helloWorld;
helloWorld;
helloWorld;
}
Run Code Online (Sandbox Code Playgroud)
也就是说,在C#中,WriteLine是一个过程,它输出其参数,但不返回任何内容。在Haskell中,putStrLn是一个函数,它接受一个字符串并为您提供一个操作,该操作将在执行该字符串时打印该字符串。这意味着写作之间绝对没有区别
do
let hello = putStrLn "Hello World"
hello
hello
Run Code Online (Sandbox Code Playgroud)
和
do
putStrLn "Hello World"
putStrLn "Hello World"
Run Code Online (Sandbox Code Playgroud)
就是说,在这个示例中,差异并不是特别深远,所以如果您还没有完全了解作者在本节中要尝试的内容,而现在继续进行下去,那很好。
如果将其与python进行比较,它的效果会更好
hello_world = print('hello world')
hello_world
hello_world
hello_world
Run Code Online (Sandbox Code Playgroud)
这里的关键是,在Haskell的IO动作是不需要被包裹在进一步的“回调”或诸如此类的事做“实”的价值观,以防止它们在执行-更确切地说,只有这样,才能做到让他们执行IS将它们放置在特定的位置(例如,内部某处main或生成的线程main)。
这也不只是一个绝招,它最终会对您的代码编写方式产生一些有趣的影响(例如,这是Haskell确实不需要任何您熟悉的通用控制结构的部分原因)使用命令式语言,并且可以使用功能代替所有操作),但是我对此不必太担心(类似这样的类比并不总是会立即单击)
如果您使用实际执行某些操作的函数,而不是使用helloWorld. 想一想以下几点:
add :: Int -> Int -> IO Int
add x y = do
putStrLn ("I am adding " ++ show x ++ " and " ++ show y)
return (x + y)
plus23 :: IO Int
plus23 = add 2 3
main :: IO ()
main = do
_ <- plus23
_ <- plus23
_ <- plus23
return ()
Run Code Online (Sandbox Code Playgroud)
这将打印 3 次“我正在添加 2 和 3”。
在 C# 中,您可以编写以下内容:
using System;
public class Program
{
public static int add(int x, int y)
{
Console.WriteLine("I am adding {0} and {1}", x, y);
return x + y;
}
public static void Main()
{
int x;
int plus23 = add(2, 3);
x = plus23;
x = plus23;
x = plus23;
return;
}
}
Run Code Online (Sandbox Code Playgroud)
这只打印一次。