声明式编程与命令式编程


24

我对命令式编程非常满意。一旦弄清了我想让计算机做什么,我就不会在算法上表达我想要计算机做什么。但是当涉及到诸如SQL之类的语言时,我经常会陷入困境,因为我的头脑太习惯于命令式编程。

例如,假设您具有以下关系:乐队(bandName,bandCountry),会场(venueName,会场Country),戏剧(bandName,会场Name),并且我想编写一个查询,指出:所有会场名称,以便每个bandCountry都有一个乐队在那个名字的场地上比赛的那个国家。

示例:我想要所有国家(bandCountry)的乐队在其中播放的所有会场名称。另外,“关系”是指SQL表。

在我的脑海中,我立即进入“针对每个bandName遍历所有bandCountries的情况,并为每个bandCountry获取来自其的乐队的列表。如果没有一个乐队在场地名中播放,请转到下一个场地名。否则,在bandCountries的末尾迭代将会场名称添加到良好的会场名称集合中”。

...但是您不能在SQL中这样说,我实际上需要思考如何表述这一点,而直观的命令式解决方案不断地困扰着我。还有其他人有这个问题吗?您是如何克服这一难题的?您发现范式转变了吗?将命令式概念映射到SQL概念,以将命令式解决方案转换为声明式解决方案?读一本好书?

附言:我不是在寻找上述查询的解决方案,而是已经解决了。


1
这是一个好问题,因为您要表达许多人(包括我自己)所具有的弱点。
David Weiser 2010年

定义问题中“关系”的含义可能会很有用。在关系模型(SQL背后的数学)中,“关系”大致类似于SQL表。许多人在真正要说“关系”时会说“关系”。
杰森·贝克

学习集合论和离散数学。

1
@ Jase21,我个人对两者都很熟悉,但是SQL中的一些琐碎的内容仍然让人觉得很有趣。干净的数学示例都没有涉及到奇怪的现实世界。另外,人们可以使用LINQ,因此不会被SQL困扰。最后,向提问者提问:随着时间的流逝,您会习惯它。
工作

Answers:


12

声明式地做事的想法是应该指定什么,而不是如何指定。

对我来说,听起来您走对了。问题不在于您以错误的方式思考问题。那是你太过分了。让我们看看您要做什么:

例如,假设您具有以下关系:乐队(bandName,bandCountry),会场(venueName,会场Country),戏剧(bandName,会场Name),并且我想编写一个查询,指出:所有会场名称,以便每个bandCountry都有一个乐队在那个名字的场地上比赛的那个国家。

到目前为止,这很好。但是,然后您执行此操作:

在我的脑海中,我立即进入“针对每个bandName遍历所有bandCountries的情况,并为每个bandCountry获取来自其的乐队的列表。如果没有一个乐队在场地名中播放,请转到下一个场地名。否则,在bandCountries的末尾迭代将会场名称添加到良好的会场名称集合中”。

本质上,您正在做不必要的工作。你知道什么,你想,这是你真正需要的。但是随后您继续尝试弄清楚如何 获得它。

如果我是你,我会尝试养成以下习惯:

  1. 定义什么你想要的。
  2. 自觉阻止自己定义如何获取它。
  3. 弄清楚如何用SQL表示您想要的内容。

您可能需要花费一些时间和精力,但是一旦您真正搞砸了声明式编程,它就会变得非常有用。实际上,您可能会在其余的代码中使用声明式编程。

如果您正在寻找一本书,我会推荐SQL和Relational Theory。它确实可以帮助您了解SQL数据库背后的理论。只记得带着一粒盐接受Date的建议。他提供了很好的信息,但有时会有些自以为是。


我不明白如何弄清楚是什么方法是错误的方法。无论您使用哪种语言,都必须弄清楚如何告诉它执行您想要的操作。
davidk01 2011年

9

考虑集合,而不是迭代器;sql语句定义所需输出集的属性(又名表/关系)

所有场地名称,以便每个乐队国家都有一个来自该国家/地区的乐队在该名称的场地中演奏

这样的结果(如果我正确理解了您的意图!)将是在该场所中至少有一个乐队演奏的一组场所。无需在bandCountry上进行迭代,因为PLAYS关系已经具有您要查找的信息,您只需消除重复项

所以在SQL中这将是:

select 
    distinct venueName
from PLAYS

编辑:好的,所以所需的实际设置要复杂一些。该数据库的问题是:来自所有国家的乐队在哪些场馆举办过?

因此,我们将所需集合的元素的成员资格标准定义为目标,然后向后工作以填充集合。如果场所在每个国家/地区中至少有一个乐队设有PLAYS行,则该场所是输出集的成员。我们如何获得这些信息?

一种方法是计算每个地点的不同国家/地区,并将其与所有国家/地区的数量进行比较。但是我们没有COUNTRY关系。如果我们仔细考虑一下给出的模型,就会发现所有国家/地区的集合都不是正确的标准。它是拥有至少一个频段的所有国家的集合。因此,我们不需要国家表(尽管对于归一化模型,我们应该有一个国家表),而且我们不在乎会场的国家,我们只需计算具有乐队的国家,例如(在MS-SQL中)

declare @BandCountryCount int
select
    @BandCountryCount = COUNT(distinct bandCountry)
from BAND

我们可以计算每个场地的乐队国家

select
    P.venueName, COUNT(distinct B.bandCountry) as VenueBandCountryCount
from PLAYS P
    inner join BAND B on B.bandName = P.bandName

我们可以使用子查询将两者组合在一起

select
    venueName
from (
    select
        P.venueName, COUNT(distinct B.bandCountry) as VenueBandCountryCount
    from PLAYS P
        inner join BAND B on B.bandName = P.bandName
) X
where X.VenueBandCountryCount = @BandCountryCount

现在,这不是最漂亮的查询(与临时变量和子查询相比,GROUP BY和HAVING可能被认为是更“优雅”的解决方案),但是很明显,我们追求的是那样,因此出于OP的目的,将其保留在此处。

OP的目的是学习如何将思维方式从命令式转变为声明式。为此,请查看描述的命令式解决方案正在执行的操作:

为每个场所名称遍历所有bandCountries,并为每个bandCountry获取来自其的乐队列表。如果它们都没有在场地名称中播放,请转到下一个场地名称。否则,在bandCountries迭代结束时,将会场名称添加到一组良好的会场名称中

上面的确定标准是什么?我认为是这样:

...如果其中一个[来自特定国家/地区的乐队]都不在场地名称中播放...

这是取消资格的标准。势在必行的思维过程从满满地开始,扔掉不符合标准的东西。我们正在过滤数据。

这对于简单的东西很好,但是有助于构造所需的结果集;相应的资格标准是什么,以允许人们加满水桶?

  • 取消资格者:如果某个乐队国家/地区没有乐队在该场地演奏,则该场地将被取消资格
  • (部分)预选赛:如果乐队国家中至少有一个乐队在某个场地演奏,那么该场地可能还可以;继续检查乐队的其他国家
  • (完全)资格赛:如果每个国家/地区中至少有一个乐队在某个场地演奏,则该场地是合格的

最终的预选赛可以通过计数来简化:如果乐队的某个国家的某个地方至少有一个乐队在场地上比赛,则该乐队“满意”;场地“满意的”乐队国家的数量必须等于要获得资格的场地的乐队国家的数量。

现在我们可以通过导航来跨关系推理:

  • 从VENUE关系开始[我们不需要它来回答,但这是关系导航的概念上的起点]
  • 在场地名称上加入PLAYS
  • 在bandName上加入BAND以获取bandCountry
  • 我们不在乎乐队的名字;仅选择场地名称和乐队国家
  • 我们不在乎多余的bandCountries;使用DISTRICT或GROUP BY消除重复项
  • 我们只关心不同bandCountries的数量,而不关心名称
  • 我们只想使用不重复bandCountries计数与bandCountries总数相同的场馆

导致返回上述解决方案(或其合理的传真)

摘要

  • 集合论
  • 关系导航路径
  • 包容性与排他性条件(合格与不合格)

它实际上是“来自所有国家(bandCountry> = venueCountry)的乐队在其中演奏的一组场所”。
EpsilonVector 2010年

@EpsilonVector:请参阅编辑
史蒂文·A·洛

4

学习以声明式风格进行思考和编程的一种方法是学习通用数组语言(如APL或J)。SQL可能不是学习如何以声明式编程的最佳工具。在APL或J中,您将学习对整个数组(向量,矩阵或更高秩的数组)进行操作,而无需显式循环或迭代。这使了解SQL和关系代数变得更加容易。作为一个非常简单的示例,为了从向量V中选择值大于100的项,在APL中我们编写:

(V>100)/V

在这里,V> 100得出与V形状相同的布尔数组,其中1表示我们要保留的值。经验丰富的APLer不会发生迭代,我们只是对向量V加上掩码,返回一个新向量。当然,从概念上讲,这就是SQL where子句或关系代数限制操作的作用。

我认为如果不做很多事情就不能很好地掌握声明式编程,而SQL通常过于具体。您需要编写许多通用代码,学习如何不使用循环以及if / then / else结构以及参与命令式,过程式和标量式编程的所有设备。

可能还有其他功能语言也可以帮助这种思维方式,但是数组语言非常接近SQL。


为“ [您无法获得良好的抓握力...而没有做很多事情” +1。也没有a = a + 1一夜之间学会命令式编程(具有明显的反直觉结构)。学习声明式样式(如逻辑,函数等)需要花费时间,就像学习命令式编程需要花费时间一样。
我的正确意见,2010年

1

首先,您必须学习两者。您可能有一个偏好,但是在另一个更好的地方工作时,请不要打架。许多程序员很想在关系数据库中使用游标,因为它们习惯于遍历每条记录,但是数据库在设置方面要好得多。您不想进入“我知道如何以这种方式,而且我拥有最大的控制权,等等,等等,等等”的心态。


1

您学会了像声明式思考一样思考声明式思考:通过练习从更简单的问题开始,并在“得到它”时着手进行。

您对命令式编程的最初经验包括一堆诸如“ a = a + 1”之类的违反直觉的(实际上是完全荒谬的)语句。您将想法全神贯注到这一点,以至于现在您甚至可能都不记得从明显的陈述不真实中得出的结论。声明式样式的问题在于,您回到了最初使用命令式样式时的状态:“笨拙的newb”。更糟糕的是,您已经使用一种与这种新样式完全矛盾的样式进行了多年的实践,并且有多年的习惯需要撤消,例如“不惜一切代价进行控制”的习惯。

声明式样式可以使用您目前缺乏直觉的另一种方法来工作(除非您多年来一直保持很高的数学能力,但大多数人没有)。您必须重新学习如何思考,并且重新学习的唯一方法就是做到这一点,一次只需一个简单的步骤。

如果您真的想学习SQL概念,那么选择SQL作为您进行声明式编程的第一步。确实,它所基于的元组演算确实具有声明性,但是不幸的是,元组演算的纯度已因实现的实际情况而受到严重损害,实际上,该语言已变得有些混乱。相反,您可能希望查看其他更直接有用(在您所习惯的意义上)的声明性语言,例如Lisps(特别是Scheme),HaskellML(主要用于函数式编程),或者PrologMercury用于(主要是)逻辑编程。

我认为学习这些其他语言将使您更好地了解声明式编程的工作原理,原因如下:

  1. 它们对于“从摇篮到坟墓”编程很有用-因为您可以从头到尾用这些语言编写完整的程序。它们单独使用非常有用,与SQL不同,SQL作为独立语言对大多数人而言实际上是毫无用处的。

  2. 它们每个都使您对声明式编程产生不同的偏见,从而可以为您提供不同的途径以最终“获得它”。

  3. 它们通常也会给您不同的角度来思考编程。即使您自己从未直接使用它们,它们也会提高您推理问题和编码的能力。

  4. 从他们那里学到的教训也将对您的SQL有所帮助-特别是如果您认真学习了关系数据库背后的元组演算,以纯粹地考虑数据的形式。

我特别建议学习一种功能语言(Clojure,作为Lisps的一种,在这里可能是一个不错的选择)一种逻辑语言(我最喜欢Mercury,但是Prolog提供了很多有用的学习材料)用于最大程度地扩展思维过程。


1

在像SQL这样的声明性环境中进行命令式思考是没有错的。只是当务之急应该比您所描述的要高一些。每当我需要查询使用SQL的数据库时,我总是对自己进行思考:

  • 这是我需要的东西。
  • 我将以这种方式将它们放在一起。
  • 我将通过以下谓词来简化我刚刚获得的东西,以真正找到我想要的东西。

上面是高级命令式算法,在SQL设置中对我来说效果很好。我认为这是自上而下的方法,Steven A. Lowe描述了一种非常好的自下而上的方法。


1

问题的关键在于您在倒数第二段中所说的:“您不能在SQL中这样说。” 在此阶段,将SQL作为外语而不是编程语言可能对您更有用。如果您这样想,编写SQL查询实际上就是将您想要的英文语句翻译成“ SQLish”。计算机完全理解SQLish,并且会完全按照您所说的进行操作,因此,只要您正确转换,就不必担心实现问题。

也就是说,学习外语的最佳方法是什么?显然,您必须学习语法和词汇表,这些内容可以从SQL文档中获得。最重要的是练习。您应该尽可能多地读写SQL,而不必觉得必须首先全面了解语法。您可以并且应该在进行过程中查找事物。当您发现用SQL描述所需的数据比用英语描述所需的数据更容易时,就会知道已经掌握了。


1

我花了很长时间也将头转向SQL。我们当时在大学里做过一些关系理论,这只会使事情复杂化。最后,我的学习过程受到了反复的尝试和错误的启发,各种学习材料和示例在我的学习过程中都很有用。从本质上讲,您最终将习惯于此,并且添加一种新的数据和查询思维方式将对您的智力发展有所帮助。

我发现通过逐步构建一组简单的脚本来加快学习速度,这些脚本演示了如何使用每种语言功能以及如何在已知表上实现某些结果(包括表定义以供参考)。

今年早些时候,我进行了一些正式的培训,涉及一个杂乱的Oracle数据库上的数据迁移项目,在该项目中,我必须逐步将库中的片段拼凑起来,以各种方式过滤查询结果,直到获得所需的内容,然后将其转换为必要的,依此类推。一些查询变得非常复杂且难以调试。我怀疑我现在是否可以阅读它们,但是我希望可以通过使用参考构建块再次获得类似的解决方案。

提高您对声明性和功能性空间的直观认识的其他方法是学习更适合特定范式的集合论和编程语言。例如,我目前正在学习一些Haskell,以保持和提高我的数学能力。


0

当您遇到问题时,通常会考虑如何解决。但是,如果您知道计算机如何为您解决问题的话!然后您担心如何消除。

我试图说出它是怎么发生的。

您可能已经熟悉递归程序,在递归程序中,您定义问题而不是说如何解决问题。您定义基数,然后基于n-1定义n。(例如),但是您可能已经知道计算机如何解决它。它从函数开始,递归调用函数,直到达到基本定义为止,然后根据该基本值评估所有其他函数。factorial(n) = n * factorial(n-1)

这就是声明式编程中发生的事情。您可以根据现有定义定义所有内容。并且计算机知道如何基于基本功能为您得出答案。

在SQL中,您可能不会将定义彼此关联,但是将对象或信息彼此关联,可以指定所需的内容,然后根据所提供的关系将计算机搜索到某物(对象,信息)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.