4

据我所知,使用 Scheme 在函数式编程中教授迭代结构的通常(也是我认为最好的)顺序是首先教授递归,然后可能会进入诸如 map、reduce 和所有 SRFI-1 过程之类的东西。我猜这可能是因为通过递归,学生拥有迭代所需的一切(如果他/她想这样做,甚至可以重写所有 SRFI-1)。

现在我想知道是否曾经尝试过相反的方法:使用 SRFI-1 中的几个过程,并且只有当它们还不够时(例如,逼近一个函数)才使用递归。我的猜测是结果不会很好,但我想知道过去使用这种方法的任何经验。

当然,这不是特定于 Scheme 的;这个问题也适用于任何功能语言。

Dave Touretsky 的COMMON LISP: A Gentle Introduction to Symbolic Computation是一本在递归之前教授“应用程序编程”(组合器的使用)的书——但是,这是一本 Common Lisp 书,他可以在此之前教授命令式循环。

4

7 回答 7

6

IMO首先从基本知识块开始更好,然后得出结果。这就是他们在数学中所做的,即他们在乘法之前不引入幂运算,在加法之前不引入乘法,因为在每种情况下前者都是从后者推导出来的。我见过一些导师反其道而行之,我相信它并不像你从基础到结果那样成功。此外,通过延迟更高级的主题,你给学生一个心理挑战,让他们自己使用他们已经拥有的知识得出这些结果。

于 2010-06-03T12:01:07.663 回答
3

说“通过递归,学生拥有迭代所必需的一切”有一些根本性的缺陷。确实,当您知道如何编写(递归)函数时,您可以做任何事情,但是对于学生来说,这有什么更好的呢?当你想一想,如果你懂机器语言,你也有你需要的一切,或者更极端一点,如果我给你一个 cpu 和一堆电线。

是的,这有点夸张,但它可能与人们的教学方式有关。采用任何语言并删除任何无关紧要的结构 - 在像 Scheme 这样的函数式语言中这样做的极端情况下,您将留下 lambda 演算(或类似的东西),这 - 再次 - 是您需要的一切。但很明显,在你覆盖更多领域之前,你不应该把初学者扔进那个锅里。

为了更具体地说明这一点,请考虑listpair之间的区别,因为它们是在 Scheme 中实现的。即使您对它们是如何成对实现的一无所知,您也可以仅使用列表做很多事情。事实上,如果你给学生一个有限的形式cons,需要一个适当的列表作为它的第二个参数,那么在你继续详细介绍如何实现列表之前,你会通过引入一个一致的更容易理解的概念来帮他们一个忙. 如果你有教过这些东西的经验,那么我相信你遇到过很多学生,他们对使用cons需要的地方感到无可救药的困惑append反之亦然。需要两者的问题对于新手来说可能非常困难,世界上所有的盒子+指针图都无法帮助他们。

所以,给一个实际的建议,我建议你看看HtDP:这本书做的非常仔细的一件事是让学生逐渐接触到编程,确保每一步的心理画面与所想的一致。学生当时就知道了。

于 2010-06-03T12:38:37.203 回答
2

我从未见过这种顺序用于教学,我发现它和你一样倒退。StackOverflow 上有不少问题表明,至少有一些程序员认为“函数式编程”完全是“神奇”组合子的应用,当他们需要的组合子不存在时,即使他们需要的组合子不存在,他们也会不知所措就像map3一样简单。

考虑到这种偏见,我会确保学生能够在介绍每个组合器之前自己编写它。

于 2010-06-03T12:00:46.527 回答
2

我也认为在递归之前引入 map/reduce 是个好主意。(但是经典的SICP先引入递归,基于list和recursion实现map/reduce,这是自底向上的构建抽象,重点还是抽象。)

这是我可以使用 F#/ML 与您分享的平方和示例:

let sumOfSqrs1 lst = 
    let rec sum lst acc =
        match lst with
            | x::xs -> sum xs (acc + x * x)
            | [] -> acc
    sum lst 0

let sumOfSqr2 lst = 
    let sqr x = x * x
    lst |> List.map sqr |> List.sum

第二种方法是解决平方和问题的一种更抽象的方法,而第一种方法表达了太多细节。函数式编程的优势在于更好的抽象。使用 List 库的第二个程序表达了可以抽象出 for 循环的想法。

一旦学生可以玩List.*,他们会很想知道这些功能是如何实现的。那时,您可以回到递归。这是一种自上而下的教学方法。

于 2010-06-03T12:01:40.037 回答
1

我认为这是一个坏主意。
递归是编程中最难理解的基础学科之一,甚至更难使用。学习这一点的唯一方法就是去做,而且要做很多。
如果学生将获得很好抽象的高阶函数,他们将使用这些过度递归,并且只会使用高阶函数。然后,当他们需要自己编写高阶函数时,他们会毫无头绪,需要您,老师,实际为他们编写代码。正如有人提到的,如果你想让人们真正理解一个主题以及如何根据他们的需要定制它
,你必须自下而上地学习一个主题。

于 2010-06-03T13:30:35.210 回答
0

我发现很多人在进行函数式编程时经常“模仿”命令式风格,并在他们不需要任何类似循环的东西而需要映射或折叠/减少时尝试用递归来模仿循环。

大多数函数式程序员都会同意你不应该试图模仿命令式风格,我认为递归根本不应该被“教授”,而应该自然地发展并且不言自明,在声明式编程中,通常在不同的点上很明显功能是根据自身定义的。递归不应被视为“循环”,而应被视为“根据自身定义函数”。

然而 Map 是同一件事的重复,通常当人们使用(尾)递归来模拟循环时,他们应该以函数式风格使用 map。

于 2010-06-09T02:32:17.593 回答
-1

问题是,你真正想教/学习的“递归”最终是尾递归,从技术上讲,它不是递归,而是一个循环。

所以我说继续教/学习真正的递归(在现实生活中没人使用,因为它不切实际),然后教他们为什么没用,然后教尾递归,然后教他们为什么不是递归。

在我看来,这似乎是最好的方法。如果您正在学习,请在过多使用高阶函数之前完成所有这些操作。如果您正在教学,请向他们展示它们如何替换循环(然后当您教授尾递归时,他们会明白循环实际上是如何隐藏但仍然存在的)。

于 2010-06-04T12:31:27.580 回答