【问题标题】:Delphi anonymous methods - pro and cons. Good practices when using closures(anonymus methods) in DelphiDelphi 匿名方法 - 优点和缺点。在 Delphi 中使用闭包(匿名方法)时的良好做法
【发布时间】:2011-12-10 18:01:19
【问题描述】:

我的团队中有一位同事在我们使用 Delphi 开发的项目中广泛使用闭包。个人而言,我不喜欢这样,因为这会使代码更难阅读,而且我认为只有在需要时才应该使用闭包。

另一方面,我已经阅读了Can someone explain Anonymous methods to me? 和其他与此相关的链接,我考虑到可能我错了,所以我请你给我一些例子,什么时候更好使用闭包而不是“老式”方法(不使用闭包)。

【问题讨论】:

  • 主要问题是变量捕获和产生的生命周期变化。可能会产生一些相当意想不到的效果,尤其是当您在同一范围内定义了多个共享变量捕获的匿名方法时。我通常会避免使用匿名方法,除非它们使代码更清晰、更简单。
  • 匿名方法属于函数式编程范式。它们在命令式 Delphi 环境中相当陌生,与函数式语言相比价值非常有限。
  • 例如查看 OmniThreadLibrary - 我喜欢 Gabriel 使用闭包的方式...
  • Lambdae 被添加到 Delphi 只是因为“每个人都有它们,而我们没有”并且没有 Heilsberg 可以对它们进行一些讨论。它违背了 Pascal 范式,自然会导致严重的语言丑化(同时也违反基本的 Pascal 括号语法)
  • 我强烈反对 Serg。我了解 Python 和 Delphi,并且我认识到 Delphi 中较长的语法是强类型的必要副作用。动态类型和 lambdas 是一个很好的匹配,但是 lambdas/闭包在强类型语言(如 C++)和 Pascal 中很有价值,就像在 Python 中一样。除了特征(通过接口实现)之外,实际上还有一些强大的方法可以使静态类型系统从 Lambdas 中受益,甚至比 Python 中的还要多,因为在 Python 中,您只需使用动态语言技巧就可以完成很多 lambda 技巧.

标签: delphi closures anonymous-methods


【解决方案1】:

我认为这个问题需要非常主观的判断。我是一个老派的德尔福开发人员,并倾向于同意你的看法。闭包不仅增加了某些风险(正如 David H 在 cmets 中指出的那样),它们还降低了所有受过经典训练的 Delphi 开发人员的可读性。那么,为什么将它们添加到语言中呢?例如,在 Delphi XE 中,语法格式化函数和闭包不能很好地协同工作,这增加了我对闭包的不信任; Delphi 编译器中添加了多少东西,IDE 还没有完全升级到支持?当您公开承认如果 Delphi 语言被冻结在 Delphi 7 级别并且再也不会改进时,您会知道您是一个脾气暴躁的老前辈。但 Delphi 是一种活生生的、强大的、不断发展的语法。这是一件好事。当您发现旧曲柄接管时,对自己重复一遍。试试看吧。

我能想到至少 10 个匿名方法真正有意义的地方,以及你应该使用它们的理由,尽管我之前的评论是我不信任它们。我只会指出我决定亲自使用的两个,以及我在使用它们时对自己的限制:

  1. Generics.Collections 中容器类中的排序方法接受匿名方法,以便您可以轻松地提供排序逻辑,而无需编写匹配相同签名的常规(非匿名)函数排序方法期望。新的泛型语法与这种风格相得益彰,虽然一开始它看起来对你来说很陌生,但它会随着你的成长而发展,即使不是真的很好用,至少比替代品更方便。

    李>
  2. 像 Synchronize 这样的 TThread 方法是重载的,除了支持单个 TThreadMethod 作为参数Thread.Synchronize(aClassMethodWithoutParameters) 之外,将参数放入该同步方法中一直是我的痛苦之源。现在你可以使用闭包(匿名方法),并传入参数。

我在编写匿名方法时建议的限制:

A.我个人的经验法则是每个函数只有一个闭包,并且只要有多个闭包,就将那段代码重构为自己的方法。这可以防止你的“方法”的圈复杂度变得疯狂。

B.此外,在每个闭包中,我更喜欢只有一个方法调用及其参数,如果我最终编写了巨大的代码块,我会将它们重写为方法。闭包是为了变量捕获,而不是全权委托编写无休止地扭曲的意大利面条代码。

样本排序:

 var     
   aContainer:TList<TPair<String, Integer>>;  
 begin   
  aContainer.Sort(    
    TMyComparer.Construct(
      function (const L, R: TPair<String, Integer>): integer
      begin
        result := SysUtils.CompareStr(L.Key,R.Key);
      end ) {Construct end}   );  {aContainer.Sort end}  
 end;

更新:一条评论指向“语言丑化”,我认为丑化是指必须写的区别:

  x.Sort(     
    TMyComparer.Construct(
      function (const L, R: TPair<String, Integer>): integer
      begin
        result := SysUtils.CompareStr(L.Key,R.Key);
      end )   ); 

我只是在这里发明了以下假设的鸭子类型(或者我应该说是推断类型)语法来进行比较:

  x.Sort( lambda( [L,R], [ SysUtils.CompareStr(L.Key,R.Key) ] ) )

其他一些语言,如 Smalltalk,和 Python 可以更紧凑地编写 lambda,因为它们是动态类型的。例如,对于 IComparer 的需求,作为传递给容器中 Sort() 方法的类型,是由具有泛型的强类型语言必须遵循的接口风格引起的复杂性的一个示例,以实现诸如排序之类的特征, 需要排序。我不认为有一个很好的方法来做到这一点。就我个人而言,我讨厌在函数调用括号内看到 procedure、begin 和 end 关键字,但我看不出还有什么可以合理地完成。

【讨论】:

  • 如果我理解正确,(1)只是一个“速记”参数,可能与它允许类型聚合作为参数而不首先单独定义它的事实相结合(标准 Pascal 确实支持的东西顺便提一句)。但是对于普通的过程/方法也可以允许参数声明中的类型聚合?
  • 不完全是简写,虽然是的,但它更短。更多的是,您编写的条件或您编写的函数比较在调用上下文中本地化,而不是在其他地方声明(在过程范围之外,作为它自己的过程)。这不仅允许捕获参数,如果你能原谅丑陋的过程开始结束 lambda 部分,它真的非常易读。如果你想允许调用 thread-procedures-with-arbiratrary-params 你将制作 100 多个版本的 TThread.Synchronize( method:TIntStringCharDoubleIntStrCharMethod );
  • "可以更紧凑地编写 lambda,因为它们是动态类型的" 你不需要动态类型。用静态类型语言为此编写编译器更加困难。但是看看 C# 在这些场景中是如何支持类型推断的。
  • 正如我所说,类型推断(鸭子类型)需要编译器中的一些智能,而 Delphi 编译器基础设施缺乏这些智能。 C# 几乎不是一种纯静态类型的语言,因为它是 CLR 驱动的代码域驱动的动态语言环境,也有静态类型,在这一点上,它基本上是语法糖。我的意思是,您可以在 .net(铁蟒)上运行 Python,因此,.net 现在是完全动态类型的,而 C# 只是上面可用的几种语法风格之一。
  • @WarrenP 我不同意。类型推断不是鸭子类型。在鸭子类型中,唯一关心的是功能,而不是名称。像接口一样,它只关心对象是否有方法 X、Y 和 Z,而不关心类型。类型推断采用 Hindley-Milner 算法从变量的使用方式推导出静态类型。最静态类型的语言可以使用 H-M 并且根本不需要支持鸭子类型。 C# 是静态类型的。这不是“糖”。 SWIG 为 Python 包装了 C++,但 C++ 不是动态的。尽管 Qt 是用 C++ 编写的,但 Python 具有 Qt 绑定。​​
猜你喜欢
  • 2016-05-07
  • 1970-01-01
  • 2019-03-09
  • 2011-03-26
  • 2011-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-09
相关资源
最近更新 更多