Naw*_*waz 636 f# haskell functional-programming scala clean-language
我承认我对功能编程知之甚少.我从这里和那里读到它,因此我们知道在函数式编程中,无论函数被调用多少次,函数都会为相同的输入返回相同的输出.它就像一个数学函数,对于函数表达式中涉及的输入参数的相同值,计算相同的输出.
例如,考虑一下:
f(x,y) = x*x + y; // It is a mathematical function
Run Code Online (Sandbox Code Playgroud)
无论你使用多少次f(10,4),它的价值永远都是104.因此,无论您在何处编写f(10,4),都可以替换它104,而无需更改整个表达式的值.此属性称为表达式的引用透明度.
正如维基百科所说(链接),
相反,在函数代码中,函数的输出值仅取决于输入到函数的参数,因此使用参数x的相同值调用函数f两次将产生相同的结果f(x).
函数式编程中是否存在时间函数(返回当前时间)?
如果是,那么它如何存在?它是否违反了函数式编程的原理?它特别违反了引用透明性,这是函数式编程的一个属性(如果我正确理解它).
或者如果不是,那么如何才能知道函数式编程中的当前时间?
Car*_*ten 354
是的,不是.
不同的函数式编程语言以不同方式解
在Haskell(一个非常纯粹的)中,所有这些东西都必须发生在称为I/O Monad的东西中- 见这里.
您可以将其视为将另一个输入(和输出)添加到您的函数(世界状态)中,或者更容易地将"不纯"视为获得更改时间的地方.
像F#这样的其他语言只是内置了一些不纯的东西,所以你可以拥有一个为同一输入返回不同值的函数 - 就像普通的命令式语言一样.
正如Jeffrey Burka在评论中提到的:这是Haskell wiki直接对I/O Monad的精彩介绍.
dai*_*chi 171
解释它的另一种方法是:没有函数可以获得当前时间(因为它不断变化),但是一个动作可以获得当前时间.假设这getClockTime是一个常数(或者是一个Nullary函数,如果你愿意的话),它表示获取当前时间的动作.无论何时使用,这个动作都是一样的,所以它是一个真正的常数.
同样,假设print是一个需要一些时间表示并将其打印到控制台的函数.由于函数调用不能在纯函数式语言的副作用,而不是我们想象,它是一个函数,它接受一个时间戳和返回的动作将它打印到控制台.同样,这是一个真正的函数,因为如果你给它相同的时间戳,它将返回每次打印它的相同动作.
现在,如何将当前时间打印到控制台?好吧,你必须结合这两个动作.那我们怎么做呢?我们不能只传递getClockTime给print,因为print需要一个时间戳,而不是一个动作.但我们可以想象有一个运算符,>>=它结合了两个动作,一个获取时间戳,另一个作为参数并打印它.将此应用于前面提到的操作,结果是... tadaaa ...获取当前时间并打印它的新动作.顺便说一句,这恰好是在Haskell中完成的.
Prelude> System.Time.getClockTime >>= print
Fri Sep 2 01:13:23 ?? (???) 2011
Run Code Online (Sandbox Code Playgroud)
因此,从概念上讲,您可以通过以下方式查看它:纯函数式程序不执行任何I/O,它定义了一个操作,然后运行时系统执行该操作.该动作是相同的每一次,但在执行它的结果取决于在执行时的情况.
我不知道这是否比其他解释更清楚,但它有时帮助我这样想.
fuz*_*fuz 147
在Haskell中,使用一个名为monad的构造来处理副作用.monad基本上意味着您将值封装到容器中,并具有一些函数来将函数从值链接到容器内的值.如果我们的容器具有以下类型:
data IO a = IO (RealWorld -> (a,RealWorld))
Run Code Online (Sandbox Code Playgroud)
我们可以安全地实施IO操作.此类型表示:类型的操作IO是一个函数,它接受类型的标记RealWorld并返回一个新标记以及结果.
这背后的想法是每个IO动作都会改变外部状态,由魔法标记表示RealWorld.使用monad,可以链接多个函数,一起改变现实世界.monad最重要的功能是>>=,发音为bind:
(>>=) :: IO a -> (a -> IO b) -> IO b
Run Code Online (Sandbox Code Playgroud)
>>=采取一个动作和一个获取此动作结果的函数,并从中创建一个新动作.返回类型是新操作.例如,让我们假装有一个函数now :: IO String,它返回一个代表当前时间的String.我们可以将它与函数链接putStrLn起来打印出来:
now >>= putStrLn
Run Code Online (Sandbox Code Playgroud)
或者用do-Notation 编写,这对命令式程序员来说比较熟悉:
do currTime <- now
putStrLn currTime
Run Code Online (Sandbox Code Playgroud)
所有这一切都是纯粹的,因为我们将变异和外部世界的信息映射到RealWorld令牌.因此,每次运行此操作时,您当然会得到不同的输出,但输入不一样:RealWorld令牌不同.
sep*_*p2k 73
大多数函数式编程语言都不是纯粹的,即它们允许函数不仅依赖于它们的值.在这些语言中,完全可以使用函数返回当前时间.从您标记的语言中,此问题适用于Scala和F#(以及ML的大多数其他变体).
在像Haskell和Clean这样纯粹的语言中,情况就不同了.在Haskell中,当前时间不能通过函数获得,而是所谓的IO操作,这是Haskell封装副作用的方式.
在Clean中它将是一个函数,但该函数将以世界值作为其参数,并返回一个新的世界值(除了当前时间)作为其结果.类型系统将确保每个世界值只能使用一次(并且每个消耗世界值的函数将产生一个新的值).这样,每次都必须使用不同的参数调用时间函数,因此每次都允许返回不同的时间.
Cra*_*ney 22
它绝对可以以纯粹的功能方式完成.有几种方法可以做到这一点,但最简单的方法是让时间函数不仅返回时间,还要返回下次测量时必须调用的函数.
在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;
}
}
Run Code Online (Sandbox Code Playgroud)
(请记住,这是一个简单,不实用的示例.特别是,列表节点不能被垃圾收集,因为它们是由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
}
Run Code Online (Sandbox Code Playgroud)
当然,必须将最后一次测量输入和输出,输入和输出,输入和输出都有点不方便.隐藏样板的方法有很多种,特别是在语言设计层面.我认为Haskell使用这种技巧,然后使用monad隐藏丑陋的部分.
Jef*_*era 16
我很惊讶没有一个答案或评论提到代数或共同引导.通常,在推理无限数据结构时会提到共同诱导,但它也适用于无休止的观察流,例如CPU上的时间寄存器.代数模型隐藏状态; 和coinduction模型观察该状态.(正常感应模型构建状态.)
这是Reactive Functional Programming的热门话题.如果你对这类东西感兴趣,请阅读:http://digitalcommons.ohsu.edu/csetech/91/(28页)
Ank*_*kur 11
是的,函数编程中可以存在获取时间函数,使用稍微修改的函数编程版本,称为不纯函数编程(默认或主要是纯函数式编程).
在获得时间(或读取文件或发射导弹)的情况下,代码需要与外部世界交互以完成工作,并且这个外部世界不是基于函数式编程的纯粹基础.为了让纯粹的函数式编程世界与这个不纯洁的外部世界进行交互,人们已经引入了不纯的函数式编程.毕竟,除了做一些数学计算之外,不与外界交互的软件没有任何用处.
很少有函数式编程的编程语言中都有这种杂质的功能内置,使得它不容易分离出其中的代码是不纯的,并且是纯粹的(如F#等)和一些函数式编程语言确保当你做一些不纯的东西与纯代码(如Haskell)相比,该代码显然更加突出.
另一个有趣的方法是,你在函数式编程中获取时间函数将采用一个"世界"对象,它具有世界当前状态,如时间,生活在世界上的人数等等.然后从哪个世界获取时间对象总是纯粹的,即你传递的是同一个世界状态,你将永远得到同一时间.
Mdu*_*hil 11
是! 你是对的!Now()或CurrentTime()或这种风格的任何方法签名都没有以一种方式表现出参考透明度.但是通过对编译器的指令,它由系统时钟输入参数化.
通过输出,Now()看起来可能不遵循引用透明度.但是系统时钟的实际行为及其上面的功能都遵循参考透明度.
您的问题混淆了计算机语言的两个相关度量:功能/命令和纯/不纯.
函数式语言定义函数的输入和输出之间的关系,命令式语言以特定的顺序描述特定的操作.
纯语言不会产生或依赖副作用,而不纯的语言始终使用它们.
百分之百的纯程序基本上没用.它们可能会执行一个有趣的计算,但由于它们没有副作用,因此它们没有输入或输出,因此您永远不会知道它们的计算结果.
为了有用,一个程序必须至少是一个微不足道的.使纯程序有用的一种方法是将它放在一个薄的不纯包装器中.像这个未经测试的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
Run Code Online (Sandbox Code Playgroud)