ค่า“ ความทรงจำ” ในการโปรแกรมเชิงฟังก์ชัน


20

ฉันตัดสินใจที่จะทำหน้าที่ของการเรียนรู้การเขียนโปรแกรมการทำงาน จนถึงตอนนี้มันช่างระเบิดและฉันก็ 'เห็นแสงสว่าง' เหมือนเดิม น่าเสียดายที่ฉันไม่รู้จักโปรแกรมเมอร์ที่ใช้งานจริงใด ๆ ที่ฉันสามารถตอบคำถามได้ ขอแนะนำ Stack Exchange

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

ฉันตัดสินใจว่าวิธีที่ดีที่สุดในการทำเช่นนี้คือการแสดงฟังก์ชั่นทางคณิตศาสตร์อย่างง่ายเช่นการเพิ่มคุณค่าให้กับพลัง ในทางทฤษฎีฉันสามารถทำได้อย่างง่ายดายด้วยฟังก์ชันที่สร้างไว้ล่วงหน้า แต่นั่นจะเอาชนะวัตถุประสงค์ของตัวอย่างได้

อย่างไรก็ตามฉันมีปัญหาในการหาวิธีการเก็บค่า ตั้งแต่นี่คือการเขียนโปรแกรมการทำงานฉันไม่สามารถเปลี่ยนตัวแปร หากฉันต้องเขียนโค้ดแบบนี้มันจะมีลักษณะดังนี้:

(ต่อไปนี้เป็นรหัสเทียมทั้งหมด)

f(x,y) {
  int z = x;
  for(int i = 0, i < y; i++){
    x = x * z;
  }
  return x;
}

ในการเขียนโปรแกรมการใช้งานฉันไม่แน่ใจ นี่คือสิ่งที่ฉันมาด้วย:

f(x,y,z){
  if z == 'null',
    f(x,y,x);
  else if y > 1,
    f(x*z,y-1,z);
  else
    return x;
}

ถูกต้องหรือไม่ ฉันจำเป็นต้องเก็บค่าzไว้ทั้งสองกรณี แต่ฉันไม่แน่ใจว่าจะทำอย่างไรในการเขียนโปรแกรมฟังก์ชั่น ตามทฤษฎีแล้ววิธีที่ฉันใช้งานได้ แต่ฉันไม่แน่ใจว่ามันถูกหรือไม่ มีวิธีที่ดีกว่าที่จะทำหรือไม่


32
ถ้าคุณต้องการให้ตัวอย่างของคุณจริงจังให้แก้ไขปัญหาเชิงปฏิบัติแทนปัญหาคณิตศาสตร์ มันเป็นเรื่องที่น่าเบื่อในหมู่นักพัฒนาว่า "FP ทั้งหมดนั้นดีสำหรับการแก้ปัญหาคณิตศาสตร์" และหากตัวอย่างของคุณคือ Yet Another Function ทางคณิตศาสตร์คุณเพียง แต่เสริมโครงสร้างของภาพลักษณ์แทนที่จะทำให้สิ่งที่คุณทำมีประโยชน์
Mason Wheeler

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

1
ขอบคุณมากที่ให้การสนับสนุน! ฉันยังใหม่อยู่มากในที่นี้ดังนั้นรหัสปลอมของฉันจึงไม่สมบูรณ์แบบ @MasonWheeler ฉันเดาว่าในกรณีนี้รหัสของฉันไม่ได้มีไว้เพื่อใช้อย่างจริงจัง ฉันยังคงเรียนรู้และเหตุผลที่ฉันรัก FP ก็เพราะมันเป็นคณิตศาสตร์ ตัวอย่างทั้งหมดของฉันคืออธิบายให้ครูฟังว่าทำไมฉันถึงใช้ FP เขาไม่เข้าใจจริงๆว่ามันคืออะไรดังนั้นนี่จึงเป็นวิธีที่ดีในการแสดงให้เขาเห็นถึงข้อดี
Ucenna

5
คุณวางแผนที่จะเขียนรหัสในภาษาใด อย่าพยายามใช้สไตล์ที่ไม่เหมาะกับภาษาที่คุณใช้
Carsten S

2
อาจเป็นประโยชน์: en.wikipedia.org/wiki/Memoization
Ethan Bolker

คำตอบ:


37

ก่อนอื่นขอแสดงความยินดีกับ "การมองเห็นแสง" คุณทำให้โลกของซอฟต์แวร์เป็นสถานที่ที่ดีขึ้นด้วยการขยายขอบเขตของคุณ

ประการที่สองไม่มีทางที่อาจารย์ที่ไม่เข้าใจการเขียนโปรแกรมที่ใช้งานได้จะสามารถพูดสิ่งที่มีประโยชน์เกี่ยวกับรหัสของคุณได้นอกเหนือจากความคิดเห็นที่ซ้ำซากเช่น "การเยื้องดูออก" สิ่งนี้ไม่น่าประหลาดใจในหลักสูตรการพัฒนาเว็บเนื่องจากการพัฒนาเว็บส่วนใหญ่ใช้ HTML / CSS / JavaScript ขึ้นอยู่กับว่าคุณสนใจการเรียนรู้การพัฒนาเว็บมากแค่ไหนคุณอาจต้องใช้ความพยายามในการเรียนรู้เครื่องมือที่อาจารย์ของคุณกำลังสอน (เจ็บปวดแม้ว่ามันอาจจะเป็น - ฉันรู้จากประสบการณ์)

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

(* raises x to the power of y *)
fun pow (x: real) (y: int) : real = 
    if y = 1 then x else x * (pow x (y-1))

โปรดทราบว่าจริงๆแล้วอัลกอริทึมนี้เหมือนกับโค้ดที่จำเป็น ในความเป็นจริงเราสามารถพิจารณาวงด้านบนเป็นน้ำตาล syntactic สำหรับกระบวนการซ้ำซ้ำ

ในฐานะที่เป็นบันทึกย่อด้านข้างไม่จำเป็นต้องมีคุณค่าzในรหัสที่จำเป็นหรือการทำงานของคุณในความเป็นจริง คุณควรเขียนฟังก์ชันที่จำเป็นของคุณเช่น:

def pow(x, y):
    var ret = 1
    for (i = 0; i < y; i++)
         ret = ret * x
    return ret

xแทนที่จะเปลี่ยนความหมายของตัวแปร


การเรียกซ้ำของคุณpowไม่ถูกต้องนัก มันเป็นpow 3 3ผลตอบแทนแทน81 27มันควรจะเป็นelse x * pow x (y-1).
8bittree

3
อ๊ะการเขียนรหัสที่ถูกต้องนั้นยาก :) แก้ไขแล้วและฉันก็เพิ่มคำอธิบายประกอบแบบลงไปด้วย @Ucenna มันควรจะเป็น SML แต่ฉันไม่ได้ใช้มันมาระยะหนึ่งแล้ว มีหลายวิธีที่จะประกาศฟังก์ชั่นยี้เกินไปฉันไม่สามารถจำคำหลักที่เหมาะสมได้ นอกเหนือจากการเปลี่ยนแปลงทางไวยากรณ์รหัสจะเหมือนกันใน JavaScript
Gardenhead

2
@jwg Javascript มีลักษณะการใช้งานบางอย่าง: ฟังก์ชั่นสามารถกำหนดฟังก์ชั่นที่ซ้อนกันฟังก์ชั่นกลับมาและยอมรับฟังก์ชั่นเป็นพารามิเตอร์; รองรับการปิดด้วยขอบเขตคำศัพท์ (ไม่มีขอบเขตแบบไดนามิกกระเพื่อม) มันขึ้นอยู่กับวินัยของโปรแกรมเมอร์ที่จะละเว้นจากการเปลี่ยนแปลงสถานะและการกลายพันธุ์ของข้อมูล
Kasper van den Berg

1
@jwg ไม่มีคำนิยามที่ตกลงกันของภาษา "การทำงาน" (หรือสำหรับ "คำสั่ง", "เชิงวัตถุ" หรือ "การประกาศ") ฉันพยายามหลีกเลี่ยงการใช้คำเหล่านี้ทุกครั้งที่ทำได้ ภายใต้ดวงอาทิตย์มีภาษามากเกินไปที่จะจัดเป็นกลุ่มที่เรียบร้อยสี่กลุ่ม
Gardenhead

1
ความนิยมเป็นตัวชี้วัดที่แย่มากซึ่งเป็นเหตุผลว่าเมื่อใดก็ตามที่มีคนกล่าวถึงภาษาหรือเครื่องมือ X ว่าจะต้องดีกว่าเพราะมันใช้กันอย่างแพร่หลายฉันรู้ว่าการดำเนินการโต้แย้งจะไม่มีจุดหมาย ฉันคุ้นเคยกับตระกูลภาษา ML มากกว่า Haskell เป็นการส่วนตัว แต่ฉันก็ไม่แน่ใจเหมือนกันว่าจริงหรือ; ฉันเดาว่าจะเป็นนักพัฒนาส่วนใหญ่ที่ยังไม่ได้ลอง Haskell ตั้งแต่แรก
Gardenhead

33

นี่เป็นเพียงแค่ภาคผนวกของคำตอบของ Gardenhead แต่ฉันอยากจะชี้ให้เห็นว่ามีชื่อรูปแบบที่คุณเห็นอยู่: การพับ

ในการเขียนโปรแกรมการทำงานfoldเป็นวิธีการรวมชุดของค่าที่ "จดจำ" ค่าระหว่างแต่ละการดำเนินการ ลองเพิ่มรายการตัวเลขอย่างมีเหตุผล:

def sum_all(xs):
  total = 0
  for x in xs:
    total = total + x
  return total

เราใช้รายการของค่าxsและสถานะเริ่มต้นของ0(แสดงโดยtotalในกรณีนี้) จากนั้นสำหรับแต่ละxในxsเรารวมค่านั้นกับสถานะปัจจุบันตามการดำเนินการรวมบางอย่าง(ในกรณีนี้นอกจาก) และใช้ผลลัพธ์เป็นสถานะใหม่ ในสาระสำคัญเทียบเท่ากับsum_all([1, 2, 3]) (3 + (2 + (1 + 0)))รูปแบบนี้สามารถแยกออกเป็นฟังก์ชันลำดับที่สูงกว่าซึ่งเป็นฟังก์ชันที่รับฟังก์ชั่นเป็นอาร์กิวเมนต์:

def fold(items, initial_state, combiner_func):
  state = initial_state
  for item in items:
    state = combiner_func(item, state)
  return state

def sum_all(xs):
  return fold(xs, 0, lambda x y: x + y)

การนำไปปฏิบัติfoldยังคงเป็นสิ่งจำเป็น แต่ก็สามารถทำซ้ำได้เช่นกัน:

def fold_recursive(items, initial_state, combiner_func):
  if not is_empty(items):
    state = combiner_func(initial_state, first_item(items))
    return fold_recursive(rest_items(items), state, combiner_func)
  else:
    return initial_state

ฟังก์ชั่นของคุณคือ:

def exponent(base, power):
  return fold(repeat(base, power), 1, lambda x y: x * y))

... ที่repeat(x, n)ส่งกลับรายการของสำเนาของnx

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


8
เรียนรู้ที่จะหาจุดที่แน่นอนเมื่อปัญหาสามารถแก้ไขได้โดยการพับหรือแผนที่ ใน FP วงวนเกือบทั้งหมดสามารถแสดงเป็นแบบโฟลด์หรือแผนที่ ดังนั้นการเรียกซ้ำแบบชัดแจ้งจึงไม่จำเป็น
Carcigenicate

1
ในบางภาษาคุณสามารถเขียนได้fold(repeat(base, power), 1, *)
user253751

4
Rico Kahler: scanเป็นหลักfoldที่แทนที่จะรวมรายการของค่าเป็นค่าเดียวมันถูกรวมเข้าด้วยกันและค่ากลางแต่ละอันจะคายกลับไปตามทางสร้างรายการของสถานะกลางทั้งหมดที่สร้างขึ้นแทนที่จะสร้างเพียง สถานะสุดท้าย มันสามารถนำไปใช้ในแง่ของfold(ทุกการดำเนินการวนลูปคือ)
แจ็ค

4
@RicoKahler และเท่าที่ฉันสามารถบอกได้การลดและการพับเป็นสิ่งเดียวกัน Haskell ใช้คำว่า "fold" ในขณะที่ Clojure ชอบ "ลด" พฤติกรรมของพวกเขาเหมือนกันกับฉัน
Carcigenicate

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

8

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

var numbers = [1, 2, 3, 4, 5]

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

แผนที่จะนำรายชื่อของบางสิ่งบางอย่างและฟังก์ชั่นและการกลับรายการที่ได้รับการปรับเปลี่ยนโดยใช้ฟังก์ชั่นที่ แต่ละรายการจะถูกส่งผ่านไปยังฟังก์ชั่นและจะกลายเป็นสิ่งที่ฟังก์ชั่นกลับมา

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

function add-two(n):
    return n + 2

var numbers2 =
    map(add-two, numbers) 

หากคุณพิมพ์numbers2คุณจะเห็น[3, 4, 5, 6, 7]ว่ารายการแรกที่มี 2 เพิ่มไปยังแต่ละองค์ประกอบ สังเกตฟังก์ชั่นที่add-twoได้รับmapการใช้งาน

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

function add-together(n1, n2):
    return n1 + n2

var sum =
    fold(add-together, 0, numbers)

หากคุณพิมพ์sumคุณจะเห็นผลรวมของรายการตัวเลข: 15

นี่คือสิ่งที่อาร์กิวเมนต์ที่ต้องfoldทำ:

  1. นี่คือฟังก์ชั่นที่เราให้พับ การพับจะส่งผ่านฟังก์ชันสะสมปัจจุบันและรายการปัจจุบันของรายการ ฟังก์ชันใดก็ตามที่ส่งคืนจะกลายเป็นตัวสะสมใหม่ซึ่งจะถูกส่งผ่านไปยังฟังก์ชันในครั้งต่อไป นี่คือวิธีที่คุณ "จดจำ" ค่าเมื่อคุณวนลูป FP-style ฉันให้ฟังก์ชั่นที่ใช้ 2 ตัวเลขและเพิ่มเข้าไป

  2. นี่คือการสะสมเริ่มต้น; สิ่งที่แอคคูมูเลเตอร์จะเริ่มต้นเหมือนก่อนที่รายการใด ๆ ในรายการจะถูกประมวลผล เมื่อคุณรวมตัวเลขจำนวนรวมก่อนที่คุณจะเพิ่มตัวเลขเข้าด้วยกันคืออะไร 0 ซึ่งฉันผ่านเป็นอาร์กิวเมนต์ที่สอง

  3. สุดท้ายเช่นเดียวกับแผนที่เรายังผ่านรายการหมายเลขเพื่อให้ดำเนินการ

หากการพับยังคงไม่สมเหตุสมผลโปรดพิจารณาสิ่งนี้ เมื่อคุณเขียน:

# Notice I passed the plus operator directly this time, 
#  instead of wrapping it in another function. 
fold(+, 0, numbers)

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

[1, 2, 3, 4, 5]

กลายเป็น:

0 + 1 + 2 + 3 + 4 + 5
^ Note the initial accumulator being added onto the left (for a left fold).

ซึ่งเท่ากับ 15

ใช้ a mapเมื่อคุณต้องการเปลี่ยนหนึ่งรายการเป็นรายการอื่นที่มีความยาวเท่ากัน

ใช้ a foldเมื่อคุณต้องการเปลี่ยนรายการเป็นค่าเดียวเช่นการรวมรายการตัวเลข

ในฐานะที่เป็น @Jorg ชี้ให้เห็นในความคิดเห็นแม้ว่า "ค่าเดียว" ไม่จำเป็นต้องเป็นสิ่งที่ง่ายเหมือนจำนวน; มันอาจเป็นวัตถุใด ๆ ก็ได้รวมถึงรายการหรือสิ่งอันดับ! วิธีที่ฉันมีโฟลเดคลิกจริงสำหรับฉันคือการกำหนดแผนที่ในรูปของการพับ สังเกตว่ารายการเป็นตัวสะสมอย่างไร:

function map(f, list):
    fold(
        function(xs, x): # xs is the list that has been processed so far
            xs.add( f(x) ) # Add returns the list instead of mutating it
        , [] # Before any of the list has been processed, we have an empty list
        , list) 

เมื่อคุณเข้าใจในความจริงแล้วคุณจะรู้ว่าเกือบทุกการวนซ้ำสามารถถูกแทนที่ด้วยการพับหรือแผนที่


1
@Ucenna @Ucenna มีข้อบกพร่องสองสามข้อที่มีรหัสของคุณ (เหมือนที่iไม่เคยถูกกำหนด) แต่ฉันคิดว่าคุณมีความคิดที่ถูกต้อง ปัญหาหนึ่งที่มีตัวอย่างของคุณคือ: function ( x) ถูกส่งผ่านเพียงหนึ่งองค์ประกอบของรายการในแต่ละครั้งไม่ใช่รายการทั้งหมด ครั้งแรกที่xมีการเรียกมันจะผ่านตัวสะสมเริ่มต้นของคุณ ( y) เนื่องจากเป็นอาร์กิวเมนต์แรกและองค์ประกอบแรกเป็นอาร์กิวเมนต์ที่สอง ครั้งต่อไปที่มันทำงานxจะถูกส่งผ่านตัวสะสมใหม่ทางด้านซ้าย (สิ่งใดก็ตามที่xส่งคืนในครั้งแรก) และองค์ประกอบที่สองของรายการเนื่องจากเป็นอาร์กิวเมนต์ที่สอง
Carcigenicate

1
@Ucenna ตอนนี้คุณมีความคิดพื้นฐานแล้วลองดูการใช้งานของแจ็คอีกครั้ง
Carcigenicate

1
@Ucenna: langs ที่แตกต่างกันมีการตั้งค่าที่แตกต่างกันไม่ว่าจะเป็นฟังก์ชั่นที่ให้การพับใช้เวลาสะสมเป็นอาร์กิวเมนต์แรกหรือครั้งที่สองโชคไม่ดี หนึ่งในเหตุผลที่ดีที่จะใช้การดำเนินการสลับเช่นการเพิ่มการสอนการพับ
แจ็ค

3
"ใช้ a foldเมื่อคุณต้องการเปลี่ยนรายการให้เป็นค่าเดียว (เช่นการสรุปรายการของตัวเลข)" - ฉันแค่ต้องการทราบว่า "ค่าเดียว" นี้อาจซับซ้อนโดยพลการ ... รวมถึงรายการ! ที่จริงแล้วfoldเป็นวิธีการทั่วไปของการทำซ้ำมันสามารถทำทุกอย่างที่ทำซ้ำได้ เช่นmapสามารถแสดงผลได้เล็กน้อยที่func map(f, l) = fold((xs, x) => append(xs, f(x)), [], l)นี่ "ค่าเดียว" ที่คำนวณโดยfoldจริง ๆ แล้วเป็นรายการ
Jörg W Mittag

2
... foldอาจจะต้องการที่จะทำอย่างไรกับรายการที่สามารถทำได้ด้วย และไม่จำเป็นต้องเป็นรายการทุกคอลเลกชันที่สามารถแสดงว่าว่าง / ไม่ว่างจะทำ ซึ่งโดยทั่วไปหมายความว่าตัววนซ้ำใด ๆ จะทำ (ฉันเดาว่าการขว้างคำว่า "catamorphism" ในนั้นจะมีมากเกินไปสำหรับการเริ่มต้นของแม้ว่า :-D)
Jörg W Mittag

1

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

ใน Haskell เช่นคุณมีฟังก์ชั่น(^)ในโหมโรงแล้ว

หรือถ้าคุณต้องการทำโปรแกรมมากขึ้น product (replicate y x)

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


1
เพื่อที่จะเชื่อมโยงคำตอบนี้กับเหตุผลอื่น ๆ อย่างมีเหตุผลควรสังเกตว่าproductเป็นเพียงฟังก์ชันทางลัดที่foldมีการคูณเป็นฟังก์ชันและ 1 เป็นอาร์กิวเมนต์เริ่มต้นและreplicateเป็นฟังก์ชันที่สร้างตัววนซ้ำ (หรือรายการ; ด้านบนทั้งสองมีความแตกต่างใน haskell) ที่ให้จำนวนเหมือนกันเอาท์พุท ตอนนี้ควรเข้าใจได้ง่ายว่าการใช้งานนี้ทำอย่างไรกับคำตอบของ @ Jack ด้านบนเพียงแค่ใช้ฟังก์ชั่นรุ่นเดียวกันที่กำหนดไว้ล่วงหน้าของกรณีเดียวกันเพื่อให้กระชับยิ่งขึ้น
Periata Breatta
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.