F#中的流上的收缩函数
编程并不总是要按时完成任务,进行冲刺,构建应用程序或修正错误,有时您想蜿蜒而行,享受旅程。
F#是一种出色的语言,可用于退步,分区,让世界的烦恼消散,并获得一些乐趣。 我知道是异端,但是是的,编程可能很有趣。
当Apple将Swift定位为儿童的理想学习语言时,它总是让我感到震惊,表面上是因为它要使用多少乐趣。 实际上,我想不出一种有趣的语言,即以陈述为导向的思想的陈旧过时的融合,甚至在刚出厂时就已经过期了。
无论如何,足够的咆哮和狂欢,让我们做些曲折的🙂
定点玩法
在介绍收缩函数之前,让我们简要介绍一下fix函数。 本文很好地解释了Haskell中的函数,其基本思想是fix使我们能够将函数的主体编写为普通函数,而fix负责递归。
修复(1 :)
:是Haskell中的惰性cons运算符,因此上述函数将生成一个无限的1序列。
由于fix负责递归机制,因此我们将重点放在编写简单函数体的内容上。
当然,如果我们尝试在f#中编写相同的函数,那么在f#中并不是那么简单。
让rec fix f = f(fix f)
设ht = h :: t
让一个=修复(缺点1)
我们迎来了一个
进程由于StackOverflowException而终止。
当然这并不令人震惊,我们都知道f#在减少函数应用程序本身之前使用急切的求值方法将函数参数评估为标准形式。 Haskell做相反的事情,它将函数评估为弱头范式,而未对参数进行评估。
之前的大量技术鬼话可能并没有多大意义,因此请阅读这篇文章,以获得关于评估策略的更容易理解的文章。 底线是我们需要在我们的f#定义中引入thunk(函数的占位符)。
你觉得幸运吗,Thunk?
我们可以像这样滚动自己的重击,或者可以利用如下的内置Lazy类型
惰性定点和无限流| F#代码段
使用惰性定点组合器定义的无限流的示例。 www.fssnip.net
修订的懒惰定义使用懒惰的构造函数将我们每个函数应用程序包装在一起。
let fix : (Lazy -> 'T) -> Lazy = fun f ->
let rec x = lazy (fx) in x
因此,现在我们可以编写无限的流了。
let ones = fix (fun x -> Cons (1, x))
尽管有点笨拙,但我们也可以编写无限的自然数流。 我们只需要明确何时强制值和何时包装值。
let nats = fix (fun x -> Cons (1, lazy ( (force (map ((+) 1))) (force x) )))
跟随真正聪明的人
因此,借助我们新奇的修复函数和它生成的时髦的… err thunky流,我们将跟随Graham Hutton的这一令人愉快的演讲,并在f#中实现收缩函数的生成器。
血腥细节警报! 随意跳过生成机器的实现,因为这并不重要,我们将在后面介绍重要部分。
C9讲座:Graham Hutton –如何提高生产力
自从我们在C9上有了一些丰富的函数式编程内容以来,已经太久了。 幸运的是,只有格雷厄姆… channel9.msdn.com
如果您还没有观看演讲,那么收缩功能可以归结为可以在所有以前的输入上进行时光倒流的功能,但是无法访问当前输入或任何将来的输入。
为了使这个想法正式化,我们引入以下类型并将其称为生成函数。 生成函数获取较早输入的列表,并生成单个输出。
类型Gen =(List ->'U)的Gen
一个简单的函数gen将生成函数转换为收缩函数。 尽管在f#中实现不是那么简单,但这是我能想到的最好的方法。
let gen'g = fix(fun f'acc xs-> Cons(g acc,lazy(力f'((head(tail(xs)):: acc)(tail xs))))
let gen(Gen g)=(fun xs-> Cons(g [],lazy((force(gen'g)[head(force xs)](force xs))))))
// gen的类型签名
val gen:Gen -> Lazy <Stream -> Stream
gen函数的输出类型签名与lazy fix函数的输入非常吻合,
//修复的类型签名
val fix:(懒惰->'T)->懒惰
因此我们可以将它们组合成新的功能gfix。
让gfix g =修复(gen g)
// gfix的类型签名
val fix:Gen -> Lazy << Stream >
由于fix将我们的输出作为输入反馈,因此我们的Gen类型被限制为T-> T而不是T->U。
现在,我们的gen函数变得很麻烦,我们便开始编写实际的发电机函数。
在更简单的函数空间中工作
如果您跳过了上一部分,那就太好了,因为那并不重要。 重要的是gfix让我们在更简单的函数空间中工作!
通过fix的相同方式,我们无需担心递归的麻烦细节,而让我们专注于编写简单的函数体, gfix使我们无需担心输入历史中的递归和反馈 。
因此,如果我们知道函数仅查看输入的历史记录以生成下一个输出,则可以在更简单和更狭窄的生成函数空间中编写此函数。
let gots = Gen(函数
| []-> 1
| x :: xs-> x)
如果我们没有任何先前的输入,则生成数字1,如果有,则简单地将先前的输入交出数字1。
让= Gen(函数
| []-> 0
| x :: xs-> x + 1)
如果没有任何先前的输入,则生成数字零,如果有,则只需将数字加1即可。
让gfibs = Gen(函数
| []-> 0
| [x]-> 1
| x :: y :: xs-> x + y)
如果我们没有任何先前的输入,则生成数字零,而单个先前的输入生成数字1,然后将两个最新输入加在一起。 简单!
谈话中的gen函数及其关联的rep函数表示同构,是一种将一个空间中的难题转化为另一空间中的问题的方法。
回到那个“有趣”的Rant
跟着Hutton博士的演讲很有趣,尝试在F#中实现他的想法也很有趣。 为了了解我从何而来的烦恼,我经常想知道是否有人坐在TypeScript或Python的Swift上,并且对这些语言很感兴趣?
您是为了娱乐还是与面向语句的语言一起玩而做什么? 你会跳出循环吗? 抛出异常? 突变状态? 用断言保护空值?
我经常想知道是否通过面向语句的语言向孩子介绍编程是为什么孩子不认为编程是为了玩耍,探索还是在周日下午沿着遥远的溪流蜿蜒而行。
无论如何,这里是代码的完整清单,以及无限流摘要中的抄写部分。 随意玩,是的,玩得开心🙂