非FP人员可以理解少量的函数式编程吗?[关闭]


43

案例:我在一家公司工作,用Python编写一个处理数组中大量数据的应用程序。我目前是该程序的唯一开发者,但将来(1-3年)可能会被其他一些程序员使用/修改/扩展,目前我还不知道。那时我可能不会在那里直接提供帮助,但是如果有时间的话,也许可以通过电子邮件提供一些支持。

因此,作为一个学习过函数式编程(Haskell)的开发人员,我倾向于解决例如以下过滤问题:

filtered = filter(lambda item: included(item.time, dur), measures)

其余的代码是OO,这只是我想解决的一些小案例,因为根据我的说法,它更简单,更漂亮。

问题:今天可以编写这样的代码吗?

  • 没有编写/学习FP的开发人员如何对这样的代码做出反应?
  • 可读吗?
  • 可以修改吗?
  • 我是否应该写文件给孩子解释该行的用途?

     # Filter out the items from measures for which included(item.time, dur) != True

我问过我的老板,他只是说“ FP是黑魔法,但如果它有效并且是最有效的解决方案,那么可以使用它。”

你对此的看法如何?作为非FP程序员,您如何应对代码?代码是“ googable”的,这样您可以了解它的作用吗?我希望收到反馈。


14
在我看来,许多现代语言都允许某种程度的函数式编程,并且使用这种编程变得越来越普遍。我个人会说(至少是相对简单的过滤/映射任务)。
约阿希姆·绍尔

我不能给你一个不受影响的答案,因为我喜欢FP。如果添加注释不简单,那将是一个好主意。
布鲁诺·谢珀

3
您的评论可能会更清楚;filter包含测试为True的元素,因此您的注释应为:# Select the item's from measures for which included(item.time, dur) == True,避免出现双重否定总是会增进理解。
马丁·皮耶特

6
您是否考虑过使用列表推导?它们通常被认为比map / filter更“ pythonic”。
phant0m 2012年

2
@MatjazMuhic好吧,这很漂亮...;)
kd35a 2012年

Answers:


50

可读吗?

对我来说:是的,但是我已经了解到,Python社区似乎通常认为列表理解比使用map()/ 更干净的解决方案filter()

实际上,GvR甚至考虑完全放弃这些功能。

考虑一下:

filtered = [item for item in measures if included(item.time, dur)]

此外,这样做的好处是列表理解将始终返回列表。map()filter()在另一方面将在Python 3返回一个迭代。

注意:如果您要使用迭代器,则只需将其替换[]为即可()

filtered = (item for item in measures if included(item.time, dur))

老实说,我几乎没有理由使用map()或使用filter()Python。

可以修改吗?

是的,但是,当然有一件事情可以使它变得更容易:将其设置为函数而不是lambda。

def is_included(item):
    return included(item.time, dur)
filtered = filter(is_included, measures)
filtered = [item for item in measures if is_included(item)]

如果您的病情变得更加复杂,这将更容易扩展,并且,您可以重复使用支票。(请注意,您可以在其他函数中创建函数,这可以使其更靠近使用它的位置。)

没有编写/学习过FP的开发人员如何对这样的代码做出反应?

他在Google上搜索了Python文档,并在五分钟后知道了它的工作方式。否则,他不应该使用Python进行编程。

map()并且filter()非常简单的。并不是说您要他们理解单子。这就是为什么我认为您不需要写这样的评论。使用好的变量名和函数名,那么代码几乎可以自我解释。您无法预期开发人员不知道哪些语言功能。就您所知,下一位开发人员可能不知道什么是字典。

我们不了解的内容通常对我们来说是不可读的。因此,如果您从未见过它们,那么您可以说它的可读性不比列表理解的可读性低。但是,正如约书亚(Joshua)在评论中提到的那样,我也相信与其他开发人员使用的内容保持一致非常重要-至少在替代方案没有实质性优势的情况下。


5
这可能是值得提的还有发电机表达式,它的工作方式相同列表理解,但回报发电机代替清单,mapfilter在Python 3做
詹姆斯

@James:好主意,我已经将它们添加到我的帖子中了。谢谢!
phant0m 2012年

2
我通常会提出reduce一个反例,您可以始终使用列表推导参数,因为reduce用内联生成器或列表推导替换它相对困难。
kojiro

4
+1表示该语言的惯用用法。恕我直言的一致性具有巨大的价值,以某种方式使用一种语言,“说话者”的很大一部分确实可以提供比有时甚至是评论更多的可维护性。
约书亚·德雷克

4
+1为“他在Google上搜索Python文档,并在五分钟后知道它的工作方式。否则,他不应该使用Python进行编程。”
Bjarke Freund-Hansen

25

由于开发人员社区重新获得了对函数式编程的兴趣,因此看到一些以最初完全面向对象的语言编写的函数式编程并不罕见。一个很好的例子是C#,其中匿名类型和lambda表达式可以通过函数式编程变得更短和更具表现力。

话虽这么说,函数编程对于初学者来说很奇怪。例如,在培训课程中,我向初学者解释了他们如何通过C#进行功能编程来增强代码,其中一些人没有被说服,还有一些人说原始代码对他们来说更容易理解。我给他们的示例如下:

重构前的代码:

var categorizedProducts = new Dictionary<string, List<Product>>();

// Get only enabled products, filtering the disabled ones, and group them by categories.
foreach (var product in this.Data.Products)
{
    if (product.IsEnabled)
    {
        if (!categorizedProducts.ContainsKey(product.Category))
        {
            // The category is missing. Create one.
            categorizedProducts.Add(product.Category, new List<Product>());
        }

        categorizedProducts[product.Category].Add(product);
    }
}

// Walk through the categories.
foreach (var productsInCategory in categorizedProducts)
{
    var minimumPrice = double.MaxValue;
    var maximumPrice = double.MinValue;

    // Walk through the products in a category to search for the maximum and minimum prices.
    foreach (var product in productsInCategory.Value)
    {
        if (product.Price < minimumPrice)
        {
            minimumPrice = product.Price;
        }

        if (product.Price > maximumPrice)
        {
            maximumPrice = product.Price;
        }
    }

    yield return new PricesPerCategory(category: productsInCategory.Key, minimum: minimumPrice, maximum: maximumPrice);
}

使用FP重构后的代码相同:

return this.Data.Products
    .Where(product => product.IsEnabled)
    .GroupBy(product => product.Category)
    .Select(productsInCategory => new PricesPerCategory(
              category: productsInCategory.Key, 
              minimum:  productsInCategory.Value.Min(product => product.Price), 
              maximum:  productsInCategory.Value.Max(product => product.Price))
    );

这使我认为:

  • 如果您知道下一个将维护您的代码的开发人员将具有足够的总体经验和一些函数式编程知识,则不必担心,但是:

  • 否则,要么避免使用函数式编程,要么在冗长的注释中同时说明与非函数式编程相比,该方法的语法,优点和可能的警告。


8
如果FP不能使您的代码对任何人都可读,则+1 ,这是错误的。
捷尔吉Andrasek

7
我可以看到以前从未见过FP的人如何孤立地认为前一片段比后者更具可读性。但是,如果我们将其乘以100怎么办?阅读100个简短,简明,几乎类似英语的句子,描述什么与数千行管道如何编码。无论多么熟悉,庞大的代码量都应该如此庞大,以至于熟悉不再是一个大赢家。在某个时候,对于任何人来说,首先要熟悉FP的元素,然后阅读几行代码,而不是阅读数千行熟悉的代码,一定更容易。
Esailija

7
最让我困扰的是,我们满足于相信开发人员不学习功能样式是可以接受的。优势已被多次证明,该范例受主流语言支持。如果人们缺乏理解功能技术的能力,即使他们不打算使用它,也应该自学或自学。我对XSLT怀有强烈的热情,但作为专业人士,这并没有阻止我学习它。
Sprague

2
@MainMa您的观点完全正确,我应该更具体一些。我的意思是,应该期望使用任何给定语言的开发人员都能有效地使用这些语言的功能。例如,在C#中使用“功能样式”(使用过滤器/映射/减少样式编程,或传递/返回函数)并不是什么新鲜事物,因此,我认为任何无法读取此类语句的C#开发人员都应更新其技能...可能很快。我当然认为每个开发人员都应该学一点Haskell,但这只是出于学术原因。这是另一回事。
Sprague

2
@Sprague:我明白你的意思。仅仅因为我们几乎不能期望C#程序员开始使用FP中的范例,而这些程序员中有太多甚至没有使用泛型。除非有这么多不熟练的程序员,否则我们的职业门槛将一直很低,尤其是因为大多数没有技术背景的人根本不了解发展的方向。
阿森尼·穆曾科

20

我不是FP程序员,最近我要用JavaScript修改同事的代码。有一个带有回调的Http请求,它看起来很像您包含的语句。我必须说,花了我一些时间(例如半小时)才能弄清楚(代码总的来说不是很大)。

没有任何评论,我认为没有任何必要。我什至没有请同事帮我理解他的代码。

考虑到我已经工作了1.5年左右,因此我认为大多数程序员将能够理解和修改此类代码。

此外,正如Joachim Sauer在其评论中所说,通常有许多语言的FP片段,例如C#(例如indexOf)。如此多的非FP程序员经常对此进行处理,并且您所包含的代码片段并非令人恐惧或难以理解。


1
谢谢你的评论!它从另一个角度为我提供了更多信息。容易成为盲人,然后当您不了解FP时您就不知道它是什么样子了:)
kd35a 2012年

1
@ kd35a,不客气。幸运的是,我在这里可以很客观))
superM 2012年

1
+1指出现在许多语言都包含FP元素。
约书亚·德雷克

LINQ是FP的C#中的主要的例子
康拉德莫拉夫斯基

3

我肯定会说!

函数式编程有很多方面,例如在您的示例中,使用高阶函数只是其中之一。

例如,我认为编写纯函数对以任何语言编写的任何软件都极为重要(其中“纯”是指没有副作用),因为:

  • 他们更容易进行单元测试
  • 它们比副作用函数更容易组合
  • 他们更容易调试

我还经常避免变异值和变量-从FP借用的另一个概念。

这两种技术在Python和其他通常不归类为功能的语言中都可以正常工作。语言本身通常也支持它们(即finalJava中的变量)。因此,将来的维护程序员在理解代码方面不会遇到巨大的障碍。


2

我们去年在一家公司工作过同样的讨论。

讨论涉及“魔术代码”,以及是否应鼓励使用。当进一步研究它时,人们似乎对真正的“魔术代码”有不同的看法。引发讨论的人似乎大多表示,使用函数式风格的表达式(在PHP中)是“神奇的代码”,而源自其他语言的开发人员在其代码中使用了更多FP风格的开发人员似乎认为,神奇的代码是通过文件名等动态包含文件时。

我们从未对此得出任何良好的结论,更多的是,人们通常认为看似陌生的代码是“神奇的”或难以阅读的。因此,避免其他用户似乎不熟悉的代码是个好主意吗?我认为这取决于它的使用位置。我将避免在主要方法(或应用程序的重要中心部分)中使用fp样式的表达式(动态文件包含等),在该方法中,数据应以清晰易读,直观的方式传输。另一方面,我不认为一个人应该害怕挑战极限,如果其他开发人员面对FP代码,并且他们也许有一些不错的内部资源来咨询这些问题,则其他开发人员可能会很快学习FP。

TL; DR:避免在应用程序的高层中央部分进行阅读(有关应用程序功能的概述,请阅读这些部分)。否则使用它。


最好避免在更高级别的代码中使用它!
kd35a 2012年

我一直认为,在不同类型的块(例如静态方法,公共方法,构造函数等)中定义编程技术时,缺乏形式主义,也有很多机会。好的答案……这让我重新审视了一些这些想法。
Sprague

2

C ++社区最近也有lambda的问题,我相信他们有大致相同的问题。答案可能不尽相同。C ++等效于:

std::copy_if(measures.begin(), measures.end(), inserter(filter),
  [dur](Item i) { return included(i, dur) } );

现在std::copy不是新的,_if变体也不是新的,但是lambda是。然而,它在上下文中的定义非常明确:dur被捕获并因此是常量,Item i在循环中有所不同,并且单个return语句完成了所有工作。

这对于许多C ++开发人员来说似乎可以接受。不过,我还没有抽样调查高阶Lambda的意见,而且我希望接受度会低得多。


有趣的是,根据使用哪种语言,可能会有不同的答案。可能与@ChristopherKäck帖子有关,关于PHP编码器比Python编码器在此类问题上的问题更多。
kd35a 2012年

0

将一个代码片段发布给一个不太精通python的同伴开发人员,并询问他是否可以花5分钟检查代码以了解他/她是否理解。

如果是,那么您可能会很高兴。如果没有,您应该考虑使其更清晰。

您的同事会很愚蠢,不明白一些显而易见的东西吗?是的,但是您应该始终按照KISS进行编程。

也许您的代码比更直接,防傻瓜的方法更有效/更美观/更优雅?然后您需要问自己:我需要这样做吗?同样,如果答案是否定的,那就不要这样做!

如果毕竟,您仍然认为自己需要并且想要以FP方式进行操作,那么一定要这样做。相信您的直觉,它们比任何论坛上的大多数人都更适合您的需求:)


随时解释拒绝投票的原因
Arnab Datta
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.