了解Haskell中的纯函数和副作用-putStrLn

Flu*_*ous 9 haskell

最近,我开始学习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)

我确信我会忽略一些很简单的东西,或者会误解他的术语。任何帮助将不胜感激。

编辑:

谢谢大家的答案!您的回答使我对这些概念有了更好的理解。我认为尚未完全点击,但以后我将重新讨论该主题,谢谢!

Cub*_*bic 7

如果我们定义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确实不需要任何您熟悉的通用控制结构的部分原因)使用命令式语言,并且可以使用功能代替所有操作),但是我对此不必太担心(类似这样的类比并不总是会立即单击)


ois*_*sdk 5

如果您使用实际执行某些操作的函数,而不是使用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)

这只打印一次。