函数式编程中如何存在时间函数?
我不得不承认,我对函数式编程知之甚less。 我从这里到那里读了一遍,所以知道在函数式编程中,无论调用多less次函数,函数都会返回相同的输出。 这与math函数完全相同,即对函数expression式中涉及的input参数的相同值进行相同的输出。
例如,考虑一下:
f(x,y) = x*x + y; //it is a mathematical function
无论你使用f(10,4)
多less次,它的值总是104
。 因此,无论你在哪里写f(10,4)
,都可以用104
replace它,而不用改变整个expression式的值。 这个属性被称为expression式的引用透明 。
正如维基百科所说( 链接 ),
相反,在函数代码中,函数的输出值仅仅依赖于input到函数的参数,所以用参数x的相同值调用函数f两次将产生相同的结果f(x)。
所以我的问题是:函数式编程中是否可以存在时间函数(返回当前时间)?
-
如果是,那么它又如何存在? 这是否违反函数式编程的原则? 它特别违反了参照透明性 ,这是函数式编程的一个属性(如果我正确地理解它的话)。
-
或者如果没有,那么在函数式编程中如何知道当前的时间呢?
解释它的另一种方式是:没有函数可以获取当前时间(因为它不断变化),但是一个动作可以获得当前时间。 假设getClockTime
是一个表示获取当前时间的动作的常量(或者一个getClockTime
函数,如果你喜欢的话)。 每次使用这个动作都是一样的,所以它是一个真正的常量。
同样,我们假设print
是一个需要一些时间表示并将其输出到控制台的函数。 由于函数调用不能在纯粹的函数式语言中产生副作用,我们可以想象它是一个函数,它需要一个时间戳并返回将其打印到控制台的操作。 再一次,这是一个真正的function,因为如果你给它一个相同的时间戳,它将返回每次打印它的相同动作 。
现在,你怎么能打印当前的时间到控制台? 那么,你必须结合这两个行动。 那么我们该怎么做呢? 我们不能只传递getClockTime
来print
,因为打印需要一个时间戳,而不是一个动作。 但是我们可以想象有一个操作符>>=
,它结合了两个操作,一个获取时间戳,一个将参数作为参数打印出来。 将此应用于前面提到的操作,结果是… tadaaa …获取当前时间并打印它的新操作。 这在Haskell中完全是这样做的。
Prelude> System.Time.getClockTime >>= print Fri Sep 2 01:13:23 東京 (標準時) 2011
因此,从概念上讲,您可以通过这种方式来查看它:纯function程序不执行任何IO操作,它定义了一个操作 ,然后运行系统执行该操作 。 这个动作每次都是一样的,但执行的结果取决于执行的时间。
我不知道这是否比其他解释更清楚,但有时帮助我这样想。
是和不是。
不同的FP语言解决方法不同。
在Haskell(一个非常纯粹的)中,所有这些东西都必须发生在所谓的IO Monad中 – 请看这里 。 你可以把它看作是获得另一个input(和输出)到你的函数(世界状态)或更容易的地方,因为“不纯”就像变化时间一样发生。
像F#这样的其他语言只是内置了一些不纯,所以你可以有一个函数为同样的input返回不同的值 – 就像普通的命令式语言一样。
正如Jeffrey Burka在他的评论中提到的那样:这里是直接从HaskellWiki获得IO Monad的介绍。
在Haskell中,人们使用一个名为monad的构造来处理副作用。 monad基本上意味着你将值封装到一个容器中,并有一些函数将函数从值链接到容器中的值。 如果我们的容器有这样的types:
data IO a = IO (RealWorld -> (a,RealWorld))
我们可以安全地执行IO操作。 这种types意味着:typesIO
的动作是一个函数,它接受一个types为RealWorld
的令牌,并返回一个新的令牌和一个结果。
这背后的想法是,每个IO动作突变的外部状态,由魔法令牌RealWorld
。 使用单子,可以链接多个function,使现实世界变异在一起。 monad最重要的function是>>=
,发音为bind :
(>>=) :: IO a -> (a -> IO b) -> IO b
>>=
采取了一个动作和一个函数,它取得了这个动作的结果,并创build了一个新的动作。 返回types是新的操作。 例如,假设现在有一个函数now :: IO String
,它返回一个表示当前时间的String。 我们可以使用函数putStrLn
将其putStrLn
打印出来:
now >>= putStrLn
或写在do
-Notation,这是一个命令程序员更熟悉:
do currTime <- now putStrLn currTime
所有这一切都是纯粹的,因为我们将外部世界的变化和信息映射到RealWorld
标记。 所以每次你运行这个动作,当然你会得到一个不同的输出,但是input是不一样的: RealWorld
令牌是不同的。
大多数函数式编程语言都不是纯粹的,即它们允许函数不仅取决于它们的值。 在这些语言中,完全有可能具有返回当前时间的函数。 从你用这个问题标记这个问题的语言适用于scala和f#(以及ML的大多数其他变体)。
在Haskell和Clean这些纯粹的语言中,情况是不同的。 在Haskell中,当前时间将不能通过函数获得,而是所谓的IO操作,这是Haskell封装副作用的方式。
在Clean中它是一个函数,但函数会以世界价值作为参数,并返回一个新的世界值(除了当前时间)。 types系统将确保每个世界的值只能被使用一次(并且每个消耗世界值的函数都会产生一个新值)。 这样,时间函数每次都必须用不同的参数调用,因此每次都可以返回一个不同的时间。
“当前时间”不是一个function。 这是一个参数。 如果你的代码依赖于当前时间,这意味着你的代码是按时间参数化的。
完全可以用纯粹的function来完成。 有几种方法可以做到这一点,但最简单的方法是使时间函数不仅返回时间,而且还返回下一次测量必须调用的函数 。
在C#中,你可以像这样实现它:
// 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; } }
(请记住,这只是一个简单而不实际的例子,特别是list节点不能被垃圾收集,因为它们是以ProgramStartTime为根源的)。
这个“ClockStamp”类就像一个不可变的链表,但实际上节点是按需生成的,所以它们可以包含“当前”时间。 任何想要测量时间的函数都应该有一个'clockStamp'参数,并且还必须返回结果中的最后一次测量值(所以调用者看不到旧的测量值),如下所示:
// 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 }
当然,要把最后一次测量的进出,进出,进出都有点不方便。 隐藏样板的方法有很多种,特别是在语言devise层面。 我认为Haskell使用这种技巧,然后用单子隐藏丑陋的部分。
是的,如果将时间作为参数,那么纯函数可能会返回时间。 不同的时间参数,不同的时间结果。 然后形成时间的其他function,并将它们与简单的函数(时间)变换(高阶)函数词汇结合起来。 由于这种方法是无状态的,因此这里的时间可以是连续的(与分辨率无关的),而不是离散的,大大提高了模块性 。 这种直觉是function反应规划(FRP)的基础。
是的,获得时间函数可以存在于FP中使用一个稍微修改后的版本,称为不纯FP(默认或主FP是纯FP)。
在获得时间(或读取文件或发射导弹)的情况下,代码需要与外部世界进行交互以完成工作,而这个外部世界不是基于FP的纯粹基础。 为了让一个纯粹的计划生育世界与这个不纯洁的外部世界互动,人们引入了不纯的计划生育。 毕竟一个不与外部世界交互的软件除了做一些math计算以外没什么用处。
几乎所有的FP编程语言都有这样的杂质特性,这样就不容易分辨哪些代码是不纯的,哪些是纯的(比如F#等),而且一些FP语言确保当你做一些不纯的东西,代码是清晰的与纯代码相比,Haskell更加突出。
另一个有趣的方式是,你在FP中的获得时间函数将采取一个“世界”对象,它具有像时间这样的世界现状,生活在世界上的人数等等。然后从世界对象那里得到时间永远是纯粹的,即你通过同一个世界的状态,你将永远得到同样的时间。
是! 你是对的! Now()或者CurrentTime()或者这种风格的任何方法签名都不以一种方式performance参照透明度。 但是通过编译器的指令,它被系统时钟input参数化。
通过输出,Now()可能看起来像不参照透明度。 但是系统时钟的实际行为及其上面的function是坚持参照透明的。
我感到惊讶的是没有任何答案或评论提到了代数或共同诱导。 通常情况下,在推理无限数据结构的时候会提到coinduction,但也适用于层出不穷的观察,如CPU上的时间寄存器。 一个代数模型的隐藏状态; 和coinduction模型观察该状态。 (正常感应模式构build状态。)
这是反应式函数编程中的热门话题。 如果你对这类东西感兴趣,请阅读: http : //digitalcommons.ohsu.edu/csetech/91/ (28页)
你的问题涉及计算机语言的两个相关测量:function/命令和纯/不纯。
function性语言定义了function的input和输出之间的关系,命令式语言以特定的顺序描述了特定的操作。
纯粹的语言不会产生或依赖于副作用,而不纯的语言会在整个过程中使用它们。
百分之一百的纯程序基本上是无用的。 他们可能执行一个有趣的计算,但是因为他们不能有副作用,所以他们没有input或输出,所以你永远不会知道他们计算的是什么。
为了有用,一个程序必须至less有一个smidge不纯。 一个使纯程序有用的方法是把它放在一个不纯的包装中。 像这个未经testing的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
如果是,那么它又如何存在? 这是否违反了函数式编程的原则? 这特别违反了参照透明度
它不存在纯粹的function意义。
或者如果没有,那么在函数式编程中如何知道当前的时间呢?
知道如何在计算机上检索时间可能首先是有用的。 基本上有车载电路,跟踪时间(这是计算机通常需要一个小电池的原因)。 那么可能会有一些内部过程设置某个内存寄存器的时间值。 这基本归结为一个可以由CPU检索的值。
对于Haskell来说,有一个“IO操作”的概念,它表示可以执行某个IO过程的types。 所以不是引用一个time
值,而是引用一个IO Time
值。 所有这些都是纯粹的function。 我们并没有引用time
而是沿着“读取时间寄存器的值”的方向 。
当我们实际执行Haskell程序时,IO操作实际上会发生。