-5

我正在阅读一篇关于声明式编程语言的文章。

如果我不了解这种类型/范式的编程语言的质量并且它与命令式语言形成对比,我是否应该只阅读这种类型的编程语言(如 Haskell)的编程,然后再阅读那篇文章?

4

5 回答 5

10

声明式范式的要点是懒惰。我们声明式程序员喜欢让编译器完成所有工作。我们尽可能多地不指定我们想要使用的算法,而只指定我们想要的结果的定义。

例如,如果在命令式设置中您想要计算直到 的整数之和n,您可以编写(在 C 中):

int f(int n) {
    int result = 0, i = 1;
    for(;i <= n; i ++)
        result += i;
    return result;
}

在声明性设置中,您只需声明这个总和是多少(在 Haskell 中):

f 0 = 0
f n = n + f (n - 1)

这不是一个算法,它只是一个定义。但是 Haskell 编译器足够聪明,无论如何都能得到结果。

当您切换到 Prolog 时,它会更加明显,下面是规范示例:

  1. 我们声明了不同人之间的一些关系:

    father(luke, anakin).
    father(anakin, a_slave_on_tattouin).
    father(a_slave_on_tattouin, someone).
    father(someone, adam).
    
  2. 然后我们声明某人的祖先是他的父亲或他父亲的祖先:

    ancestor(Young, Old) :-
        father(Young, Old).
    
    ancestor(Young, VeryOld) :-
        father(Young, Old),
        ancestor(Old, VeryOld).
    

就是这样,Prolog 可以释放魔法并回答以下问题:

?- ancestor(luke, X).
X = anakin ;
X = a_slave_on_tattouin ;
X = someone ;
X = adam ;
false.

对阅读上述内容有一些帮助:Head :- Body表示Head if Body。那么上面,

ancestor(Young, VeryOld) :-
    father(Young, Old),
    ancestor(Old, VeryOld).

如果是的父亲并且是的祖先,则表示VeryOld是的祖先。YoungOldYoungVeryOldOld

;意味着。所以这里 Prolog 告诉我们卢克有 4 个祖先,anakina_slave_on_tattouin,等等。

正如你所看到的,我们没有指定任何关于算法的内容,但 Prolog 仍然可以做一些很棒的事情,比如为我们提供luke's 家谱的所有细节。这就是声明式编程的力量:您指定数据,这就是您所关心的,剩下的就是处理编译器的聪明人的工作。

于 2012-08-30T09:19:40.603 回答
4

可能需要一些时间来了解 Haskell 和 Prolog 以及其他任何东西,但这是值得的。这是一个类比,可以让您开始使用声明式与命令式。

假设您想找出当 x 为 5.279 时 5sin(7x+20)+cos(x) 是多少,而您所拥有的只是您的计算器。如果它是一个旧的计算器,你必须告诉它如何做以及以什么顺序。如果我们使用MCfor clear memory to 0 和MRfor recall memory 并将M+当前数字添加到内存中的数字,您实际输入的是

MC       -- clear the memory
5.279    -- take the number 5.279
*7=      -- multiply it by seven
+20=     -- add twenty
sin=     -- find sin of that
*5=      -- times by five
M+       -- store that in the memory
5.279    -- take the number 5.279
cos      -- find cos of it
M+       -- add that to what you had in the memory
MR       -- get the total back out of memory onto the screen

您正在逐步告诉计算器要进行哪些计算,因为它没有被编程来理解数学,只是算术。你计算的结构已经被转化为步骤而变得模糊了。有些人喜欢这种循序渐进的方式,觉得它非常简单,无法理解为什么其他人会被其他方式打扰。有用。你不必相信计算器。

现在任何编程语言和任何现代计算器都可以让你输入

5*sin(7*5.279+20)+cos(5.279)

这更清晰,在过去十年中购买的一些计算器甚至可以让你做

5.279 -> x
5sin(7x+20)+cos(x)

现代计算器让您根据自己的理解来表达问题,而不是将其分解为计算器的步骤。有些人喜欢这种随心所欲的做事方式,觉得它非常简单,不明白为什么其他人会被其他方式打扰。很明显。您不必向计算器解释。

尽管这5.279 -> x; 5sin(7x+20)+cos(x)几乎正是您在命令式编程语言中解决此特定问题的方式,但我们刚才正在讨论计算器的样式,而使用旧计算器的第一种方法更加命令式(逐步) 风格,而新计算器的这种风格更具声明性(只是说出你的意思)。

让我们改变上下文,而不是使用计算器来计算一个数字,我们使用一些感觉技巧来绘制一个简单的废话图片。假设我们在测量时总是从左上角开始。命令式(逐步)风格将是

take a piece of paper 7 by 10
pick up a red pen
put it at the point (3,4)   
draw a 2x4 rectangle here
colour it in
pick up a black pen
put it at the point (4,4)
draw a circle here radius 3
colour it in

而声明式的just-say-what-you-mean风格将是

on a piece of paper 7 by 10
red 2x4 rectangle at (3,4)
underneath
black circle radius 3 at (4,4)

这些不是命令式或声明式代码的示例,而是试图向您展示思维方式和编程风格的差异。在声明式编程中,您说出您的意思,然后使用一个非常聪明的编译器将您的声明转换为最佳指令序列。因为您已经说出了您的意思,所以它可以从根本上将实现这一目标的代码更改为最有效的形式,同时保留您的意思。你信任编译器。

在命令式编程中,你一步一步说出你想要做什么,确保你自己使用了你能想到的最有效的方法,然后编译器使用一些技巧来消除不必要的缓慢并转换为更低的级指令序列。因为您已经指定了执行操作的顺序,所以它可以进行多少优化是有限度的,但是您相信程序员已经做出了最佳选择。

(我谈到信任是因为有人读过这份信任的草稿很重要——她解释说她以命令的方式使用计算器,因为如果她以声明的方式进行计算,她并不真正信任它。我认为这就是程序员也一样。许多程序员对编译器的信任度不够,无法声明性——他们想要控制,并喜欢自己优化代码。)

听起来,一直使用声明式风格会容易得多,但声明式语言通常有一个陡峭的学习曲线,而且大多数人更喜欢命令式。传统上,速度存在巨大差异(命令式的开销更少,因为它更直接地像计算机工作),但一些更现代的声明性代码编译器似乎在很多时候都在速度表的前几名 - 编译的诗句解释比命令式和声明式的区别要大得多,这就是为什么 python 等即使它们是命令式的也经常变慢。

于 2012-08-30T12:06:16.073 回答
2

要回答你的问题,我应该只是阅读我建议答案是否定的,你应该开始编程。我真的不相信你可以很好地理解编程语言的实用性和适用性,或者它们的各种分类,而不用它们编写程序。阅读它们是对编程的补充,而不是替代。

于 2012-08-30T09:25:17.977 回答
0

通常被指出作为声明式编程示例的语言是Prolog(它实际上仍在我的“学习”列表中;SWI-Prolog在Squeeze的 Debian 存储库中可用)。

当然,尝试它是最好的,但如果您只是想体验一下范式,那么仔细查看一些代码示例可能就足够了。

于 2012-08-30T03:22:52.890 回答
-1

声明式编程主要是关于发明领域特定语言 (DSL),然后使用这些子语言进行编程。 Haskell是该特定风格的一个非常好的选择。在 Haskell 中,几乎所有特定领域的语言都嵌入在宿主语言中。然后将它们称为嵌入式 DSL (EDSL)。换句话说,您不需要编写解析器,而是受益于 Haskell 优雅的轻量级语法。让我举几个例子。

假设您要对图像/视频处理进行编码。您注意到几乎所有过滤器都有一些共同点:它们根据周围环境更新点。有一个抽象的概念,称为comonads用于该特定目的。您不表达状态更改,而是对与过滤器关联的转换函数进行编码。换句话说,您对数据相关性和依赖关系进行编码。请注意模糊滤镜令人难以置信的简洁性以及它适用于图片视频(任何 a 的任何内容Filterable)的事实:

blur :: (Filterable img) => Radius -> img -> img
blur radius = extend (average . surroundingPoints radius)

假设您要对图形用户界面 (GUI) 进行编码。您注意到几乎所有的 GUI 都有一些共同点:GUI 的各个元素基本上是随时间变化的字段,它们之间存在相关性。有一个抽象概念称为函数式反应式编程(FRP)用于该特定目的。您不处理事件,而只是描述元素之间的数据关系:

text1 = textField "text1"
text2 = textField "text2"
sum   = fmap show (text1 + text2) <|> "Please enter numbers"

dialog =
    label "First number:" ~|~ text1
        ~---~
    label "Second number:" ~|~ text2
        ~---~
    label "Sum:" ~|~ label sum

构建对话框所需的一切都包含在此描述中,包括用户未输入数字时发生的情况。这既不是特殊语言也不是伪代码。这是实际的 Haskell 代码(使用Netwire库)!

于 2012-08-30T04:52:41.620 回答