关于scala:函数式编程中如何存在时间函数?

How can a time function exist in functional programming?

我不得不承认我对函数式编程不太了解。我从头到尾都读过,所以才知道在函数编程中,无论函数被调用多少次,函数都会为相同的输入返回相同的输出。它就像一个数学函数,它对函数表达式中涉及的输入参数的相同值计算出相同的输出。

例如,考虑一下:

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.

函数编程中是否存在时间函数(返回当前时间)?

  • 如果是,那么它怎么可能存在?它是否违反了函数式编程的原则?它特别违反了引用透明性,这是函数式编程的一个特性(如果我正确理解的话)。

  • 或者如果没有,那么如何知道函数编程中的当前时间?


是和不是。

不同的函数式编程语言解决方法不同。

在哈斯凯尔(一个非常纯粹的国家),所有这些事情都发生在一个叫做I/O Monad的地方——看这里。

你可以把它看作是在你的函数(世界状态)中得到另一个输入(和输出),或者更容易把它看作是一个"不纯"的地方,比如得到变化的时间。

其他语言如f只是内置了一些不纯性,因此您可以拥有一个函数,它为相同的输入返回不同的值,就像普通的命令式语言一样。

正如Jeffrey Burka在评论中提到的:下面是直接从haskell wiki对I/O Monad的很好的介绍。


另一种解释方法是:没有函数可以得到当前时间(因为它一直在变化),但是一个操作可以得到当前时间。假设getClockTime是一个常量(或者是一个空函数,如果您愿意的话),它表示获取当前时间的动作。这个动作每次都是一样的,无论何时使用,所以它是一个真正的常量。

同样,假设print是一个函数,它需要一些时间表示并将其打印到控制台。由于函数调用在纯函数语言中不能有副作用,因此我们假设它是一个函数,它采用时间戳并返回将其打印到控制台的操作。同样,这是一个真正的函数,因为如果您给它相同的时间戳,它将每次返回相同的打印操作。

现在,如何将当前时间打印到控制台?好吧,你必须把这两个动作结合起来。那我们该怎么做呢?我们不能只将getClockTime传递给print,因为print需要时间戳,而不是操作。但是我们可以想象,有一个操作符,>>=,它组合了两个操作,一个操作得到时间戳,另一个操作把一个作为参数并打印出来。将此应用于前面提到的操作,结果是…塔达亚…获取当前时间并打印它的新操作。顺便说一下,这就是哈斯克尔的做法。

1
2
Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

因此,从概念上讲,您可以这样看待它:一个纯函数程序不执行任何I/O,它定义了一个操作,然后运行时系统执行该操作。每次的动作都是相同的,但执行的结果取决于执行时的情况。

我不知道这是否比其他解释更清楚,但有时这有助于我这样想。


在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将其链接,以打印出来:

1
now >>= putStrLn

或者用do符号编写,这对于命令式程序员来说更为熟悉:

1
2
do currTime <- now
   putStrLn currTime

所有这些都是纯粹的,因为我们将外部世界的突变和信息映射到RealWorld标记上。所以每次运行这个操作,当然会得到不同的输出,但是输入是不同的:RealWorld令牌是不同的。


大多数函数式编程语言并不纯粹,即它们允许函数不仅依赖于它们的值。在这些语言中,函数返回当前时间是完全可能的。从您标记的语言来看,这个问题适用于scala和f(以及大多数其他ML变体)。

在haskell和clean这样的纯语言中,情况是不同的。在haskell中,当前时间不能通过函数获得,而是通过所谓的IO操作,这是haskell封装副作用的方法。

在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隐藏了丑陋的部分。


我很惊讶,所有的回答和评论都没有提到内裤或是新产品。通常,当对无限数据结构进行推理时,会提到共生产,但它也适用于无休止的观察流,例如CPU上的时间寄存器。一个coalgebra模型隐藏状态;以及观察该状态的共同生成模型。(正常感应模型构造状态。)

这是反应式函数编程中的一个热门话题。如果你对这类东西感兴趣,请阅读:http://digitalcommons.oshu.edu/csetech/91/(28页)


是的,如果将纯函数的时间作为参数给定,它就可以返回时间。不同的时间参数,不同的时间结果。然后形成其他的时间函数,并将它们与一个简单的函数词汇表(时间的一部分)结合起来——转换(高阶)函数。由于该方法是无状态的,因此这里的时间可以是连续的(独立于分辨率),而不是离散的,大大提高了模块性。这种直觉是功能反应式规划(FRP)的基础。


对!你说得对!now()或currentTime()或此类风格的任何方法签名都不能以某种方式显示引用透明性。但通过对编译器的指令,它由系统时钟输入参数化。

通过输出,now()可能看起来不像引用透明的后面。但系统时钟的实际行为及其上面的功能是依附的参考透明度。


是的,一个获取时间函数可以存在于函数式编程中,在函数式编程中使用一个稍微修改过的版本,称为不纯函数式编程(默认的或主要的是纯函数式编程)。

在获得时间(或读取文件或发射导弹)的情况下,代码需要与外部世界交互才能完成任务,而这个外部世界并不是基于功能编程的纯基础。为了让一个纯粹的函数式编程世界与这个不纯的外部世界相互作用,人们引入了不纯的函数式编程。毕竟,除了做一些数学计算之外,不与外界交互的软件没有任何用处。

很少有函数式编程语言内置了这种不纯洁的特性,这样就不容易区分哪些代码是不纯洁的,哪些是纯粹的(如f_等),而某些函数式编程语言确保当你做一些不纯洁的事情时,与纯代码(如haskell)相比,代码是明显突出的。

另一个有趣的方法是,函数编程中的get-time函数将采用一个"world"对象,该对象具有世界的当前状态,如时间、生活在世界中的人数等,然后获取时间,从中世界对象将始终是纯的,即您在相同的世界状态中通过,您将始终获得sam时间。


你的问题混淆了计算机语言的两个相关度量:功能/命令和纯/不纯。

函数语言定义函数的输入和输出之间的关系,命令式语言以特定的执行顺序描述特定的操作。

纯语言不产生副作用,也不依赖副作用,而不纯语言则始终使用副作用。

百分之百的纯程序基本上是无用的。它们可能会执行一个有趣的计算,但是因为它们不能有副作用,所以它们没有输入或输出,所以您永远不会知道它们计算的是什么。

为了有用,一个程序必须至少是一个轻微的污点。使纯程序有用的一种方法是将其放入一个薄的不纯包装器中。像这个未经测试的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


你在函数编程中提出了一个非常重要的主题,也就是说,执行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操作实际上会发生。