อะไรคือความแตกต่างระหว่างการโปรแกรมเชิงโพรซีเดอร์และการโปรแกรมเชิงฟังก์ชัน? [ปิด]


247

ฉันได้อ่านบทความ Wikipedia สำหรับทั้งขั้นตอนการเขียนโปรแกรมและการเขียนโปรแกรมเพื่อการใช้งาน แต่ฉันยังสับสนอยู่เล็กน้อย ใครบางคนสามารถต้มมันลงไปที่แกนกลางได้หรือไม่?


วิกิพีเดียหมายความว่า FP เป็นส่วนหนึ่งของ (เช่นอยู่เสมอ) การเขียนโปรแกรมที่เปิดเผย แต่ที่ไม่เป็นความจริงและ conflates อนุกรมวิธานของ IP เทียบกับ DP
Shelby Moore III

คำตอบ:


151

ฟังก์ชั่นภาษา (นึกคิด) ช่วยให้คุณสามารถเขียนฟังก์ชั่นทางคณิตศาสตร์เช่นฟังก์ชั่นที่ใช้เวลาอาร์กิวเมนต์nและส่งกลับค่า หากโปรแกรมถูกดำเนินการฟังก์ชันนี้จะได้รับการประเมินอย่างมีเหตุผลตามต้องการ 1

ในทางกลับกันภาษาขั้นตอนจะดำเนินการตามลำดับขั้นตอน (มีวิธีการแปลงตรรกะตามลำดับเป็นตรรกะการทำงานที่เรียกว่ารูปแบบการส่งต่อ )

ด้วยเหตุนี้โปรแกรมที่ใช้งานได้อย่างแท้จริงจะให้คุณค่าที่เหมือนกันสำหรับอินพุตและลำดับของการประเมินไม่ชัดเจน ซึ่งหมายความว่าค่าที่ไม่แน่นอนเช่นการป้อนข้อมูลของผู้ใช้หรือค่าสุ่มนั้นยากที่จะสร้างแบบจำลองในภาษาที่ใช้งานได้จริง


1ทุกอย่างในคำตอบนี้เป็นลักษณะทั่วไป คุณสมบัตินี้ประเมินการคำนวณเมื่อต้องการผลลัพธ์แทนที่จะเรียงตามลำดับที่เรียกว่าเรียกว่า "ความเกียจคร้าน" ไม่ใช่ทุกภาษาที่ใช้งานได้จริงเป็นคนขี้เกียจในระดับสากล แต่คำอธิบายที่ให้ไว้ที่นี่ให้“ กรอบงานทางจิต” เพื่อคิดเกี่ยวกับรูปแบบการเขียนโปรแกรมที่แตกต่างกันซึ่งไม่ได้แยกประเภทและแตกต่างกัน


9
ค่าที่ไม่แน่นอนเช่นการป้อนข้อมูลผู้ใช้หรือค่าสุ่มยากที่จะสร้างแบบจำลองในภาษาที่ใช้งานได้จริง แต่นี่เป็นปัญหาที่แก้ไขแล้ว ดูพระ
Apocalisp

" ขั้นตอนต่อเนื่องซึ่งโปรแกรมการทำงานจะซ้อนกัน" หมายถึงการจัดให้มีการแยกส่วนของความกังวลโดยเน้นองค์ประกอบของฟังก์ชั่นเช่นการแยกการพึ่งพาระหว่าง subcomputations ของการคำนวณที่กำหนดขึ้น
Shelby Moore III

ดูเหมือนว่าผิด - โพรซีเดอร์สามารถซ้อนกันได้โพรซีเดอร์สามารถมีพารามิเตอร์ได้
Hurda

1
@Hurda ใช่สามารถใช้คำนี้ดีกว่า ประเด็นก็คือการเขียนโปรแกรมเชิงกระบวนงานเกิดขึ้นทีละขั้นตอนตามลำดับที่กำหนดไว้ล่วงหน้าในขณะที่โปรแกรมการทำงานไม่ได้ดำเนินการแบบขั้นตอน ค่อนข้างจะคำนวณค่าเมื่อจำเป็น อย่างไรก็ตามการขาดการตกลงกันโดยทั่วไปตามคำนิยามของคำศัพท์การเขียนโปรแกรมทำให้ภาพรวมดังกล่าวถัดจากไร้ประโยชน์ ฉันแก้ไขคำตอบของฉันในเรื่องนั้น
Konrad Rudolph

97

โดยทั่วไปทั้งสองรูปแบบเหมือนหยินและหยาง หนึ่งถูกจัดระเบียบในขณะที่อื่น ๆ ที่วุ่นวาย มีสถานการณ์ที่เมื่อการตั้งโปรแกรมฟังก์ชั่นเป็นตัวเลือกที่ชัดเจนและสถานการณ์อื่น ๆ คือการเขียนโปรแกรมตามขั้นตอนเป็นตัวเลือกที่ดีกว่า นี่คือสาเหตุที่มีภาษาอย่างน้อยสองภาษาที่เพิ่งออกมาพร้อมกับเวอร์ชันใหม่ซึ่งรวบรวมสไตล์การเขียนโปรแกรมทั้งสองไว้ ( Perl 6และD 2 )

ขั้นตอน:

  • เอาต์พุตของรูทีนไม่ได้มีความสัมพันธ์โดยตรงกับอินพุตเสมอไป
  • ทุกอย่างทำตามลำดับที่เฉพาะเจาะจง
  • การดำเนินการตามปกติอาจมีผลข้างเคียง
  • มีแนวโน้มที่จะเน้นการใช้โซลูชั่นในลักษณะเชิงเส้น

Perl 6

sub factorial ( UInt:D $n is copy ) returns UInt {

  # modify "outside" state
  state $call-count++;
  # in this case it is rather pointless as
  # it can't even be accessed from outside

  my $result = 1;

  loop ( ; $n > 0 ; $n-- ){

    $result *= $n;

  }

  return $result;
}

D 2

int factorial( int n ){

  int result = 1;

  for( ; n > 0 ; n-- ){
    result *= n;
  }

  return result;
}

การทำงาน:

  • มักจะเรียกซ้ำ
  • ส่งคืนเอาต์พุตเดียวกันสำหรับอินพุตที่กำหนดเสมอ
  • ลำดับของการประเมินมักจะไม่ได้กำหนด
  • ต้องไร้สัญชาติ ie ไม่มีการดำเนินการสามารถมีผลข้างเคียง
  • เหมาะสำหรับการใช้งานแบบขนาน
  • มีแนวโน้มที่จะเน้นวิธีการแบ่งและพิชิต
  • อาจมีคุณสมบัติของ Lazy Evaluation

Haskell

(คัดลอกมาจากWikipedia );

fac :: Integer -> Integer

fac 0 = 1
fac n | n > 0 = n * fac (n-1)

หรือในหนึ่งบรรทัด:

fac n = if n > 0 then n * fac (n-1) else 1

Perl 6

proto sub factorial ( UInt:D $n ) returns UInt {*}

multi sub factorial (  0 ) { 1 }
multi sub factorial ( $n ) { $n * samewith $n-1 } # { $n * factorial $n-1 }

D 2

pure int factorial( invariant int n ){
  if( n <= 1 ){
    return 1;
  }else{
    return n * factorial( n-1 );
  }
}

หมายเหตุด้านข้าง:

แฟคทอเรียลเป็นตัวอย่างทั่วไปที่แสดงให้เห็นว่าการสร้างโอเปอเรเตอร์ใหม่ใน Perl 6 นั้นง่ายเพียงใดเช่นเดียวกับที่คุณสร้างรูทีนย่อย คุณลักษณะนี้ฝังอยู่ใน Perl 6 ซึ่งผู้ให้บริการส่วนใหญ่ในการนำ Rakudo ไปใช้จะถูกกำหนดด้วยวิธีนี้ นอกจากนี้ยังช่วยให้คุณเพิ่มผู้สมัครหลายคนของคุณเองไปยังผู้ให้บริการที่มีอยู่

sub postfix:< ! > ( UInt:D $n --> UInt )
  is tighter(&infix:<*>)
  { [*] 2 .. $n }

say 5!; # 120␤

ตัวอย่างนี้ยังแสดงการสร้างช่วง ( 2..$n) และการลดรายการ meta-operator ( [ OPERATOR ] LIST) รวมกับตัวดำเนินการคูณตัวเลข infix ( *)
นอกจากนี้ยังแสดงว่าคุณสามารถใส่--> UIntลายเซ็นแทนreturns UIntหลังจากนั้น

(คุณสามารถหนีโดยเริ่มต้นช่วงด้วย2เพราะทวีคูณ "โอเปอเรเตอร์" จะกลับมา1เมื่อถูกเรียกโดยไม่มีข้อโต้แย้งใด ๆ )


สวัสดีคุณช่วยกรุณายกตัวอย่างสำหรับ 2 ประเด็นดังต่อไปนี้ที่กล่าวถึงสำหรับ "ขั้นตอนการดำเนินการ" พิจารณาตัวอย่างของการใช้แฟกทอเรียลใน Perl 6 1) การส่งออกของรูทีนไม่ได้มีความสัมพันธ์โดยตรงกับอินพุต 2) การดำเนินการตามปกติอาจมีผลข้างเคียง
Naga Kiran

sub postfix:<!> ($n) { [*] 1..$n }
Brad Gilbert

@BradGilbert No operation can have side effects- คุณช่วยอธิบายได้ไหม?
kushalvm

2
อาจเป็นคำตอบที่ดีที่สุดที่ฉันเคยพบ .... และฉันได้ทำการวิจัยบางอย่างเกี่ยวกับประเด็นเหล่านั้น .. ที่ช่วยฉันจริงๆ! :)
Navaneeth

1
@AkashBisariya sub foo( $a, $b ){ ($a,$b).pick }←ไม่คืนค่าผลลัพธ์เดียวกันสำหรับอินพุตเดียวกันเสมอในขณะที่สิ่งต่อไปนี้จะต้องทำsub foo( $a, $b ){ $a + $b }
Brad Gilbert

70

ฉันไม่เคยเห็นคำนิยามนี้ให้ที่อื่น แต่ฉันคิดว่านี่สรุปความแตกต่างที่ได้รับที่นี่ค่อนข้างดี:

ฟังก์ชันการเขียนโปรแกรมมุ่งเน้นไปที่การแสดงออก

การเขียนโปรแกรมตามขั้นตอนจะเน้นไปที่ข้อความ

นิพจน์มีค่า โปรแกรมที่ใช้งานได้คือนิพจน์ที่มีค่าเป็นลำดับของคำสั่งสำหรับคอมพิวเตอร์ที่จะดำเนินการ

คำสั่งไม่มีค่าและปรับเปลี่ยนสถานะของเครื่องแนวคิดบางอย่างแทน

ในภาษาที่ใช้งานได้จริงจะไม่มีข้อความใด ๆ ในแง่ที่ว่าไม่มีทางจัดการกับสถานะ (พวกเขายังอาจมีโครงสร้างประโยคคำว่า "ประโยค" แต่ถ้ามันจัดการกับสถานะฉันจะไม่เรียกมันว่าข้อความในแง่นี้ ) ในภาษาขั้นตอนอย่างหมดจดจะไม่มีการแสดงออกทุกอย่างจะเป็นคำสั่งที่จัดการกับสถานะของเครื่อง

Haskell จะเป็นตัวอย่างของภาษาที่ใช้งานได้อย่างหมดจดเพราะไม่มีวิธีจัดการกับรัฐ รหัสเครื่องจะเป็นตัวอย่างของภาษาขั้นตอนอย่างหมดจดเพราะทุกอย่างในโปรแกรมเป็นคำสั่งที่จัดการกับสถานะของการลงทะเบียนและหน่วยความจำของเครื่อง

ส่วนที่สับสนคือภาษาโปรแกรมส่วนใหญ่มีทั้งสองอย่างการแสดงออกและข้อความซึ่งช่วยให้คุณสามารถผสมกระบวนทัศน์ ภาษาสามารถแบ่งได้เป็นฟังก์ชันที่มีประโยชน์มากกว่าหรือมีขั้นตอนมากขึ้นโดยขึ้นอยู่กับว่าพวกเขาส่งเสริมการใช้ข้อความเทียบกับนิพจน์

ตัวอย่างเช่น C จะทำงานได้มากกว่าภาษาโคบอลเพราะการเรียกใช้ฟังก์ชั่นเป็นการแสดงออกในขณะที่เรียกโปรแกรมย่อยในภาษาโคบอลเป็นคำสั่ง (ที่จัดการกับสถานะของตัวแปรที่แชร์และไม่ส่งคืนค่า) Python จะทำงานได้ดีกว่า C เพราะช่วยให้คุณสามารถแสดงตรรกะตามเงื่อนไขในฐานะนิพจน์โดยใช้การประเมินผลแบบลัดวงจร (ทดสอบ && path1 || path2 ซึ่งตรงกันข้ามกับคำสั่ง if) โครงการจะทำงานได้ดีกว่า Python เพราะทุกอย่างในโครงร่างนั้นเป็นนิพจน์

คุณยังสามารถเขียนในรูปแบบการใช้งานได้ในภาษาที่สนับสนุนกระบวนทัศน์กระบวนงานและในทางกลับกัน มันยากขึ้นและ / หรืออึดอัดใจกว่าที่จะเขียนในกระบวนทัศน์ที่ไม่ได้รับการสนับสนุนจากภาษา


2
คำอธิบายที่ดีที่สุดและรวบรัดที่สุดที่ฉันเคยเห็นบนเว็บไชโย!
tommed

47

ในวิทยาการคอมพิวเตอร์การเขียนโปรแกรมเชิงการทำงานเป็นกระบวนทัศน์การเขียนโปรแกรมที่ปฏิบัติกับการคำนวณเป็นการประเมินฟังก์ชั่นทางคณิตศาสตร์ มันเน้นการประยุกต์ใช้ฟังก์ชั่นในทางตรงกันข้ามกับรูปแบบการเขียนโปรแกรมขั้นตอนที่เน้นการเปลี่ยนแปลงในรัฐ


4
ในขณะที่นี่คือคำอธิบายที่ช่วยฉันได้มากที่สุดฉันยังคงคลุมเครือในแนวคิดของการเขียนโปรแกรมการทำงาน ฉันกำลังมองหารูปแบบของการเขียนโปรแกรมที่ไม่ได้ขึ้นอยู่กับการอ้างอิงวัตถุภายนอกเพื่อให้ทำงานได้ ตัวอย่างเช่นฉันจะไม่ใส่GetUserContext()ในฟังก์ชั่นบริบทของผู้ใช้จะถูกส่งผ่านมานี่คือการเขียนโปรแกรมการทำงานหรือไม่ ขอบคุณล่วงหน้า.
Matt Cashatt

27

การเขียนโปรแกรมฟังก์ชั่น

num = 1 
def function_to_add_one(num):
    num += 1
    return num


function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)

#Final Output: 2

ขั้นตอนการโปรแกรม

num = 1 
def procedure_to_add_one():
    global num
    num += 1
    return num


procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()

#Final Output: 6

function_to_add_one เป็นฟังก์ชั่น

procedure_to_add_one เป็นขั้นตอน

แม้ว่าคุณจะใช้ฟังก์ชั่นห้าครั้งทุกครั้งที่มันกลับมา2

ถ้าคุณเรียกใช้ขั้นตอนที่ห้าครั้งในตอนท้ายของการทำงานที่ห้ามันจะทำให้คุณ6


5
ตัวอย่างนี้ง่ายมากที่จะเข้าใจคำว่า "ไร้สัญชาติ" และ "ข้อมูลที่ไม่เปลี่ยนรูป" ในการเขียนโปรแกรมเชิงปฏิบัติการการอ่านคำจำกัดความและความแตกต่างทั้งหมดที่ระบุไว้ข้างต้นไม่ได้ทำให้ฉันสับสนจนกระทั่งอ่านคำตอบนี้ ขอบคุณ!
maximus

26

ฉันเชื่อว่าการเขียนโปรแกรมตามขั้นตอน / หน้าที่ / วัตถุประสงค์นั้นเกี่ยวกับวิธีจัดการกับปัญหา

สไตล์แรกจะวางแผนทุกอย่างเป็นขั้นตอนและแก้ไขปัญหาโดยการดำเนินการทีละขั้นตอน (ขั้นตอน) ในแต่ละครั้ง ในทางกลับกันการเขียนโปรแกรมเชิงฟังก์ชันจะเน้นวิธีการแบ่งและเอาชนะซึ่งปัญหาแบ่งออกเป็นปัญหาย่อยจากนั้นแต่ละปัญหาย่อยจะได้รับการแก้ไข (การสร้างฟังก์ชันเพื่อแก้ปัญหาย่อยนั้น) และผลลัพธ์จะถูกรวมเข้าด้วยกัน สร้างคำตอบสำหรับปัญหาทั้งหมด สุดท้ายการเขียนโปรแกรมเชิงวัตถุจะเลียนแบบโลกแห่งความจริงด้วยการสร้างโลกจำลองขนาดเล็กภายในคอมพิวเตอร์ที่มีวัตถุจำนวนมากซึ่งแต่ละอันมีลักษณะเฉพาะที่ไม่เหมือนใครและโต้ตอบกับผู้อื่น จากการโต้ตอบเหล่านั้นผลลัพธ์จะออกมา

รูปแบบการเขียนโปรแกรมแต่ละแบบมีข้อดีและจุดอ่อนของตนเอง ดังนั้นการทำบางสิ่งบางอย่างเช่น "การเขียนโปรแกรมบริสุทธิ์" (เช่นขั้นตอนอย่างหมดจด - ไม่มีใครทำสิ่งนี้โดยวิธีการซึ่งเป็นชนิดแปลก - หรือการทำงานอย่างหมดจดหรือวัตถุประสงค์อย่างหมดจด) เป็นเรื่องยากมากถ้าไม่เป็นไปไม่ได้ยกเว้นปัญหาเบื้องต้น ออกแบบมาเพื่อแสดงให้เห็นถึงประโยชน์ของรูปแบบการเขียนโปรแกรม (ดังนั้นเราจึงเรียกผู้ที่ชอบความบริสุทธิ์ "weenie": D)

จากรูปแบบเหล่านั้นเรามีภาษาการเขียนโปรแกรมที่ออกแบบมาเพื่อปรับให้เหมาะสมสำหรับแต่ละสไตล์ ตัวอย่างเช่นการชุมนุมเป็นเรื่องเกี่ยวกับขั้นตอน โอเคภาษาขั้นต้นส่วนใหญ่เป็นขั้นตอนไม่ใช่แค่ Asm เช่น C, Pascal (และ Fortran ฉันได้ยิน) จากนั้นเรามี Java ที่มีชื่อเสียงทั้งหมดในโรงเรียนวัตถุประสงค์ (ที่จริงแล้ว Java และ C # นั้นยังอยู่ในชั้นเรียนที่เรียกว่า "money-oriented" แต่นั่นก็เป็นหัวข้อสำหรับการอภิปรายอื่น) วัตถุประสงค์ก็คือ Smalltalk ในโรงเรียนที่ใช้งานได้เราจะมี "การทำงานที่เกือบจะ" (บางคนคิดว่าพวกเขาไม่บริสุทธิ์) ครอบครัว Lisp และ ML และอีกหลายคน "Haskell, Erlang และอื่น ๆ อีกมากมาย" อย่างไรก็ตามมีหลายภาษาทั่วไปเช่น Perl, Python ทับทิม


13

หากต้องการขยายความคิดเห็นของ Konrad:

ด้วยเหตุนี้โปรแกรมที่ใช้งานได้อย่างแท้จริงจะให้คุณค่าที่เหมือนกันสำหรับอินพุตและลำดับของการประเมินไม่ชัดเจน

ด้วยเหตุนี้รหัสการทำงานโดยทั่วไปจะง่ายต่อการขนาน เนื่องจากมี (โดยทั่วไป) ไม่มีผลข้างเคียงของฟังก์ชั่นและพวกเขา (โดยทั่วไป) เพียงแค่ดำเนินการตามข้อโต้แย้งของพวกเขาปัญหาจำนวนมากที่เกิดขึ้นพร้อมกันหายไป

การเขียนโปรแกรมฟังก์ชั่นยังใช้เมื่อคุณจำเป็นต้องมีความสามารถในการพิสูจน์รหัสของคุณถูกต้อง นี่เป็นเรื่องยากที่จะทำกับการเขียนโปรแกรมตามขั้นตอน (ไม่ใช่เรื่องง่ายกับการใช้งาน แต่ยังง่ายกว่า)

คำเตือน: ฉันไม่ได้ใช้การเขียนโปรแกรมการทำงานในปีและเพิ่งเริ่มมองมันอีกครั้งดังนั้นฉันอาจไม่ถูกต้องที่นี่ :)


12

สิ่งหนึ่งที่ฉันไม่เห็นจริง ๆ เน้นที่นี่คือภาษาการทำงานที่ทันสมัยเช่น Haskell มากขึ้นในฟังก์ชั่นชั้นหนึ่งสำหรับการควบคุมการไหลกว่าการสอบถามซ้ำ คุณไม่จำเป็นต้องกำหนดแฟคทอเรียลแบบเรียกซ้ำใน Haskell ดังที่ได้กล่าวมา ฉันคิดว่าชอบ

fac n = foldr (*) 1 [1..n]

เป็นการสร้างสำนวนที่สมบูรณ์แบบและมีความใกล้ชิดกับการใช้ลูปมากกว่าการใช้การเรียกซ้ำแบบชัดแจ้ง


10

การโปรแกรมเชิงฟังก์ชันนั้นเหมือนกับการโปรแกรมแบบโพรซีเดอร์ซึ่งไม่ได้ใช้ตัวแปรโกลบอล


7

ภาษาเชิงโพรซีเดอร์มีแนวโน้มที่จะติดตามสถานะ (ใช้ตัวแปร) และมีแนวโน้มที่จะดำเนินการตามลำดับขั้นตอน ภาษาที่ใช้งานได้จริงไม่ได้ติดตามสถานะใช้ค่าที่ไม่เปลี่ยนรูปแบบและมีแนวโน้มที่จะดำเนินการเป็นชุดของการอ้างอิง ในหลายกรณีสถานะของ call stack จะเก็บข้อมูลที่จะเทียบเท่ากับสิ่งที่จะถูกเก็บไว้ในตัวแปรสถานะในรหัสขั้นตอน

การเรียกซ้ำเป็นตัวอย่างคลาสสิกของการเขียนโปรแกรมสไตล์การใช้งาน


1
หลังจากอ่านหน้านี้ฉันกำลังคิดถึงสิ่งเดียวกัน -> "การเรียกซ้ำเป็นตัวอย่างคลาสสิกของการเขียนโปรแกรมสไตล์การทำงาน" และคุณก็เคลียร์แล้วขอบคุณตอนนี้ฉันคิดว่าฉันได้รับบางอย่างแล้ว
Mudassir Hussain

6

คอนราดกล่าวว่า:

ด้วยเหตุนี้โปรแกรมที่ใช้งานได้อย่างแท้จริงจะให้คุณค่าที่เหมือนกันสำหรับอินพุตและลำดับของการประเมินไม่ชัดเจน ซึ่งหมายความว่าค่าที่ไม่แน่นอนเช่นการป้อนข้อมูลของผู้ใช้หรือค่าสุ่มนั้นยากที่จะสร้างแบบจำลองในภาษาที่ใช้งานได้จริง

ลำดับของการประเมินในโปรแกรมที่ใช้งานได้จริงอาจยาก (โดยเฉพาะอย่างยิ่งกับความเกียจคร้าน) หรือแม้แต่ไม่สำคัญ แต่ฉันคิดว่าการบอกว่ามันไม่ได้นิยามไว้อย่างชัดเจนทำให้ดูเหมือนคุณไม่สามารถบอกได้ว่าโปรแกรมของคุณกำลังจะไป ไปทำงานเลย!

บางทีคำอธิบายที่ดีกว่าอาจเป็นไปได้ว่าโฟลว์การควบคุมในโปรแกรมการทำงานนั้นขึ้นอยู่กับว่าจำเป็นต้องใช้ค่าของอาร์กิวเมนต์ของฟังก์ชันหรือไม่ สิ่งที่ดีเกี่ยวกับเรื่องนี้ว่าในโปรแกรมที่เขียนอย่างดีสถานะจะชัดเจน: แต่ละฟังก์ชั่นแสดงรายการอินพุตเป็นพารามิเตอร์แทนที่จะเป็นสภาวะโลกโดยพลการ ดังนั้นในระดับบางอย่างมันเป็นเรื่องง่ายที่จะเหตุผลเกี่ยวกับคำสั่งของการประเมินผลที่เกี่ยวกับฟังก์ชั่นได้ตลอดเวลา แต่ละฟังก์ชั่นสามารถเพิกเฉยต่อส่วนที่เหลือของจักรวาลและมุ่งเน้นไปที่สิ่งที่มันต้องทำ เมื่อรวมเข้าด้วยกันฟังก์ชั่นนั้นรับประกันว่าจะทำงานเหมือนกัน [1] เหมือนกับที่มันแยกกัน

... ค่าที่ไม่แน่นอนเช่นการป้อนข้อมูลของผู้ใช้หรือค่าสุ่มยากที่จะสร้างแบบจำลองในภาษาที่ใช้งานได้จริง

วิธีการแก้ปัญหาการป้อนข้อมูลในโปรแกรมที่ทำงานได้อย่างหมดจดคือการฝังภาษาที่จำเป็นในฐานะDSLโดยใช้นามธรรมที่ทรงพลังเพียงพอนามธรรมที่มีประสิทธิภาพเพียงพอในภาษาที่จำเป็น (หรือฟังก์ชันที่ไม่บริสุทธิ์) สิ่งนี้ไม่จำเป็นเพราะคุณสามารถ "โกง" และผ่านสถานะโดยปริยายและลำดับการประเมินนั้นชัดเจน (ไม่ว่าคุณจะชอบหรือไม่ก็ตาม) ด้วยเหตุนี้ "การโกง" และการประเมินผลบังคับของพารามิเตอร์ทั้งหมดสำหรับทุกฟังก์ชั่นในภาษาที่จำเป็น 1) คุณสูญเสียความสามารถในการสร้างกลไกโฟลว์การควบคุมของคุณเอง (โดยไม่มีมาโคร) 2) โค้ดโดยค่าเริ่มต้น3) และการใช้งานบางอย่างเช่นการเลิกทำ (การเดินทางข้ามเวลา) ต้องใช้ความระมัดระวัง (โปรแกรมเมอร์ที่จำเป็นต้องจัดเก็บสูตรสำหรับการคืนค่าเก่า!) ในขณะที่การเขียนโปรแกรมที่ใช้งานได้จริงจะซื้อทุกสิ่งเหล่านี้ให้คุณ ลืม - "ฟรี"

ฉันหวังว่านี่จะไม่ดูเหมือนความกระตือรือร้นฉันแค่ต้องการเพิ่มมุมมอง การเขียนโปรแกรมและการเขียนโปรแกรมจำเป็นโดยเฉพาะอย่างยิ่งกระบวนทัศน์ผสมในภาษาที่มีประสิทธิภาพเช่น C # 3.0 ยังคงเป็นวิธีที่มีประสิทธิภาพโดยสิ้นเชิงที่จะได้รับสิ่งที่ทำและไม่มี bullet

[1] ... ยกเว้นอาจมีการใช้หน่วยความจำด้วยความเคารพ (cf. foldl และ foldl 'ใน Haskell)


5

หากต้องการขยายความคิดเห็นของ Konrad:

และลำดับของการประเมินไม่ชัดเจน

ภาษาที่ใช้งานได้บางภาษามีสิ่งที่เรียกว่า Lazy Evaluation ซึ่งหมายความว่าฟังก์ชั่นจะไม่ถูกดำเนินการจนกว่าจะมีความต้องการค่า จนกว่าจะถึงเวลานั้นฟังก์ชั่นของตัวเองคือสิ่งที่ผ่านไป

ภาษาขั้นตอนคือขั้นตอนที่ 1 ขั้นตอนที่ 2 ขั้นตอนที่ 3 ... ถ้าในขั้นตอนที่ 2 คุณพูดว่าเพิ่ม 2 + 2 มันจะทำอย่างนั้น ในการประเมินผลที่ขี้เกียจคุณจะบอกว่าเพิ่ม 2 + 2 แต่ถ้าไม่เคยใช้ผลลัพธ์ก็จะไม่เพิ่มเข้าไปอีก


4

หากคุณมีโอกาสฉันจะแนะนำและรับสำเนา Lisp / Scheme และทำโครงการบางอย่างในนั้น ส่วนใหญ่ของความคิดที่ได้กลายเป็น bandwagons เมื่อเร็ว ๆ นี้แสดงใน Lisp ทศวรรษที่ผ่านมา: การเขียนโปรแกรมการทำงานต่อเนื่อง (เป็นปิด), การเก็บขยะแม้แต่ XML

นั่นจะเป็นวิธีที่ดีในการเริ่มต้นความคิดปัจจุบันทั้งหมดและอีกสองสามอย่างเช่นการคำนวณเชิงสัญลักษณ์

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


3

@Creighton:

ใน Haskell มีฟังก์ชั่นห้องสมุดชื่อproduct :

prouduct list = foldr 1 (*) list

หรือเพียงแค่:

product = foldr 1 (*)

ดังนั้น "สำนวน" แฟคทอเรียล

fac n = foldr 1 (*)  [1..n]

ก็จะเป็น

fac n = product [1..n]

สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม หากต้องการวิจารณ์หรือขอคำชี้แจงจากผู้แต่งโปรดแสดงความคิดเห็นใต้โพสต์ของพวกเขา
Nick Kitto

ฉันเชื่อว่าสิ่งนี้ถูกโพสต์เมื่อหลายปีก่อนก่อนที่จะมีการเพิ่มระบบความคิดเห็นหากคุณสามารถเชื่อได้: stackoverflow.com/help/badges/30/beta?userid=2543
Jared Updike

2

ขั้นตอนการโปรแกรมมิงหารลำดับของข้อความสั่งและโครงสร้างตามเงื่อนไขเป็นบล็อกแยกที่เรียกว่าโพรซีเดอร์ที่กำหนดพารามิเตอร์เหนืออาร์กิวเมนต์ที่มีค่า (ไม่สามารถใช้งานได้)

ฟังก์ชั่นการเขียนโปรแกรมเหมือนกันยกเว้นว่าฟังก์ชั่นเป็นค่าชั้นหนึ่งดังนั้นพวกเขาสามารถส่งผ่านเป็นข้อโต้แย้งไปยังฟังก์ชั่นอื่น ๆ และกลับมาเป็นผลมาจากการเรียกใช้ฟังก์ชั่น

โปรดทราบว่าการโปรแกรมเชิงฟังก์ชันนั้นเป็นลักษณะทั่วไปของการโปรแกรมเชิงโพรซีเดอร์ในการตีความนี้ อย่างไรก็ตามชนกลุ่มน้อยตีความ "ฟังก์ชั่นการเขียนโปรแกรม" เพื่อหมายถึงผลข้างเคียงที่ไม่มีความแตกต่างกันมาก แต่ไม่เกี่ยวข้องกับภาษาหลักทั้งหมดยกเว้น Haskell


1

การทำความเข้าใจความแตกต่างหนึ่งต้องเข้าใจว่า "เจ้าพ่อ" กระบวนทัศน์ของทั้งสองขั้นตอนการเขียนโปรแกรมและการทำงานเป็นคำสั่งโปรแกรม

การเขียนโปรแกรมตามขั้นตอนพื้นฐานเป็นเพียงวิธีการจัดโครงสร้างโปรแกรมที่จำเป็นซึ่งวิธีการหลักของนามธรรมเป็น "ขั้นตอน" (หรือ "ฟังก์ชั่น" ในภาษาการเขียนโปรแกรมบางภาษา) แม้แต่การเขียนโปรแกรมเชิงวัตถุก็เป็นอีกวิธีหนึ่งในการสร้างโปรแกรมที่จำเป็นซึ่งสถานะถูกห่อหุ้มในวัตถุกลายเป็นวัตถุด้วย "สถานะปัจจุบัน" รวมทั้งวัตถุนี้มีชุดของฟังก์ชั่นวิธีการและสิ่งอื่น ๆ ที่ช่วยให้คุณ โปรแกรมเมอร์จัดการหรือปรับปรุงสถานะ

ตอนนี้เกี่ยวกับการเขียนโปรแกรมฟังก์ชั่นส่วนสำคัญในวิธีการของมันคือมันระบุค่าที่จะใช้และวิธีการถ่ายโอนค่าเหล่านี้ (ดังนั้นจึงไม่มีสถานะและไม่มีข้อมูลที่ไม่แน่นอนในขณะที่ใช้ฟังก์ชั่นเป็นค่าระดับเฟิร์สคลาสและส่งผ่านเป็นพารามิเตอร์ไปยังฟังก์ชั่นอื่น ๆ )

PS: การทำความเข้าใจกับทุกกระบวนทัศน์การเขียนโปรแกรมที่ใช้ควรชี้แจงความแตกต่างระหว่างพวกเขาทั้งหมด

PSS: ในตอนท้ายของวันกระบวนทัศน์การเขียนโปรแกรมเป็นเพียงวิธีการที่แตกต่างกันในการแก้ปัญหา

PSS: คำตอบ quora นี้มีคำอธิบายที่ดี


0

ไม่มีคำตอบที่นี่แสดงการเขียนโปรแกรมฟังก์ชั่นสำนวน คำตอบแบบแฟคทอเรียลแบบเรียกซ้ำนั้นยอดเยี่ยมสำหรับการแสดงแบบเรียกซ้ำใน FP แต่โค้ดส่วนใหญ่ไม่ได้เรียกซ้ำดังนั้นฉันจึงไม่คิดว่าคำตอบนั้นเป็นตัวแทนอย่างสมบูรณ์

สมมติว่าคุณมีอาร์เรย์ของสตริงและแต่ละสตริงแทนจำนวนเต็มเช่น "5" หรือ "-200" คุณต้องการตรวจสอบอาร์เรย์อินพุตของสตริงกับกรณีทดสอบภายในของคุณ (ใช้การเปรียบเทียบจำนวนเต็ม) โซลูชันทั้งสองแสดงอยู่ด้านล่าง

ขั้นตอน

arr_equal(a : [Int], b : [Str]) -> Bool {
    if(a.len != b.len) {
        return false;
    }

    bool ret = true;
    for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
        int a_int = a[i];
        int b_int = parseInt(b[i]);
        ret &= a_int == b_int;  
    }
    return ret;
}

การทำงาน

eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization

arr_equal(a : [Int], b : [Str]) -> Bool =
    zip(a, b.map(toInt)) # Combines into [Int, Int]
   .map(eq)
   .reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value

ในขณะที่ภาษาที่ใช้งานได้จริงเป็นภาษาที่ใช้ในการวิจัย (ในขณะที่โลกแห่งความเป็นจริงชอบผลข้างเคียงฟรี) ภาษาขั้นตอนในโลกแห่งความเป็นจริงจะใช้ไวยากรณ์การทำงานที่ง่ายกว่ามากเมื่อเหมาะสม

นี้มักจะนำมาใช้กับห้องสมุดภายนอกเช่นLodashหรือที่มีอยู่ในตัวกับภาษาใหม่เช่นสนิม ยกของหนักของการเขียนโปรแกรมการทำงานจะทำด้วยฟังก์ชั่น / แนวคิดเช่นmap, filter, reduce, currying,partial , ครั้งที่สามของการที่คุณสามารถมองขึ้นสำหรับการทำความเข้าใจต่อไป

ภาคผนวก

เพื่อที่จะใช้ในไวลด์คอมไพเลอร์โดยปกติจะต้องหาวิธีแปลงเวอร์ชันการทำงานเป็นเวอร์ชันโพรซีเดอร์ภายในเนื่องจากการเรียกใช้ฟังก์ชันสูงเกินไป กรณีแบบเรียกซ้ำเช่นแฟคทอเรียลที่แสดงจะใช้ลูกเล่นเช่นการโทรหางเพื่อลบการใช้หน่วยความจำ O (n) ความจริงที่ว่าไม่มีผลข้างเคียงช่วยให้คอมไพเลอร์ทำงานเพื่อใช้การปรับให้&& retเหมาะสมแม้ว่า.reduceจะดำเนินการเสร็จสิ้นแล้วก็ตาม การใช้ Lodash ใน JS เห็นได้ชัดว่าไม่อนุญาตให้มีการปรับให้เหมาะสมดังนั้นจึงเป็นที่นิยมสำหรับประสิทธิภาพ (ซึ่งมักไม่เกี่ยวข้องกับการพัฒนาเว็บ) ภาษาเช่น Rust จะปรับให้เหมาะสมภายใน (และมีฟังก์ชั่นเช่นtry_foldเพื่อช่วย&& retเพิ่มประสิทธิภาพ)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.