"point free"风格(在函数式编程中)是什么?

134

最近我注意到一个短语——“无参风格”...

首先,有个问题是关于对象/记录"无参风格"的:这里,还有一个问题是关于Haskell中柯里化和“无参风格”的混淆:这里

然后,我在这里发现他们提到了“另一个值得讨论的话题是作者对‘无参风格’的厌恶。”

什么是“无参风格”?能否给出简明的解释?它是否与“自动”柯里化有关?

为了了解我的水平——我一直在学习Scheme,并编写了一个简单的Scheme解释器...我理解什么是“隐式”柯里化,但我不懂任何Haskell或ML。


5
请注意:要了解为什么它被称为_pointfree_,请访问HaskellWiki上的Pointfree / But pointfree has more points! - Petr
7个回答

77

请查看维基百科文章以获取您的定义:

默示编程(point-free programming)是一种编程范式,其中函数定义不包括关于其参数的信息,并使用组合器和函数组合[...]而不是变量。

Haskell示例:

传统方式(您明确指定参数):

sum (x:xs) = x + (sum xs)
sum [] = 0

无参式编程(sum没有显式参数——它只是一个从0开始的带有+的fold函数):

 sum = foldr (+) 0
甚至更简单:您可以将 g(x) = f(x) 改为 g = f
所以,是的:它与柯里化(或函数组合等操作)密切相关。

2
它与柯里化有什么关系? - kaleidic
3
由于没有变量名,你需要组合部分应用的函数。这就是我们所说的柯里化(或更准确地说,通过柯里化实现的内容)。 - Dario
1
你是不是指的是 sum (x:xs) ... 而不是 sum sum (x:xs) ... - Ehtesh Choudhury
你能解释一下为什么我们需要使用括号,而不能简单地使用 sum = foldr + 0 吗? - joelittlejohn
1
@joelittlejohn 因为Haskell会认为你在把foldr0相加,所以中缀函数需要用括号括起来。 - Wezl'

47

点无风格(Point-free style)意味着函数定义时不明确提及函数的参数,并且函数是通过函数组合来定义的。

如果你有两个函数,比如:

square :: a -> a
square x = x*x

inc :: a -> a
inc x = x+1

如果你想将这两个函数合并为一个计算 x*x+1 的函数,你可以像这样定义它:"点-完整"。

f :: a -> a
f x = inc (square x)

点无关(point-free)的替代方案是不讨论参数x

f :: a -> a
f = inc . square

36
愚蠢地,用 Haskell 编写时,“无点”风格通常看起来比“有点”风格更加冗长(点号更多)。这种烦恼成为一种很好的记忆方法。(《Real World Haskell》一书对此进行了评论。) - Dan
4
关于@Dan的评论,Pointfree HaskellWiki页面解释了为什么它被称为“无点式”。 - Vincent Savard
2
@Dan:我不认为这很愚蠢,因为Haskell点应该是“那个圆形运算符”(应该更像°)。但是对于新手来说确实很困惑,尤其是当你刚接触函数式编程语言时;每本介绍Haskell的书都应该解释无点风格。 - Sebastian Mach

20
一个 JavaScript 示例:
//not pointfree cause we receive args
var initials = function(name) {
  return name.split(' ').map(compose(toUpperCase, head)).join('. ');
};

const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];
const join = m => m.join();

//pointfree
var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));

initials("hunter stockton thompson");
// 'H. S. T'

参考资料


1
链接已更改为https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch05.md#pointfree,但我无法使代码运行。 - Qiulang

8

无参风格指的是代码没有明确提到它的参数,尽管它们存在并被使用。

这在Haskell中是可行的,因为函数的工作方式不同于其他编程语言。

例如:

myTake = take

返回一个只需要一个参数的函数,因此除非你想这样做,否则没有理由明确地对参数进行类型定义。


1
有时候,在 Haskell 98 中它不起作用,例如 myShow = show。关于这个问题在 Haskell wiki 上有更多的信息。 - Ehtesh Choudhury

1

我无法使Brunno提供的javascript示例代码正常工作,尽管该代码清楚地说明了点自由(即没有参数)的概念。因此,我使用ramda.js提供另一个示例。

假设我需要找出句子中最长的单词,给定一个字符串"Lorem ipsum dolor sit amet consectetur adipiscing elit",我需要输出类似于{ word: 'consectetur', length: 11 }这样的结果。

如果我使用普通的JS风格代码,我会像这样编写代码,使用map和reduce函数:

let str = 'Lorem ipsum dolor sit amet consectetur adipiscing elit'
let strArray = str.split(' ').map((item) => ({ word: item, length: item.length }))
let longest = strArray.reduce(
    (max, cur) => (cur.length > max.length ? cur : max), 
    strArray[0])
console.log(longest)

使用ramda,我仍然会使用map和reduce,但是我会这样编码
const R = require('ramda')
let longest = R.pipe(
  R.split(' '),
  R.map((item) => ({ word: item, length: item.length })),
  R.reduce((max, cur) => (max.length > cur.length ? max : cur), { length: 0 })
)
let tmp = longest(str)
console.log(tmp)

我认为我的 Ramda 代码的要点是使用管道将函数链接在一起,这使得我的目的更加清晰。不需要创建临时变量 strArray 是一个额外的好处(如果管道中有超过3个步骤,则它将成为真正的好处)。

1

最好的解释在Haskell wiki中(如下所述),由Petr在一条评论中提出

In the declaration

f x = x + 1

we define the function f in terms of its action on an arbitrary point x. Contrast this with the points-free version:

f = (+ 1)

where there is no mention of the value on which the function is acting.

该点是一个数学点(上面有x),因此使用“无点”表示法。如果需要,可以通过链接获取更多详细信息。

0

这里是一个在TypeScript中不使用任何其他库的示例:

interface Transaction {
  amount: number;
}

class Test {
  public getPositiveNumbers(transactions: Transaction[]) {
    return transactions.filter(this.isPositive);

    //return transactions.filter((transaction: {amount: number} => transaction.amount > 0));
  }

  public getBigNumbers(transactions: Transaction[]) {
    // point-free
    return transactions.filter(this.moreThan(10));

    // not point-free
    // return transactions.filter((transaction: any) => transaction.amount > 10);
  }

  private isPositive(transaction: Transaction) {
    return transactions.amount > 0;
  }

  private moreThan(amount: number) {
    return (transaction: Transaction) => {
      return transactions.amount > amount;
    }
  }
}

你可以看到无参考样式更加"流畅"且易于阅读。


这不是无点风格,这只是一个 lambda 函数和命名函数之间的区别。 - kralyk
@kralyk 我认为你没有理解重点,this.moreThan(10) 不是一个命名函数,它是一个柯里化函数,同时也是一个会隐式(因此是无点)以 transaction 作为输入的函数。 - AZ.
虽然使用类作为点式编程的示例并不是最好的选择。 - Andreas Herd

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