哪些特性使得Perl成为一种函数式编程语言?

21

受启发于:https://stackoverflow.com/questions/30977789/why-is-c-not-a-functional-programming-language

我发现了Higher Order Perl

这让我想到了Perl是一种函数式编程语言的说法。现在,我理解函数式编程是一种技术(就像面向对象编程)。

然而,我找到了一份关于函数式编程语言规范

  • 第一类函数
  • 高阶函数
  • 词法闭包
  • 模式匹配
  • 单一赋值
  • 惰性求值
  • 垃圾回收
  • 类型推断
  • 尾调用优化
  • 列表推导
  • 单子效应

其中的一些我非常熟悉:

例如,垃圾回收是通过Perl中的引用计数实现的,在不再需要时释放内存。

词法闭包甚至可以在FAQ中找到:什么是闭包?——这里可能有更好的文章:http://www.perl.com/pub/2002/05/29/closure.html

但是对于其中一些我开始感到有些模糊-例如列表推导-我认为这是指map/grepList::Utilreduce?)

有人能帮我填空吗?上面提到的哪些是Perl易于实现的(是否有简单的例子),还有哪些方面它不擅长?


3
没有尾调用优化, https://dev59.com/SGIj5IYBdhLWcg3wCg7X - mpapec
2个回答

28

相关的有用内容:

Perl僧侣对函数式编程的抨击

高阶Perl

C2.com函数式编程定义

一等函数

在计算机科学中,如果一种编程语言将函数视为一等公民,则称其具有一等函数。具体而言,这意味着该语言支持将函数作为参数传递给其他函数,从其他函数返回它们作为值,并将它们分配给变量或存储在数据结构中。

所以在Perl中:

my $print_something = sub { print "Something\n" };

sub do_something {
    my ($function) = @_;
    $function->();
}

do_something($print_something);

结论:本地支持

高阶函数

在数学和计算机科学中,高阶函数(也称为函数形式、函数或函数子)是执行以下至少一项操作的函数:

  • 将一个或多个函数作为输入

  • 输出一个函数

参考这篇perlmonks文章:

在Perl术语中,我们经常将它们称为回调、工厂以及返回代码引用的函数(通常是闭包)。

结论:本地支持

词法闭包

在Perl FAQ中,我们有关于什么是闭包?的问题:

闭包是一个具有精确但难以解释含义的计算机科学术语。通常,在Perl中,闭包被实现为具有对其自身范围外的词法变量的持久引用的匿名子例程。这些词法变量神奇地指向在定义子例程时周围存在的变量(深度绑定)。闭包最常用于编程语言中,其中函数的返回值可以是函数本身,就像在Perl中一样。
在文章Achieving Closure中可能会更清楚地解释这一点。
sub make_hello_printer {
    my $message = "Hello, world!";
    return sub { print $message; }
}

my $print_hello = make_hello_printer();
$print_hello->()

结论:本地支持

模式匹配

在纯函数语言和本页面的上下文中,模式匹配是一种分派机制:选择正确的函数变体进行调用。受标准数学符号启发。

分派表是最接近的近似值 - 本质上是匿名子例程或代码引用的哈希。

use strict;
use warnings;

sub do_it {
    print join( ":", @_ );
}
my $dispatch = {
    'onething'      => sub { print @_; },
    'another_thing' => \&do_it,
};

$dispatch->{'onething'}->("fish");

因为它只是一个哈希,你也可以添加代码引用和匿名子例程。(注意-与面向对象编程有些相似)

结论:解决方法

单一赋值

在纯函数式语言中,任何更改现有值的分配(例如 x := x + 1)都是不允许的。在函数式编程中,分配被弃用,而使用单一分配,也称为初始化。单一赋值是名称绑定的一个示例,并且与本文中描述的分配不同之处在于它只能在变量创建时执行一次;不允许后续重新分配。

我不确定perl是否真正做到了这一点。最接近的近似可能是引用/匿名子例程或者常数。

结论:不支持

惰性求值

等到最后一刻才评估表达式,特别是为了优化可能不使用表达式值的算法。 Perl 5中惰性求值技术的示例 再次回到 Higher Order Perl(我与这本书没有关联,只是它似乎是该主题的关键文本之一)。
核心概念在于 - 在perl中创建一个'链接列表'(使用面向对象技术),但在您的'结束标记'嵌入代码引用,以评估是否到达那里。
结论:解决方法 垃圾回收
“GarbageCollection(GC),也称为自动内存管理,是堆内存的自动回收。”
Perl通过引用计数来实现这一点,当不再引用时释放资源。请注意,在函数式编程中可能会遇到某些问题,特别是循环引用,这在perldoc perlref中有所涉及。
结论:本地支持 类型推断 类型推断是分析程序以推断某些或所有表达式的类型,通常在编译时进行。
Perl隐式地将值转换为需要的格式。通常情况下,这足够好,您不需要对其进行干预。偶尔需要通过显式数字或字符串操作来“强制”该过程。经典的方法是添加0或连接空字符串。
你可以通过使用dualvars来重载标量以执行不同的操作。

结论:原生支持

尾调用优化

尾调用优化(或尾调用合并或尾调用消除)是尾递归的一般化:如果一个函数在返回之前做的最后一件事是调用另一个函数,那么它应该安全地直接跳转到第二个函数的开头,让它重复使用第一个函数的堆栈帧(环境)。

为什么Perl如此害怕“深递归”?

它会工作,但如果递归深度>100,则会发出警告。您可以通过添加以下内容来禁用此功能:

no warnings 'recursion';

但是,显然你需要对递归深度和内存占用略加谨慎。

据我所知,没有任何特定的优化方法,如果你想以高效的方式完成这样的任务,可能需要将递归展开并进行迭代。

Perl支持尾调用。可以查看goto ⊂符号,或者查看由Sub::Call::Tail提供的更简洁的语法。

结论:本地支持

列表推导

列表推导是许多现代函数式编程语言的一个特性。在一定的规则下,它们为生成列表中的元素提供了简洁的表示法。列表推导是concat、map和filter函数的语法糖组合。

Perl有mapgrepreduce

它还能处理范围和重复的扩展:

my @letters = ( "a" .. "z" ); 

所以你可以:

my %letters = map { $_ => 1 } ( "A" .. "z" ); 

结论:本地支持(List::Utils是核心模块)

单子效应

...不行,我还是对这些有困难。它要么比我理解的简单得多,要么比我理解的复杂得多。

如果有人有更多的东西,请加入或编辑此帖子或......什么的。我对涉及的一些概念仍然很模糊,所以这篇文章更像是一个起点。


3
尽管所有这些特征都是函数式编程语言的典型特征,但很少有特征是专门针对函数式语言的。而我认为的定义特征,即应用编程(一切都是函数,不存在副作用),完全缺失。另一个缺失的特征是“eval”(Perl拥有),以及将结构化数据作为代码进行评估的能力(类似Lisp的“eval”,Perl缺乏该功能,其“eval”只能处理未经分析的字符串)。 - reinierpost
1
另一个创新是发明新的语法(例如在Lisp中使用宏可以实现);Perl在这方面走得很远,例如参见IO::All - reinierpost
1
我认为你应该将单次赋值的判断改为不支持。在Perl语言中,没有任何支持来确保变量一旦在运行时被赋值后就永远不会被重新赋值。(想想Java的final或C/C++的const)。我也没有看到真正的解决方法。 - reinierpost
1
关于monads:它们只在您进行纯函数式编程时才有意义,在Perl中这是可能的,但无法强制执行。毫不奇怪,CPAN上有一个(实验性的)模块Data::Monad可以在Perl中提供monads。 - reinierpost
1
PS:在单一赋值方面,Moose支持只读属性和不可变类。因此,您可以使用Moose实现它。 - reinierpost
显示剩余9条评论

4

非常好的话题,我想写一篇标题为“骆驼很实用”的文章。让我贡献一些代码。

Perl 也支持像这样的匿名函数

 sub check_config {
    my ( $class, $obj ) = @_;

    my $separator = ' > ';

    # Build message from class namespace.
    my $message = join $separator, ( split '::', $class );

    # Use provided object $obj or
    # create an instance of class with defaults, provided by configuration.
    my $object = $obj || $class->new;

    # Return a Function.
    return sub {
        my $attribute = shift;

        # Compare attribute with configuration,
        # just to ensure it is read from there.
        is $object->config->{$attribute},

            # Call attribute accessor so it is read from config,
            # and validated by type checking.
            $object->$attribute,

            # Build message with attribute.
            join $separator, ( $message, $attribute );
        }
}

sub check_config_attributes {
    my ( $class, $obj ) = @_;

    return sub {
        my $attributes = shift;
        check_config( $class, $obj )->($_) for (@$attributes);
        }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接