สไตล์“ point free” (ใน Functional Programming) คืออะไร?


106

วลีที่ฉันสังเกตเห็นเมื่อเร็ว ๆ นี้คือแนวคิดของสไตล์ "point free" ...

อย่างแรกมีคำถามนี้และคำถามนี้ด้วย

จากนั้นฉันก็ค้นพบที่นี่พวกเขากล่าวถึง "อีกหัวข้อหนึ่งที่อาจมีค่าควรพูดคุยกันคือผู้เขียนไม่ชอบสไตล์ไร้จุดมุ่งหมาย"

สไตล์ "point free" คืออะไร? ใครช่วยให้คำอธิบายสั้น ๆ มันเกี่ยวข้องกับการแกงแบบ "อัตโนมัติ" หรือไม่?

เพื่อให้ทราบถึงระดับของฉัน - ฉันได้สอน Scheme ด้วยตัวเองและได้เขียนล่าม Scheme แบบง่ายๆ ... ฉันเข้าใจว่าการแกง "โดยนัย" คืออะไร แต่ฉันไม่รู้จัก Haskell หรือ ML เลย


3
หมายเหตุ: เพื่อดูว่าทำไมจึงเรียกว่าPointfree visit Pointfree / แต่ pointfree มีคะแนนมากกว่า! ที่ HaskellWiki
Petr

คำตอบ:


66

เพียงดูบทความ Wikipediaเพื่อรับคำจำกัดความของคุณ:

Tacit programming (การเขียนโปรแกรมแบบไม่ใช้จุด) เป็นกระบวนทัศน์การเขียนโปรแกรมที่นิยามของฟังก์ชันไม่รวมข้อมูลเกี่ยวกับอาร์กิวเมนต์โดยใช้ตัวผสมและองค์ประกอบของฟังก์ชัน [... ] แทนตัวแปร

ตัวอย่าง Haskell:

ธรรมดา (คุณระบุอาร์กิวเมนต์อย่างชัดเจน):

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

ไม่มีจุด ( sumไม่มีอาร์กิวเมนต์ที่ชัดเจน - เป็นเพียงการพับโดย+เริ่มต้นด้วย 0):

 sum = foldr (+) 0

หรือง่ายกว่านั้น: g(x) = f(x)คุณสามารถเขียนg = fแทนได้

ใช่: มันเกี่ยวข้องอย่างใกล้ชิดกับการแกง (หรือการดำเนินการเช่นองค์ประกอบของฟังก์ชัน)


8
ฉันเห็นแล้ว! ดังนั้นคุณจึงสร้างฟังก์ชันใหม่ ๆ ได้เสมอเพียงแค่รวมฟังก์ชันอื่น ๆ เข้าด้วยกันแทนที่จะประกาศอาร์กิวเมนต์ ... สง่างามมาก!
Paul Hollingsworth

22
ฉันไม่ชอบที่จะต้องตั้งชื่อใหม่สำหรับตัวแปร / อาร์กิวเมนต์เมื่อฉันเขียนโปรแกรม นั่นเป็นเหตุผลสำคัญประการหนึ่งที่ทำให้ฉันชอบสไตล์ไร้จุด!
Martijn

2
เกี่ยวข้องกับ Currying ในทางใด?
kaleidic

1
@kaleidic: เนื่องจากไม่มีชื่อตัวแปรคุณจึงต้องเขียนฟังก์ชันที่ใช้บางส่วน นั่นคือสิ่งที่เราเรียกว่าแกงกะหรี่ (หรืออย่างแม่นยำยิ่งขึ้นสิ่งที่เกิดขึ้นได้จากการแกง)
ริโอ

1
คุณไม่ได้หมายถึงsum (x:xs) ...แทนที่จะเป็นsum sum (x:xs) ...?
Ehtesh Choudhury

34

รูปแบบไม่มีจุดหมายถึงไม่ได้กล่าวถึงอาร์กิวเมนต์ของฟังก์ชันที่กำหนดไว้อย่างชัดเจนว่าฟังก์ชันถูกกำหนดผ่านองค์ประกอบของฟังก์ชัน

หากคุณมีสองฟังก์ชันเช่น

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

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

และหากคุณต้องการรวมฟังก์ชันทั้งสองนี้เข้าด้วยกันเพื่อคำนวณx*x+1คุณสามารถกำหนดให้เป็น "point-full" ดังนี้:

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

ทางเลือกที่ไม่มีจุดจะไม่พูดถึงข้อโต้แย้งx:

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

22
โง่มากใน Haskell วิธีที่ 'ไม่มีจุด' มักจะเป็นวิธีที่ดูชัดเจนกว่า (ช่วงเวลามากกว่า) ความรำคาญนี้ทำให้ช่วยในการจำได้ดีเยี่ยม (หนังสือ Real World Haskell แสดงความคิดเห็นเกี่ยวกับเรื่องนี้)
Dan

3
เกี่ยวกับการแสดงความคิดเห็น @ แดนที่Pointfreeหน้า HaskellWiki มีคำอธิบายว่าทำไมมันถูกเรียกว่าpointfree
Vincent Savard

2
@ แดน: ฉันไม่คิดว่ามันโง่หรอกนะเพราะจุดที่ Haskell หมายถึง "ตัวดำเนินการวงกลมนั้น" (ควรจะดูเหมือนมากกว่านะ) แต่น่าสับสนโดยเฉพาะอย่างยิ่งเมื่อคุณยังใหม่กับภาษาโปรแกรมที่ใช้งานได้ หนังสือแนะนำตัวทุกเล่มเกี่ยวกับ haskell ควรอธิบายแบบไม่มีจุด
Sebastian Mach

14

ตัวอย่าง 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
ลิงก์เปลี่ยนเป็นgithub.com/MostlyAdequate/mostly-adequate-guide/blob/master/… แต่ฉันไม่สามารถเรียกใช้โค้ดได้
Qiulang

7

Point free style หมายความว่าโค้ดจะไม่กล่าวถึงอาร์กิวเมนต์อย่างชัดเจนแม้ว่าจะมีอยู่จริงและกำลังใช้งานอยู่ก็ตาม

สิ่งนี้ใช้งานได้ใน Haskell เนื่องจากวิธีการทำงานของฟังก์ชัน

ตัวอย่างเช่น:

myTake = take

ส่งคืนฟังก์ชันที่รับอาร์กิวเมนต์เดียวดังนั้นจึงไม่มีเหตุผลที่จะพิมพ์อาร์กิวเมนต์อย่างชัดเจนเว้นแต่คุณต้องการเช่นกัน


1
บางครั้งก็ใช้ไม่ได้ใน Haskell 98 เช่นเดียวกับในmyShow = show. มีข้อมูลเพิ่มเติมในHaskell wiki
Ehtesh Choudhury

0

ฉันไม่สามารถสร้างตัวอย่าง javascript ที่ Brunno ใช้งานได้แม้ว่าโค้ดจะแสดงถึงแนวคิดที่ไม่มีจุดหมาย (เช่นไม่มีข้อโต้แย้ง) อย่างชัดเจน ผมจึงใช้ramda.jsเพื่อเป็นตัวอย่างอื่น

สมมติว่าฉันต้องหาคำที่ยาวที่สุดในประโยคโดยกำหนดสตริงที่"Lorem ipsum dolor sit amet consectetur adipiscing elit"ฉันต้องการผลลัพธ์เช่น{ word: 'consectetur', length: 11 }

ถ้าฉันใช้โค้ดสไตล์ JS ธรรมดาฉันจะเขียนโค้ดแบบนี้โดยใช้แผนที่และฟังก์ชันลดขนาด

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 ฉันยังคงใช้แผนที่ & ลด แต่ฉันจะเขียนโค้ดแบบนี้

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

นี่คือตัวอย่างหนึ่งใน 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;
    }
  }
}

คุณจะเห็นว่ารูปแบบไม่มีจุดคือ "คล่อง" และอ่านง่ายกว่า


นั่นไม่ใช่รูปแบบที่ไม่มีจุด แต่เป็นเพียงความแตกต่างระหว่างแลมด้าและฟังก์ชันที่มีชื่อ
kralyk

@kralyk ฉันคิดว่าคุณพลาดจุดthis.moreThan(10)นี้ไม่ใช่ฟังก์ชันที่มีชื่อมันเป็นฟังก์ชัน curried เช่นเดียวกับฟังก์ชันที่จะใช้transactionเป็นอินพุตโดยปริยาย (ดังนั้นชี้ให้ฟรี)
AZ.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.