79

我将教授离散结构的低年级课程。我之所以选择教科书《离散结构、逻辑和可计算性》,部分原因是它包含有助于使用函数式编程语言实现的示例和概念。(我也认为这是一本很好的教科书。)

我想要一种易于理解的 FP 语言来说明 DS 概念并且学生可以使用。大多数学生最多只有一两个学期的 Java 编程。在查看了 Scheme、Erlang、Haskell、Ocaml 和 SML 之后,我选择了 Haskell 或 Standard ML。出于以下概述的原因,我倾向于 Haskell,但我希望那些在其中一个或另一个中活跃的程序员的意见。

  • Haskell 和 SML 都具有模式匹配,这使得描述递归算法变得轻而易举。
  • Haskell 有很好的列表推导,可以很好地匹配这些列表的数学表达方式。
  • Haskell 有惰性求值。非常适合使用列表理解技术构建无限列表。
  • SML 有一个真正的交互式解释器,可以在其中定义和使用函数。在 Haskell 中,函数必须在单独的文件中定义并在用于交互式 shell 之前进行编译。
  • SML 以易于理解的语法明确确认函数参数和返回类型。例如:val foo = fn : int * int -> int。Haskell 的隐式 curry 语法有点迟钝,但并不完全陌生。例如:foo :: Int -> Int -> Int。
  • Haskell 默认使用任意精度的整数。它是 SML/NJ 中的一个外部库。SML/NJ 默认将输出截断为 70 个字符。
  • Haskell 的 lambda 语法很微妙——它使用单个反斜杠。SML 更明确。不过,不确定我们是否会在这个类中需要 lambda。

本质上,SML 和 Haskell 大致等价。我倾向于 Haskell,因为我喜欢 Haskell 中的列表推导和无限列表。但我担心 Haskell 紧凑语法中大量的符号可能会给学生带来问题。根据我在 SO 上阅读其他帖子所收集到的信息,不建议从 FP 开始的初学者使用 Haskell。但我们不会构建成熟的应用程序,只是尝试简单的算法。

你怎么看?


编辑:在阅读了您的一些出色回复后,我应该澄清一些要点。

在 SML 中,在解释器中定义函数和在外部文件中定义函数之间没有语法上的区别。假设您要编写阶乘函数。在 Haskell 中,您可以将此定义放入文件中并将其加载到 GHCi 中:

fac 0 = 1
fac n = n * fac (n-1)

对我来说,这很清楚、简洁,并且符合书中的数学定义。但是如果你想直接在 GHCi 中编写函数,你必须使用不同的语法:

let fac 0 = 1; fac n = n * fac (n-1)

当使用交互式解释器时,从教学的角度来看,当学生可以在文件和命令行中使用相同的代码时,非常非常方便。

“显式确认函数”是指在定义函数时,SML 会立即告诉您函数的名称、参数的类型和返回类型。在 Haskell 中,你必须使用:type命令,然后你会得到有点令人困惑的咖喱符号。

Haskell 还有一件很酷的事情——这是一个有效的函数定义:

fac 0 = 1
fac (n+1) = (n+1) * fac n

同样,这符合他们可能在教科书中找到的定义。在 SML 中不能这样做!

4

8 回答 8

90

就像我喜欢 Haskell 一样,以下是我更喜欢将 SML 用于离散数学和数据结构课程(以及大多数其他初学者的课程)的原因:

  • Haskell 程序的时间和空间成本可能很难预测,即使对于专家来说也是如此。SML 提供了更多有限的方法来吹制机器。

  • 交互式解释器中函数定义的语法与文件中使用的语法相同,因此您可以剪切和粘贴。

  • 尽管 SML 中的运算符重载完全是假的,但它也很简单。在 Haskell 中教一整堂课而不必进入类型课程将是很困难的。

  • 学生可以使用print. (尽管正如评论者指出的那样,在 Haskell 中使用 . 可以得到几乎相同的效果Debug.Trace.trace。)

  • 无限的数据结构让人们大吃一惊。对于初学者,你最好让他们定义一个包含 ref 单元和 thunk 的流类型,这样他们就知道它是如何工作的:

    datatype 'a thunk_contents = UNEVALUATED of unit -> 'a
                               | VALUE of 'a
    type 'a thunk = 'a thunk_contents ref
    val delay : (unit -> 'a) -> 'a thunk
    val force : 'a thunk -> 'a
    

    现在它不再是魔法了,你可以从这里转到流(无限列表)。

  • 布局不像 Python 那样简单,而且可能会造成混淆。

Haskell 有两个优势:

  • 在核心 Haskell 中,您可以在函数定义之前编写函数的类型签名。这对学生和其他初学者非常有帮助。在 SML 中没有一种处理类型签名的好方法。

  • Haskell 有更好的具体语法。Haskell 语法是对 ML 语法的重大改进。我写了一篇关于何时在 ML 程序中使用括号的简短说明;这有点帮助。

最后,有一把双刃剑:

  • Haskell 代码默认是纯代码,因此您的学生不太可能偶然发现不纯的构造(IO monad、state monad)。但出于同样的原因,它们不能打印,如果你想做 I/O,那么至少你必须解释do符号,return这很令人困惑。

在一个相关主题上,这里有一些对你的课程准备的建议:不要忽视Chris Okasaki 的Purely Functional Data Structures。即使您没有让您的学生使用它,您也一定会想要一份。

于 2009-05-01T22:59:49.780 回答
29

我们在大学的第一年教 Haskell。我对此的感觉有点复杂。一方面,在第一年教 Haskell 意味着他们不必忘记命令式风格。Haskell 还可以生成非常简洁的代码,之前有一些 Java 的人可以欣赏。

我注意到学生经常遇到的一些问题:

  • 起初,模式匹配可能有点困难。学生们最初在了解值构造和模式匹配之间的关系时遇到了一些问题。他们也有一些区分抽象的问题。我们的练习包括编写简化算术表达式的函数,一些学生很难看出抽象表示(例如Const 1)和元语言表示(1)之间的区别。

    此外,如果您的学生应该自己编写列表处理函数,请注意指出模式之间的区别

    []
    [x]
    (x:xs)
    [x:xs]
    

    根据你想教他们多少函数式编程,你可以给他们一些库函数,让他们玩弄。

  • 我们没有教我们的学生匿名函数,我们只是告诉他们where子句。对于某些任务,这有​​点冗长,但在其他方面工作得很好。我们也没有告诉他们部分申请;这在 Haskell 中可能很容易解释(由于其书写类型的形式),因此可能值得向他们展示。

  • 他们很快发现了列表推导式,并且比高阶函数(如filter, map, zipWith.

  • 我认为我们在教他们如何让他们按类型引导他们的思想方面错过了一点。不过,我不太确定这是否对初学者有帮助。

  • 错误消息通常对初学者不是很有帮助,他们可能偶尔需要一些帮助。我自己没有尝试过,但是有一个专门针对新手的 Haskell 编译器,主要是通过更好的错误消息:Helium

  • 对于小程序,可能的空间泄漏之类的事情不是问题。

总的来说,Haskell 是一种很好的教学语言,但也有一些缺陷。鉴于学生对列表推导比高阶函数感觉更舒服,这可能是你需要的论据。我不知道你的课程有多长,或者你想教他们多少编程,但一定要计划一些时间来教他们基本概念——他们会需要的。

于 2009-05-01T13:11:49.443 回答
14

顺便提一句,

# SML 有一个真正的交互式解释器,可以在其中定义和使用函数。在 Haskell 中,函数必须在单独的文件中定义并在用于交互式 shell 之前进行编译。

是不准确的。使用 GHCi:

Prelude> let f x = x ^ 2
Prelude> f 7
49
Prelude> f 2
4

在 haskell.org edu 上也有很好的 Haskell 教育资源。页面,来自不同老师的经验。http://haskell.org/haskellwiki/Haskell_in_education

最后,如果您使用 Haskell,您将能够仅仅为了好玩而教他们多核并行性 :-)

于 2009-05-01T14:54:52.113 回答
13

许多大学将 Haskell 作为第一门函数式语言甚至第一门编程语言来教授,所以我认为这不会成为问题。

在完成了这样一门课程的一些教学之后,我不同意您发现的可能混淆的可能性。早期混淆的最可能来源是由错误的布局引起的解析错误,以及当数字文字使用不正确时有关类型类的神秘消息。

我也不同意任何关于不建议从 FP 开始的初学者使用 Haskell 的建议。这肯定是一种大爆炸式的方法,而具有突变的严格语言则不是,但我认为这是一种非常有效的方法。

于 2009-05-01T07:14:38.803 回答
9
  • SML 有一个真正的交互式解释器,可以在其中定义和使用函数。在 Haskell 中,函数必须在单独的文件中定义并在用于交互式 shell 之前进行编译。

虽然 Hugs 可能有这个限制,但 GHCi 没有:

$ ghci
GHCi, version 6.10.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> let hello name = "Hello, " ++ name
Prelude> hello "Barry"
"Hello, Barry"

我喜欢 GHC(i) 而不是 Hugs 的原因有很多,这只是其中之一。

  • SML 以易于理解的语法明确确认函数参数和返回类型。例如:val foo = fn : int * int -> int。Haskell 的隐式 curry 语法有点迟钝,但并不完全陌生。例如:foo :: Int -> Int -> Int。

SML 也有你所说的“隐式 curry”语法。

$ sml
Standard ML of New Jersey v110.69 [built: Fri Mar 13 16:02:47 2009]
- fun add x y = x + y;
val add = fn : int -> int -> int

本质上,SML 和 Haskell 大致等价。我倾向于 Haskell,因为我喜欢 Haskell 中的列表推导和无限列表。但我担心 Haskell 紧凑语法中大量的符号可能会给学生带来问题。根据我在 SO 上阅读其他帖子所收集到的信息,不建议从 FP 开始的初学者使用 Haskell。但我们不会构建成熟的应用程序,只是尝试简单的算法。

比起 SML,我更喜欢使用 Haskell,但我还是会先教 SML。

  • 借用 nominolo 的想法,列表推导似乎确实会减慢学生学习某些高阶函数的速度。
  • 如果您想要惰性和无限列表,那么明确地实现它是有益的。
  • 因为 SML 被急切地评估,执行模型更容易理解,并且“通过 printf 调试”比在 Haskell 中工作得好得多。
  • SML 的类型系统也比较简单。虽然你的类可能无论如何都不会使用它们,但 Haskell 的类型类仍然是一个额外的障碍——让他们理解 SML 中的'avs''a区别已经足够困难了。
于 2009-05-01T15:08:48.273 回答
7

大多数答案都是技术性的,但我认为您至少应该考虑一个不是:Haskell(作为 OCaml),此时,有一个更大的社区在更广泛的环境中使用它。Hackage还有一个大型的库和应用程序数据库,这些库和应用程序是为了盈利和乐趣而编写的。这可能是让您的一些学生在课程结束后继续使用该语言的一个重要因素,并可能在以后尝试其他功能语言(如标准 ML)。

于 2013-07-01T23:28:38.807 回答
5

我很惊讶你没有考虑 OCaml 和 F#,因为它们解决了你的许多问题。当然,体面和有用的开发环境是学习者的重中之重吗?在这方面,SML 远远落后,而 F# 则领先于所有其他 FPL。

此外,OCaml 和 F# 都有列表推导。

于 2009-05-12T19:44:26.273 回答
5

哈斯克尔。由于我从使用 Haskell 中学到的东西,我在 CS 的算法/理论课上领先。它是如此全面的语言,只要使用它,它就会教你大量的 CS 知识。

但是,SML 更容易学习。Haskell 具有惰性求值和控制结构等特性,使其功能更加强大,但代价是陡峭的(ish)学习曲线。SML 没有这样的曲线。

也就是说,大多数 Haskell 都在从不太科学/数学的语言中学习东西,例如 Ruby、ObjC 或 Python。

于 2015-03-11T21:10:39.757 回答