我不得不承认我对函数式编程不太了解。我从头到尾都读过,所以才知道在函数编程中,无论函数被调用多少次,函数都会为相同的输入返回相同的输出。它就像一个数学函数,它对函数表达式中涉及的输入参数的相同值计算出相同的输出。
例如,考虑一下:
1
| f(x,y) = x*x + y; // It is a mathematical function |
不管你用了多少次f(10,4),它的值总是104。因此,无论您在哪里编写了f(10,4),都可以用104替换它,而不会改变整个表达式的值。此属性称为表达式的引用透明性。
正如维基百科所说(链接)
Conversely, in functional code, the output value of a function depends only on the arguments that are input to the function, so calling a function f twice with the same value for an argument x will produce the same result f(x) both times.
函数编程中是否存在时间函数(返回当前时间)?
- 我立即想到的是,函数将获取一个方法参数作为时间,而调用方只提供当前时间。通过这种方式,使用相同的参数提供相同的结果,并由调用方更改参数。
- 我认为大多数(或所有)函数语言都不那么严格,它把函数式编程和命令式编程结合在一起。至少,这是我对F的印象。
- @亚当:打电话的人首先怎么知道现在的时间?
- @纳瓦兹,哦,我明白你在说什么了,我以为你指的是创建一个内部使用当前时间的函数。我敢肯定,如果条件改变了,返回不同的值是非法的,时间的改变就是条件的改变。
- @亚当:事实上,在纯功能语言中这是非法的(如:不可能)。
- @SEPP2K有趣的东西,所以如果一个纯粹的功能系统需要与一个时间元素一起工作,它将需要外部提供吗?作为论据。
- @亚当:差不多。一种纯粹的通用语言,通常能提供一些工具,在不破坏引用透明性的情况下,进入"世界状态"(即当前时间、目录中的文件等)。在哈斯克尔,这是木偶单子,在清洁,这是世界型。因此,在这些语言中,需要当前时间的函数要么将其作为参数,要么需要返回IO操作而不是其实际结果(haskell),要么将世界状态作为参数(clean)。
- @亚历克斯,你的印象是正确的,F是多半径的,强调FP,但如果你愿意,你可以在F中做纯OO。
- 当考虑到fp时,很容易忘记:计算机是一大块可变状态。FP并没有改变这一点,只是掩盖了它。
- 有些语言有一个伪常量变量,如当前时间戳。这是一个每秒钟都不同的常数。
- @米哈伊尔:哈哈哈……这让我发笑。为什么人们一开始会称之为常量,而后者的值每秒都在变化?只是为了"感觉"一致?
- +1在F中处理随机数生成器时也是一样。容易做,但"不纯洁"
- @纳瓦兹,老实说,不知道…它不是一个可以改变或设置的"变量"…所以它是一个常数,至少在一次跑步中是如此。
- 只需考虑从当前运行的"time"函数的每个先前输出,作为函数输入的一部分。因此,在同一运行期间,不能用相同的输入调用两次。
- @大卫:这不是哲学太多了吗?这也是深奥的哲学吗?:此外,使用这种深奥的哲学,甚至命令式编程也可以称为函数式编程。
- 我真的建议阅读1.2 von neumann语言(或真正的第一章),在介绍使用haskell的函数式编程系统时。作者在解释为什么引用透明性对数学如此重要方面做得非常好。
- @米哈伊尔和哈斯凯尔,我们称所有东西——包括函数——变量,尽管改变它们是不可能的。
- 你可能想看看这个视频。他谈了一段时间纯函数编程,然后说,"好吧,但是如果我们一直写纯函数,我们就永远不能从用户那里得到任何输入,或者产生任何输出——我们的程序实际上不能做任何事情。"程序不能完全由纯(非IO)函数构成。但是你可以有一个很大的haskell程序,它几乎完全是纯函数,那么main = interact processInput是唯一可以做任何IO的部分。
- 好问题,但我认为它更适合程序员。
- @我不同意。imho这是一个非常有效的问题,因为它是关于编程主题的,而不是关于"元"的东西。
- @fuzzxl,如果它是关于"meta"的东西,它将属于meta堆栈溢出。这是一个非常适合程序员的问题。
- 任何没有IO的程序都是无用的…
- 即使是一台没有状态的纯计算机也有副作用,它们产生热量,太糟糕了,我们没有真正的图灵机,只有那些近似值。
是和不是。
不同的函数式编程语言解决方法不同。
在哈斯凯尔(一个非常纯粹的国家),所有这些事情都发生在一个叫做I/O Monad的地方——看这里。
你可以把它看作是在你的函数(世界状态)中得到另一个输入(和输出),或者更容易把它看作是一个"不纯"的地方,比如得到变化的时间。
其他语言如f只是内置了一些不纯性,因此您可以拥有一个函数,它为相同的输入返回不同的值,就像普通的命令式语言一样。
正如Jeffrey Burka在评论中提到的:下面是直接从haskell wiki对I/O Monad的很好的介绍。
- 关于哈斯凯尔的IO Monad,最重要的是要认识到,解决这个问题不仅仅是一个黑客;Monad是在某些情况下定义一系列动作的通用解决方案。一个可能的背景是现实世界,我们有IO Monad。另一个上下文在原子事务中,对于该事务我们有stm monad。另一个上下文是作为纯函数实现过程算法(例如knuth shuffle),对于它我们有st monad。你也可以定义你自己的单子。monad是一种可重载的分号。
- 我发现不调用诸如获取当前时间"函数"之类的东西,而是像"过程"之类的东西很有用(尽管可以论证,haskell解决方案是一个例外)。
- 从haskell的角度来看,经典的"程序"(具有类似"…"类型的东西)。->()')作为带有…->()什么都不能做。
- 但在本例中,这类似于"()->…"(一个常量函数),我不会将其称为过程,而是按您的意愿调用它:d
- 典型的哈斯克尔术语是"行动"。
- "monad是一种可重载的分号。"+1
另一种解释方法是:没有函数可以得到当前时间(因为它一直在变化),但是一个操作可以得到当前时间。假设getClockTime是一个常量(或者是一个空函数,如果您愿意的话),它表示获取当前时间的动作。这个动作每次都是一样的,无论何时使用,所以它是一个真正的常量。
同样,假设print是一个函数,它需要一些时间表示并将其打印到控制台。由于函数调用在纯函数语言中不能有副作用,因此我们假设它是一个函数,它采用时间戳并返回将其打印到控制台的操作。同样,这是一个真正的函数,因为如果您给它相同的时间戳,它将每次返回相同的打印操作。
现在,如何将当前时间打印到控制台?好吧,你必须把这两个动作结合起来。那我们该怎么做呢?我们不能只将getClockTime传递给print,因为print需要时间戳,而不是操作。但是我们可以想象,有一个操作符,>>=,它组合了两个操作,一个操作得到时间戳,另一个操作把一个作为参数并打印出来。将此应用于前面提到的操作,结果是…塔达亚…获取当前时间并打印它的新操作。顺便说一下,这就是哈斯克尔的做法。
1 2
| Prelude> System.Time.getClockTime >>= print
Fri Sep 2 01:13:23 東京 (標準時) 2011 |
因此,从概念上讲,您可以这样看待它:一个纯函数程序不执行任何I/O,它定义了一个操作,然后运行时系统执行该操作。每次的动作都是相同的,但执行的结果取决于执行时的情况。
我不知道这是否比其他解释更清楚,但有时这有助于我这样想。
- 我不相信。您方便地将getClockTime称为操作而不是函数。好吧,如果您这样调用,那么调用每个函数动作,那么即使命令式编程也会变成函数式编程。或者,你也可以称之为行动计划。
- @纳瓦兹:这里需要注意的关键是,不能从函数内部执行操作。您只能将操作和功能组合在一起以生成新的操作。执行操作的唯一方法是将其组合到您的main操作中。这允许将纯功能代码与命令式代码分离,并且这种分离是由类型系统强制实现的。将动作视为第一类对象还允许您传递它们并构建自己的"控制结构"。
- 我个人不喜欢(讨厌)人们谈论单子时经常使用的"动作"这个词。这使得听起来有些函数(是的,函数)不是函数,而是操作。行动只是人们(而不是语言)对功能施加的标签。就像把我的int boolVal叫做布尔值。不是。它是一个int。你只是把它当作一个布尔值。总之,哈斯克尔的一切(也许不是所有…咳嗽(unsafeperformio and friends)是该术语真正数学意义上的一个函数。
- @trinithis好吧,也许我应该更清楚地说明,动作也是函数(就像haskell中的所有值都是函数,至少如果你调用常量或空函数),但并非所有函数都是动作。但我不认为"行动"这个词是误导或不精确的。动作是一种值的类型,您可以通过它的类型来区分它,就像您可以通过它们的类型来区分ints一样。我没有对类型参数进行过多的深入研究,一部分是因为它包含在其他回复中,另一部分是因为我想集中讨论我的观点。
- @纳瓦兹:(自我提升)你可以在这里检查我对行动的回答。它适用于IO、随机数、时间等。
- 不是哈斯克尔的一切都是一种功能——那完全是胡说八道。函数的类型包含一个->—这就是标准定义术语的方式,这是haskell上下文中唯一合理的定义。所以类型为IO Whatever的东西不是函数。
- @SEPP2K:也许我对这个定义做了一点扩展,所以我在括号中的注释。但我不是第一个提出空函数概念的人,所以不要严厉地评判我。另外,类型包含=>的值呢?也许你(或标准)也不认为它们的功能,除非它们也包含一个->,但我觉得我不必像你和标准那样谈论haskell,只要我解释我的术语。
- @SEPP2K:那么main :: IO ()和pi :: Floating呢?
- @那么,mylist::[a->b]是函数吗?;)
- @dainichi-=>用于添加约束:例如,如果您创建了一个通用排序函数,那么您应该将Ord a =>放在类型之前,以确保传入的所有内容都有比较值的方法。它与函数类型无关。
- @纳瓦兹:这就是能够组成"动作"的关键所在。原则上,你可以把任何命令程序翻译成一元Haskell代码。区别在于,Haskell的类型检查器强制将IO操作与纯功能代码分离。
- @特立尼西:不是哈斯克尔的一切都是功能!就像普通的编程语言一样,它也有数字和字符串之类的东西,比如说,它们不是函数,不能应用于参数。哈斯克尔不像大多数人想象的那么奇怪。
- @奥马尔:也许"同构于一个函数"会是一个更好的短语。我或多或少是在搞参考透明。
- 当我对"不纯的事情发生在一个单子里"的回答没意见的时候,这让我觉得很有意思。也帮助我理解了为什么需要>>=
- @托马塞丁,我参加聚会真的很晚了,但我想澄清一下:putStrLn不是一个动作,而是一个返回动作的函数。getLine是一个包含动作的变量。动作是值,变量和函数是我们给出这些动作的"容器"/"标签"。
- 我认为添加类型注释可能有助于使这个答案更好。
- 这是一个很好的方式来解释IO Monad所做的工作,并有助于澄清我的理解,谢谢:)
- 免责声明我是从类别理论的角度讲的,@sepp2k int是一种类型,list[int]是一种类型,list[u]是int=>list[int]类型的函数。因此,io是wathever的函数=>io[wathever]
- @奥玛兰托&237;n-camarna:在范畴理论中,一切都与&171;id&187;函数相关。因此,int和string可以被操作为id_u int:int=>int
- @托马塞丁·蒙纳德是如此复杂,可能从很多方面理解它,以至于说有一种解释是危险的。在这个问题的背景下,我觉得这个特别的勇气真的很好
- @戴尼基:尽管我已经知道它是如何工作的,但你的答案的简单性、相关性和明晰性还是很好的。
- @在范畴理论中,每个对象都有一个同一性态(态不必是函数)。haskell还有5号,这不是一个函数:它的类型是,比如int,不是A->B的形式。当然,还有id:int->int,但是我相信你知道这个id函数不同于5号。
- @奥玛兰托&237;n-camarna,对于一个范畴理论家来说,5是一个从()到int的箭头,使其成为一个函数!
- @Sara不完全是;一个范畴理论家会说,Int和() -> Int之间存在同构,因为一个元素的每个元素都可以用另一个元素来标识。这并不意味着一个元素是另一个元素。
在haskell中,一个使用名为monad的构造来处理副作用。monad基本上意味着将值封装到一个容器中,并具有一些函数来将函数从值链接到容器中的值。如果我们的容器具有以下类型:
1
| data IO a = IO (RealWorld -> (a,RealWorld)) |
我们可以安全地执行IO操作。此类型表示:IO类型的操作是一个函数,它接受RealWorld类型的令牌并返回一个新的令牌以及一个结果。
这背后的想法是,每个IO动作都会改变外部状态,由神奇的符号RealWorld表示。使用monads,可以将多个改变现实世界的函数链接在一起。monad最重要的功能是>>=,发音为bind:
1
| (>>=) :: IO a -> (a -> IO b) -> IO b |
>>=采取一个行动和一个职能,采取这一行动的结果,并由此创造一个新的行动。返回类型是新操作。例如,假设有一个函数now :: IO String,它返回一个表示当前时间的字符串。我们可以使用函数putStrLn将其链接,以打印出来:
或者用do符号编写,这对于命令式程序员来说更为熟悉:
1 2
| do currTime <- now
putStrLn currTime |
所有这些都是纯粹的,因为我们将外部世界的突变和信息映射到RealWorld标记上。所以每次运行这个操作,当然会得到不同的输出,但是输入是不同的:RealWorld令牌是不同的。
- -1:我不喜欢RealWorld的烟幕。然而,最重要的是,这个所谓的物体是如何通过链条传递的。缺少的部分是它开始的地方,源代码或到现实世界的连接在那里——它从在IO Monad中运行的主函数开始。
- @kaizer.se您可以想到一个全局RealWorld对象,它在程序启动时被传递到程序中。
- 基本上,您的main函数接受RealWorld参数。只有在执行的时候才会传进来。
- 你看,他们隐藏RealWorld的原因是,有些haskell程序员不改变RealWorld中的一个程序,使得haskell curry的地址和出生日期成为隔壁邻居(这可能会破坏这样会伤害Haskell编程语言。)
- 我认为,当开始考虑并发性时,RealWorld这个比喻并不能很好地工作。当分叉一个IO动作时,这是否意味着整个世界都被复制了?如果是这样,它是如何重新连接的?类似于Java的EDCOX1(6)的抽象版本可能是更好的(即EDCOX1,6,s,您不能运行自己,但只绑定到函数以产生新的Runnabess)。
- @Frerichrabe好问题!RealWorld方案通常在并发情况下分离,因为您得到了非确定性,但任何方案都是如此。我不知道理论是如何模拟的。
- RealWorld -> (a, RealWorld)即使在并发的情况下也不会分解为隐喻,只要你记住现实世界可能随时被你的功能(或当前进程)之外的宇宙其他部分所改变。因此(a)甲烷不会分解,(b)每次将一个类型为RealWorld的值传递给一个函数时,必须重新评估该函数,因为此时现实世界将发生变化(建模为@fuz解释,每次与现实世界交互时都会返回不同的"令牌值")。
大多数函数式编程语言并不纯粹,即它们允许函数不仅依赖于它们的值。在这些语言中,函数返回当前时间是完全可能的。从您标记的语言来看,这个问题适用于scala和f(以及大多数其他ML变体)。
在haskell和clean这样的纯语言中,情况是不同的。在haskell中,当前时间不能通过函数获得,而是通过所谓的IO操作,这是haskell封装副作用的方法。
在clean中,它将是一个函数,但是函数将以一个世界值作为参数,并返回一个新的世界值(除了当前时间之外)。类型系统将确保每个世界值只能使用一次(并且每个使用世界值的函数将生成一个新的值)。这样,每次调用时间函数时都必须使用不同的参数,从而允许每次返回不同的时间。
- 这使它听起来像哈斯克尔和清洁做不同的事情。据我所知,它们的作用是一样的,只是haskell提供了更好的语法(?)为了完成这一点。
- @康拉德:从这个意义上说,它们都使用类型系统特性来抽象副作用,但这就是问题所在。请注意,用世界类型来解释IO Monad是非常好的,但是haskell标准实际上没有定义世界类型,而且在haskell中实际上不可能获得类型world的值(虽然这在clean中是非常可能的,而且确实是必要的)。此外,haskell没有作为类型系统特性的唯一性类型,因此,如果它给了您访问一个世界的权限,它就不能确保您使用它的方式与clean的方式完全相同。
- clean称之为唯一性类型。
"当前时间"不是函数。它是一个参数。如果代码依赖于当前时间,则表示代码是由时间参数化的。
它完全可以用一种纯粹的功能性方式来完成。有几种方法可以做到这一点,但最简单的方法是让时间函数不仅返回时间,还返回为获取下一次时间度量而必须调用的函数。
在C中,您可以这样实现它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
public static readonly ClockStamp ProgramStartTime = new ClockStamp ();
public readonly DateTime Time ;
private ClockStamp _next ;
private ClockStamp () {
this. Time = DateTime. Now;
}
public ClockStamp NextMeasurement () {
if (this. _next == null) this. _next = new ClockStamp ();
return this. _next ;
}
} |
(记住,这是一个简单而不实际的例子。尤其是,列表节点不能被垃圾收集,因为它们是由programstarttime创建的。)
这个"clockstamp"类就像一个不可变的链接列表,但实际上节点是按需生成的,因此它们可以包含"当前"时间。任何想要测量时间的函数都应该有一个"clockstamp"参数,并且必须在其结果中返回其最后一次测量值(这样调用方就不会看到旧的测量值),如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| // Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue <T > {
public readonly ClockStamp Time ;
public readonly T Value ;
public TimeStampedValue (ClockStamp time, T value ) {
this. Time = time ;
this. Value = value ;
}
}
// Times an empty loop.
public static TimeStampedValue <TimeSpan > TimeALoop (ClockStamp lastMeasurement ) {
var start = lastMeasurement. NextMeasurement();
for (var i = 0; i < 10000000; i++ ) {
}
var end = start. NextMeasurement();
var duration = end. Time - start. Time;
return new TimeStampedValue <TimeSpan >(end, duration );
}
public static void Main (String [] args ) {
var clock = ClockStamp. ProgramStartTime;
var r = TimeALoop (clock );
var duration = r. Value; //the result
clock = r. Time; //must now use returned clock, to avoid seeing old measurements
} |
当然,要通过最后的测量是有点不方便的,进出,进出。隐藏样板文件有很多方法,特别是在语言设计级别。我认为哈斯克尔使用了这种技巧,然后用monads隐藏了丑陋的部分。
- 有趣的是,但是for循环中的i++并不具有参考透明性;)
- @我并不完美。:p请放心,脏的可变性不会影响结果的参考透明度。如果您两次通过相同的"上次测量",您将得到一个过时的下一次测量并返回相同的结果。
- @斯特里兰,谢谢你。我认为在命令式代码中,看到用这种方式解释函数概念很有趣。然后我可以想象一种语言,在这种语言中,自然的和语法上的清洁。
- 事实上,你也可以用C的单子方式,从而避免时间戳的明确传递。你需要像struct TimeKleisli { private delegate Res(TimeStampedValue); }这样的东西。但是使用这个的代码看起来仍然不如使用do语法的haskell好。
- @Leftaroundabout通过将bind函数实现为一个名为SelectMany的方法,可以有点假装在c中有monad,这使查询理解语法成为可能。但是你仍然不能在monads上进行多态编程,所以这是一场对抗弱类型系统的艰苦战斗:(
我很惊讶,所有的回答和评论都没有提到内裤或是新产品。通常,当对无限数据结构进行推理时,会提到共生产,但它也适用于无休止的观察流,例如CPU上的时间寄存器。一个coalgebra模型隐藏状态;以及观察该状态的共同生成模型。(正常感应模型构造状态。)
这是反应式函数编程中的一个热门话题。如果你对这类东西感兴趣,请阅读:http://digitalcommons.oshu.edu/csetech/91/(28页)
- 这和这个问题有什么关系?
- 您的问题是以纯粹的功能性方式(例如,返回当前系统时钟的函数)对时间相关行为进行建模。您可以在所有函数及其依赖树中线程类似于IO monad的内容,以访问该状态;也可以通过定义观察规则而不是构造规则来建模状态。这就是为什么在函数式编程中对复杂状态进行归纳建模显得如此不自然的原因,因为隐藏状态实际上是一种共有属性。
- 伟大的源泉!有最近的吗?JS社区似乎仍在努力进行流数据抽象。
是的,如果将纯函数的时间作为参数给定,它就可以返回时间。不同的时间参数,不同的时间结果。然后形成其他的时间函数,并将它们与一个简单的函数词汇表(时间的一部分)结合起来——转换(高阶)函数。由于该方法是无状态的,因此这里的时间可以是连续的(独立于分辨率),而不是离散的,大大提高了模块性。这种直觉是功能反应式规划(FRP)的基础。
对!你说得对!now()或currentTime()或此类风格的任何方法签名都不能以某种方式显示引用透明性。但通过对编译器的指令,它由系统时钟输入参数化。
通过输出,now()可能看起来不像引用透明的后面。但系统时钟的实际行为及其上面的功能是依附的参考透明度。
是的,一个获取时间函数可以存在于函数式编程中,在函数式编程中使用一个稍微修改过的版本,称为不纯函数式编程(默认的或主要的是纯函数式编程)。
在获得时间(或读取文件或发射导弹)的情况下,代码需要与外部世界交互才能完成任务,而这个外部世界并不是基于功能编程的纯基础。为了让一个纯粹的函数式编程世界与这个不纯的外部世界相互作用,人们引入了不纯的函数式编程。毕竟,除了做一些数学计算之外,不与外界交互的软件没有任何用处。
很少有函数式编程语言内置了这种不纯洁的特性,这样就不容易区分哪些代码是不纯洁的,哪些是纯粹的(如f_等),而某些函数式编程语言确保当你做一些不纯洁的事情时,与纯代码(如haskell)相比,代码是明显突出的。
另一个有趣的方法是,函数编程中的get-time函数将采用一个"world"对象,该对象具有世界的当前状态,如时间、生活在世界中的人数等,然后获取时间,从中世界对象将始终是纯的,即您在相同的世界状态中通过,您将始终获得sam时间。
- "毕竟,除了做一些数学计算之外,一个不与外界交互的软件没有任何用处。"据我所知,即使在这种情况下,计算的输入也会在程序中硬编码,也不是很有用。一旦你想从文件或终端读取输入数据到你的数学计算,你就需要不纯的代码。
- 输入数据作为命令行参数的情况如何:)
- @安克尔:那是完全一样的事情。如果程序与其他东西(比如通过键盘的世界,可以说)进行交互,那么它仍然是不纯洁的。
- 但是,命令行参数是作为初始数据传递给程序的东西,而不是程序从环境中请求的东西,而且在程序的生命周期中,参数值将是相同的,无论您读了多少次。
- @是的,我认为你是对的!尽管在命令行上传递大型输入数据可能不太实际,但这可能是一种纯粹的方法。
- 拥有"世界对象",包括生活在世界上的人数,将执行计算机提升到一个近乎无所不知的水平。我认为通常情况下,它包含了一些内容,比如你的硬盘上有多少个文件,以及当前用户的主目录是什么。
- "世界对象"只是一个双关语:)
- @如果您将所有输入数据传递给主函数,那么您就可以保持纯粹。这是非常不方便的-你甚至不能欺骗和通过一个流,因为阅读一个流涉及到副作用。
- @Ziggystar"世界对象"实际上并不包含任何内容,它只是一个程序之外世界变化状态的代理。它的唯一目的是以类型系统可以识别的方式显式标记可变状态。
你的问题混淆了计算机语言的两个相关度量:功能/命令和纯/不纯。
函数语言定义函数的输入和输出之间的关系,命令式语言以特定的执行顺序描述特定的操作。
纯语言不产生副作用,也不依赖副作用,而不纯语言则始终使用副作用。
百分之百的纯程序基本上是无用的。它们可能会执行一个有趣的计算,但是因为它们不能有副作用,所以它们没有输入或输出,所以您永远不会知道它们计算的是什么。
为了有用,一个程序必须至少是一个轻微的污点。使纯程序有用的一种方法是将其放入一个薄的不纯包装器中。像这个未经测试的haskell程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| -- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n- 1) + fib (n- 2)
-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
putStrLn "Please enter the input parameter"
inputStr <- readLine
putStrLn "Starting time:"
getCurrentTime >>= print
let inputInt = read inputStr -- this line is pure
let result = fib inputInt -- this is also pure
putStrLn "Result:"
print result
putStrLn "Ending time:"
getCurrentTime >>= print |
- 如果您能解决获取时间的具体问题,并稍微解释一下我们认为IO值和结果的纯粹程度,这将是很有帮助的。
- 事实上,即使100%的纯程序也会加热CPU,这是一个副作用。
你在函数编程中提出了一个非常重要的主题,也就是说,执行I/O。许多纯语言都是通过使用嵌入的特定于域的语言来实现的,例如,一个子语言,它的任务是对动作进行编码,这样可以产生结果。
例如,haskell运行时希望我定义一个名为main的操作,该操作由构成我的程序的所有操作组成。然后运行时执行此操作。大多数情况下,它执行纯代码。运行时将不时使用计算的数据执行I/O,并将数据反馈回纯代码。
你可能会抱怨这听起来像是作弊,在某种程度上是这样的:通过定义动作并期望运行时执行它们,程序员可以做正常程序所能做的一切。但是haskell的强类型系统在程序的纯部分和"不纯"部分之间创建了一个强大的屏障:你不能简单地将当前CPU时间增加两秒,然后打印它,你必须定义一个导致当前CPU时间的操作,并将结果传递给另一个增加两秒并打印结果的操作。但是,编写过多的程序被认为是不好的风格,因为与告诉我们关于值是什么的所有信息的haskell类型相比,它很难推断出是哪种影响造成的。
示例:c中的clock_t c = time(NULL); printf("%d
", c + 2);,而haskell中的main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)。运算符>>=用于组合动作,将第一个动作的结果传递给产生第二个动作的函数。这看起来相当神秘,haskell编译器支持语法糖,允许我们编写后一个代码,如下所示:
1 2 3 4 5 6 7 8 9
| type Clock = Integer -- To make it more similar to the C code
-- An action that returns nothing, but might do something
main :: IO ()
main = do
-- An action that returns an Integer, which we view as CPU Clock values
c <- getCPUTime :: IO Clock
-- An action that prints data, but returns nothing
print (c + 2*1000*1000*1000*1000) :: IO () |
后者看起来很必要,不是吗?
If yes, then how can it exist? Does it not violate the principle of
functional programming? It particularly violates referential
transparency
它不存在于纯粹的功能性意义上。
Or if no, then how can one know the current time in functional
programming?
首先,了解如何在计算机上检索时间可能很有用。本质上,有机载电路可以跟踪时间(这就是计算机通常需要一个小电池的原因)。然后可能有一些内部进程在某个内存寄存器上设置时间值。它本质上归结为一个可以由CPU检索的值。
对于haskell,有一个"IO操作"的概念,它表示可以执行某些IO过程的类型。因此,我们引用的不是time值,而是IO Time值。所有这些都是纯粹的功能性的。我们引用的不是time,而是"读取时间寄存器的值"。
当我们实际执行haskell程序时,IO操作实际上会发生。