我必须承认我对函数式编程了解不多。我从这里和那里读到它,所以开始知道在函数式编程中,一个函数返回相同的输出,对于相同的输入,无论函数被调用多少次。它就像一个数学函数,对于函数表达式中包含的输入参数的相同值,计算出相同的输出。
例如,考虑这个:
f(x,y) = x*x + y; // It is a mathematical function
不管你用了多少次f(10,4)它的值总是104。因此,无论你在哪里写f(10,4),你都可以用104替换它,而不改变整个表达式的值。此属性称为表达式的引用透明性。
正如维基百科所说,
相反,在函数式代码中,函数的输出值只取决于函数的输入参数,因此调用函数f两次,参数x的值相同,两次将产生相同的结果f(x)。
函数式编程中是否存在时间函数(返回当前时间)?
如果是,那它怎么可能存在?它是否违反了函数式编程的原则?它尤其违反了引用透明性,这是函数式编程的特性之一(如果我理解正确的话)。
如果没有,那么在函数式编程中如何知道当前时间呢?
是的,函数式编程中可以存在一个获得时间的函数,它使用了函数式编程的一个稍微修改过的版本,称为非纯函数式编程(默认的或主要的是纯函数式编程)。
在获取时间(或读取文件,或发射导弹)的情况下,代码需要与外部世界进行交互以完成工作,而这个外部世界并不是基于函数式编程的纯粹基础。为了允许纯函数式编程世界与这个不纯的外部世界进行交互,人们引入了不纯函数式编程。毕竟,不与外部世界交互的软件除了做一些数学计算之外没有任何用处。
很少有函数式编程语言具有这种内嵌的不纯特性,这样就不容易区分哪些代码是不纯的,哪些是纯的(如f#等),而一些函数式编程语言确保当你做一些不纯的事情时,代码与纯代码相比明显突出,如Haskell。
另一种有趣的方式是,函数式编程中的get time函数将接受一个“world”对象,该对象具有世界的当前状态,如时间、生活在世界中的人数等。然后从世界对象中获取时间,这个世界对象总是纯的,即你在相同的世界状态下通过,你将总是得到相同的时间。
另一种解释是:没有函数可以获得当前时间(因为它一直在变化),但一个动作可以获得当前时间。让我们说getClockTime是一个常量(或者一个零函数,如果你喜欢的话),它表示获取当前时间的动作。无论什么时候使用,这个动作每次都是一样的,所以它是一个真正的常数。
同样地,我们说print是一个函数,它需要一些时间来表示并将其打印到控制台。由于函数调用在纯函数式语言中不会产生副作用,因此我们将其想象为一个接受时间戳的函数,并返回将其打印到控制台的操作。同样,这是一个真实的函数,因为如果你给它相同的时间戳,它每次都会返回相同的打印操作。
Now, how can you print the current time to the console? Well, you have to combine the two actions. So how can we do that? We cannot just pass getClockTime to print, since print expects a timestamp, not an action. But we can imagine that there is an operator, >>=, which combines two actions, one which gets a timestamp, and one which takes one as argument and prints it. Applying this to the actions previously mentioned, the result is... tadaaa... a new action which gets the current time and prints it. And this is incidentally exactly how it is done in Haskell.
Prelude> System.Time.getClockTime >>= print
Fri Sep 2 01:13:23 東京 (標準時) 2011
因此,从概念上讲,您可以这样看待它:纯函数式程序不执行任何I/O,它定义了一个操作,然后运行时系统执行该操作。该操作每次都是相同的,但执行它的结果取决于执行时的情况。
我不知道这是否比其他解释更清楚,但它有时会帮助我这样思考。
不引入计划生育的其他概念就可以回答这个问题。
可能性1:time作为函数参数
一门语言包括
语言核心和
标准库。
引用透明性是语言核心的属性,而不是标准库的属性。它绝不是用那种语言编写的程序的属性。
使用OP的符号,应该有一个函数
f(t) = t*v0 + x0; // mathematical function that knows current time
他们会要求标准库获取当前时间,比如1.23,并以该值作为参数f(1.23)(或者只是1.23*v0 + x0,参考透明!)来计算函数。这样代码就能知道当前时间。
可能性2:返回值为时间
回答OP的问题:
函数式编程中是否存在时间函数(返回当前时间)?
是的,但是这个函数必须有一个参数,你必须用不同的输入来计算它,这样它就会返回不同的当前时间,否则它就违反了FP的原则。
f(s) = t(s)*v0 + x0; // mathematical function t(s) returns current time
这是我上面所描述的另一种方法。但话又说回来,首先获取这些不同输入的问题仍然归结为标准库。
可能性3:函数式响应式编程
其思想是函数t()计算为与函数t2配对的当前时间。当需要当前时间时,调用t2(),它会给函数t3,以此类推
(x, t2) = t(); // it's x o'clock now
...
(x2, t3) = t2(); // now it's already x2 o'clock
...
t(); x; // both evaluate to the initial time, referential transparency!
关于FP还有更多,但我认为这超出了op的范围。例如,如何要求标准库计算一个函数,并以纯函数的方式对其返回值进行操作:这是关于副作用而不是参考透明度的。
在函数式编程中时间函数是如何存在的?
早在1988年,Dave Harrison在定义带有实时处理功能的早期函数式语言时就面临着这个问题。他为Ruth选择的解决方案可以在他的论文《功能性实时编程:Ruth语言及其语义》第50页中找到:
一个唯一的时钟在运行时自动提供给每个Ruth进程,以提供实时信息,[…]
那么这些时钟是如何定义的呢?第61页:
时钟树由一个表示当前时间的非负整数节点和两个包含未来事件时间的子树组成。
此外:
当树被(惰性地)计算时,每个节点都被实例化为节点实例化时的系统时间值,从而使程序引用当前时间。
翻译成Haskell:
type Clock = Tree Time
type Time = Integer -- must be zero or larger
data Tree a = Node { contents :: a,
left :: Tree a,
right :: Tree a }
除了访问当前时间(带内容)之外,每个Ruth进程还可以提供其他时钟(带左时钟和右时钟),以便在程序的其他地方使用。如果一个进程不止一次需要当前时间,那么它必须每次都使用一个新节点——实例化后,节点的内容保持不变。
这就是函数式语言中时间函数的存在方式:无论调用它的地方,它总是应用于唯一的输入值(在这里是一个时间树)。
你的问题合并了计算机语言的两种相关衡量标准:函数式/命令式和纯粹/不纯粹。
函数式语言定义了函数的输入和输出之间的关系,命令式语言按照特定的执行顺序描述了特定的操作。
纯粹的语言不会产生或依赖副作用,而不纯粹的语言则自始至终都在使用副作用。
百分之百纯粹的程序基本上是无用的。他们可能会进行有趣的计算,但因为他们没有副作用,他们没有输入或输出,所以你永远不会知道他们计算了什么。
要想有用,一个程序至少要有一点不纯。使纯程序有用的一种方法是将它放在一个薄的非纯包装器中。比如下面这个未经测试的Haskell程序:
-- 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