ฟังก์ชั่นเวลามีอยู่ในการตั้งโปรแกรมการทำงานอย่างไร


646

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

ตัวอย่างเช่นพิจารณาสิ่งนี้:

f(x,y) = x*x + y; // It is a mathematical function

ไม่ว่าคุณจะใช้ไปกี่ครั้งf(10,4)ก็ตามคุณค่าของมันจะยังคงอยู่ตลอด104ไป ดังนั้นทุกที่ที่คุณเขียนf(10,4)คุณสามารถแทนที่ด้วย104โดยไม่ต้องเปลี่ยนค่าของการแสดงออกทั้งหมด คุณสมบัตินี้เรียกว่าการอ้างอิงโปร่งใสของการแสดงออก

ตามที่ Wikipedia พูดว่า ( ลิงค์ )

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

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

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

  • หรือถ้าไม่, แล้วเราจะรู้เวลาปัจจุบันในการโปรแกรมเชิงฟังก์ชันได้อย่างไร?


15
ฉันคิดว่าภาษาการทำงานส่วนใหญ่ (หรือทั้งหมด) นั้นไม่ค่อยเข้มงวดนักและรวมการเขียนโปรแกรมที่ใช้งานได้และจำเป็นเข้าด้วยกัน อย่างน้อยนี่คือความประทับใจของฉันจาก F #
Alex F

13
@ Adam: ผู้โทรจะรู้เวลาปัจจุบันได้อย่างไรในตอนแรก
นาวาซ

29
@ อดัม: จริงๆแล้วมันผิดกฎหมาย (ใน: เป็นไปไม่ได้) ในภาษาที่ใช้งานได้หมดจด
sepp2k

47
@Adam: ค่อนข้างมาก ภาษาวัตถุประสงค์ทั่วไปที่บริสุทธิ์มักจะมีสิ่งอำนวยความสะดวกบางอย่างที่จะได้รับ "รัฐโลก" (เช่นสิ่งต่าง ๆ เช่นเวลาปัจจุบันไฟล์ในไดเรกทอรี ฯลฯ ) โดยไม่ทำลายความโปร่งใสในการอ้างอิง ใน Haskell นั่นคือ IO monad และใน Clean มันเป็นโลกประเภท ดังนั้นในภาษาเหล่านั้นฟังก์ชั่นที่ต้องการเวลาปัจจุบันอาจใช้เป็นอาร์กิวเมนต์หรือจะต้องส่งคืนการกระทำ IO แทนผลลัพธ์จริง (Haskell) หรือใช้สถานะโลกเป็นอาร์กิวเมนต์ (Clean)
sepp2k

12
เมื่อคิดถึง FP มันง่ายที่จะลืม: คอมพิวเตอร์เป็นสถานะที่ไม่แน่นอน FP ไม่ได้เปลี่ยนแปลงสิ่งนั้น แต่ปกปิดเพียงอย่างเดียว
Daniel

คำตอบ:


176

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

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

ทีนี้คุณจะพิมพ์เวลาปัจจุบันไปยังคอนโซลได้อย่างไร? คุณต้องรวมการกระทำสองอย่างเข้าด้วยกัน แล้วเราจะทำอย่างไร เราไม่สามารถผ่านgetClockTimeไปprintได้เนื่องจากการพิมพ์คาดว่าจะมีการประทับเวลาไม่ใช่การกระทำ แต่เราสามารถจินตนาการได้ว่ามีโอเปอเรเตอร์>>=ซึ่งรวมสองแอคชั่นหนึ่งอันที่ได้รับการประทับเวลาและอีกอันหนึ่งที่ใช้เวลาหนึ่งเป็นอาร์กิวเมนต์และพิมพ์มัน การนำสิ่งนี้ไปใช้กับการกระทำที่กล่าวถึงก่อนหน้าผลลัพธ์คือ ... tadaaa ... การกระทำใหม่ที่ได้รับเวลาปัจจุบันและพิมพ์ออกมา และนี่คือสิ่งที่บังเอิญทำใน Haskell

Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

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

ฉันไม่รู้ว่านี่เป็นคำอธิบายที่ชัดเจนกว่าคำอธิบายอื่น ๆ หรือไม่ แต่บางครั้งมันก็ช่วยให้ฉันคิดได้ด้วยวิธีนี้


33
มันไม่น่าเชื่อสำหรับฉัน คุณเรียกgetClockTimeการกระทำที่สะดวกแทนฟังก์ชั่น ถ้าคุณเรียกเช่นนั้นเรียกการกระทำทุกฟังก์ชั่นแล้วแม้แต่การเขียนโปรแกรมที่จำเป็นก็จะกลายเป็นการเขียนโปรแกรมการทำงาน หรือบางทีคุณต้องการที่จะเรียกมันว่าactionalโปรแกรมต่างๆ
นาวาซ

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

36
ไม่ใช่ทุกสิ่งใน Haskell เป็นฟังก์ชั่น - นั่นไร้สาระที่สุด ฟังก์ชั่นเป็นสิ่งที่ประเภทมี->- นั่นคือวิธีที่มาตรฐานกำหนดคำศัพท์และนั่นคือคำจำกัดความที่สมเหตุสมผลเพียงอย่างเดียวในบริบทของ Haskell ดังนั้นบางสิ่งบางอย่างที่มีชนิดที่IO Whateverเป็นไม่ได้ฟังก์ชั่น
sepp2k

9
@ sepp2k ดังนั้น myList :: [a -> b] เป็นฟังก์ชันหรือไม่ ;)
fuz

8
@ThomasEding ฉันไปงานปาร์ตี้ช้ามาก แต่ฉันแค่ต้องการชี้แจงเรื่องนี้: putStrLnไม่ใช่การกระทำ - มันเป็นฟังก์ชั่นที่คืนค่าการกระทำ getLineเป็นตัวแปรที่มีการกระทำ การกระทำคือค่าตัวแปรและฟังก์ชั่นคือ "ภาชนะบรรจุ" / "ป้ายกำกับ" ที่เราให้การกระทำเหล่านั้น
kqr

356

ใช่และไม่.

ภาษาโปรแกรมการทำงานที่แตกต่างกันแก้ปัญหาต่างกัน

ใน Haskell (บริสุทธิ์มาก) ทุกสิ่งนี้จะเกิดขึ้นได้ในสิ่งที่เรียกว่าI / O Monad - ดูที่นี่

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

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

ดังที่ Jeffrey Burka พูดถึงในความคิดเห็นของเขา: นี่คือการแนะนำที่ดีสำหรับ I / O Monadตรงจาก Haskell wiki


223
สิ่งสำคัญที่ต้องตระหนักถึงเกี่ยวกับ IO monad ใน Haskell คือมันไม่ใช่แค่การแฮ็คเพื่อแก้ไขปัญหานี้ Monads เป็นวิธีการแก้ปัญหาทั่วไปในการกำหนดลำดับของการกระทำในบางบริบท บริบทหนึ่งที่เป็นไปได้คือโลกแห่งความจริงซึ่งเรามี IO monad บริบทอื่นอยู่ภายในธุรกรรมอะตอมซึ่งเรามี STM monad อีกบริบทหนึ่งคือการใช้อัลกอริธึมขั้นตอน (เช่น Knuth shuffle) เป็นฟังก์ชั่นบริสุทธิ์ซึ่งเรามี ST monad และคุณสามารถกำหนดพระของคุณเองได้เช่นกัน Monads เป็นเซมิโคลอนที่มากเกินไป
พอลจอห์นสัน

2
ฉันพบว่ามีประโยชน์ที่จะไม่เรียกสิ่งต่าง ๆ เช่นการรับ "ฟังก์ชั่น" เวลาปัจจุบัน แต่บางอย่างเช่น "ขั้นตอน" (แม้ว่าการพิสูจน์ได้ว่าโซลูชัน Haskell เป็นข้อยกเว้นนี้)
singpolyma

จาก "ขั้นตอน" แบบคลาสสิกของ Haskell ในมุมมอง (สิ่งที่มีประเภทเช่น '... -> ()') นั้นค่อนข้างเล็กน้อยในฐานะที่เป็นฟังก์ชั่นที่บริสุทธิ์ด้วย ... -> () ไม่สามารถทำอะไรได้เลย
Carsten

3
คำว่า Haskell โดยทั่วไปคือ "การกระทำ"
Sebastian Redl

6
"Monads เป็นอัฒภาคมากเกินไป" +1
2805751

147

ใน Haskell เราใช้โครงสร้างที่เรียกว่าmonadเพื่อจัดการกับผลข้างเคียง โดยทั่วไปแล้ว Monad หมายความว่าคุณใส่ค่าลงในคอนเทนเนอร์และมีฟังก์ชั่นบางอย่างเพื่อเชื่อมโยงฟังก์ชันจากค่าไปยังค่าภายในคอนเทนเนอร์ หากคอนเทนเนอร์ของเรามีประเภท:

data IO a = IO (RealWorld -> (a,RealWorld))

เราสามารถใช้การกระทำของ IO ได้อย่างปลอดภัย ประเภทนี้หมายถึง: การกระทำของประเภทIOเป็นฟังก์ชั่นที่ใช้โทเค็นของประเภทRealWorldและส่งกลับโทเค็นใหม่พร้อมกับผล

คิดที่อยู่เบื้องหลังนี้คือการที่แต่ละ IO RealWorldดำเนินการแปรรูปรัฐนอกแสดงโดยโทเค็นที่มีมนต์ขลัง ด้วยการใช้ monads เราสามารถโยงฟังก์ชั่นหลาย ๆ อย่างที่กลายเป็นโลกแห่งความจริงเข้าด้วยกัน หน้าที่ที่สำคัญที่สุดของ monad คือการผูกมัดที่>>=เด่นชัด:

(>>=) :: IO a -> (a -> IO b) -> IO b

>>=ใช้เวลาหนึ่งการกระทำและฟังก์ชั่นที่ใช้ผลของการกระทำนี้และสร้างการกระทำใหม่ออกมาจากนี้ ชนิดส่งคืนคือการดำเนินการใหม่ ตัวอย่างเช่นสมมติว่ามีฟังก์ชั่นnow :: IO Stringซึ่งจะส่งกลับสตริงที่แสดงเวลาปัจจุบัน เราสามารถโยงมันเข้ากับฟังก์ชั่นputStrLnเพื่อพิมพ์:

now >>= putStrLn

หรือเขียนเป็นdo-Notation ซึ่งเป็นที่คุ้นเคยกับโปรแกรมเมอร์ที่จำเป็น:

do currTime <- now
   putStrLn currTime

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


3
-1: ฉันไม่มีความสุขกับRealWorldหน้าจอควัน แต่สิ่งที่สำคัญที่สุดคือการส่งผ่านวัตถุที่อ้างถึงนี้ในห่วงโซ่ ชิ้นส่วนที่หายไปคือตำแหน่งที่เริ่มต้นโดยที่แหล่งกำเนิดหรือการเชื่อมต่อกับโลกแห่งความเป็นจริง - มันเริ่มต้นด้วยฟังก์ชั่นหลักที่ทำงานใน IO monad
u0b34a0f6ae

2
@ kaizer.se คุณสามารถนึกถึงRealWorldวัตถุทั่วโลกที่ถูกส่งเข้าสู่โปรแกรมเมื่อเริ่มต้น
fuz

6
โดยทั่วไปmainฟังก์ชั่นของคุณจะRealWorldโต้แย้ง มันจะผ่านการประหารชีวิตเท่านั้น
Louis Wasserman

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

2
RealWorld -> (a, RealWorld) ไม่แยกย่อยเป็นคำอุปมาแม้ภายใต้ภาวะพร้อมกันตราบใดที่คุณจำไว้ว่าโลกแห่งความจริงอาจถูกเปลี่ยนแปลงโดยส่วนอื่น ๆ ของจักรวาลนอกฟังก์ชั่นของคุณ (หรือกระบวนการปัจจุบันของคุณ) ตลอดเวลา ดังนั้น (a) methaphor จะไม่พังทลายและ (b) ทุกครั้งที่ค่าที่มีRealWorldตามประเภทนั้นถูกส่งไปยังฟังก์ชันฟังก์ชันจะต้องถูกประเมินอีกครั้งเพราะโลกแห่งความจริงจะมีการเปลี่ยนแปลงในเวลาเดียวกัน ( ซึ่งมีรูปแบบตามที่ @fuz อธิบายให้คืนค่า 'token value' ที่แตกต่างกันทุกครั้งที่เราโต้ตอบกับโลกแห่งความเป็นจริง)
Qqwy

73

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

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

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


2
นี่ทำให้มันฟังดูราวกับว่า Haskell and Clean ทำสิ่งที่แตกต่าง จากสิ่งที่ฉันเข้าใจพวกเขาทำสิ่งเดียวกัน Haskell เสนอไวยากรณ์ที่ดีกว่า (?) เพื่อให้บรรลุสิ่งนี้
Konrad Rudolph

27
@ Konrad: พวกเขาทำสิ่งเดียวกันในแง่ที่ว่าทั้งสองใช้คุณลักษณะของระบบพิมพ์กับผลข้างเคียงที่เป็นนามธรรม แต่ก็เกี่ยวกับมัน โปรดทราบว่ามันเป็นการดีมากที่จะอธิบาย IO monad ในแง่ของประเภทของโลก แต่มาตรฐาน Haskell ไม่ได้กำหนดประเภทของโลกและเป็นไปไม่ได้ที่จะได้รับคุณค่าของประเภทโลกใน Haskell (ในขณะที่มันเป็นไปได้และแน่นอน จำเป็นในการทำความสะอาด) Haskell เพิ่มเติมไม่มีการพิมพ์ที่ไม่เหมือนใครเป็นคุณลักษณะของระบบการพิมพ์ดังนั้นหากคุณให้การเข้าถึงโลกมันไม่สามารถรับประกันได้ว่าคุณใช้มันในแบบที่บริสุทธิ์อย่างที่ Clean ทำ
sepp2k

51

"เวลาปัจจุบัน" ไม่ใช่ฟังก์ชั่น มันเป็นพารามิเตอร์ หากรหัสของคุณขึ้นอยู่กับเวลาปัจจุบันแสดงว่ารหัสของคุณถูกกำหนดพารามิเตอร์ตามเวลา


22

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

ใน C # คุณสามารถใช้มันเช่นนี้:

// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
    public static readonly ClockStamp ProgramStartTime = new ClockStamp();
    public readonly DateTime Time;
    private ClockStamp _next;

    private ClockStamp() {
        this.Time = DateTime.Now;
    }
    public ClockStamp NextMeasurement() {
        if (this._next == null) this._next = new ClockStamp();
        return this._next;
    }
}

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

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

// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
    public readonly ClockStamp Time;
    public readonly T Value;
    public TimeStampedValue(ClockStamp time, T value) {
        this.Time = time;
        this.Value = value;
    }
}

// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
    var start = lastMeasurement.NextMeasurement();
    for (var i = 0; i < 10000000; i++) {
    }
    var end = start.NextMeasurement();
    var duration = end.Time - start.Time;
    return new TimeStampedValue<TimeSpan>(end, duration);
}

public static void Main(String[] args) {
    var clock = ClockStamp.ProgramStartTime;
    var r = TimeALoop(clock);
    var duration = r.Value; //the result
    clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}

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


สิ่งที่น่าสนใจ แต่i++ใน for for loop นั้นไม่มีความโปร่งใสในการอ้างอิง)
snim2

@ snim2 ฉันไม่สมบูรณ์แบบ : P รับความปลอบใจจากข้อเท็จจริงที่ว่าความไม่แน่นอนที่สกปรกนั้นไม่มีผลกระทบต่อความโปร่งใสในการอ้างอิงของผลลัพธ์ หากคุณผ่าน 'lastMeasurement' ที่เหมือนกันในสองครั้งคุณจะได้รับการวัดถัดไปค้างและกลับผลลัพธ์เดียวกัน
Craig Gidney

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

ในความเป็นจริงคุณสามารถไปทาง monad ใน C # เช่นกันดังนั้นหลีกเลี่ยงการผ่านเวลาที่ชัดเจน struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }คุณจำเป็นต้องมีสิ่งที่ต้องการ แต่โค้ดของสิ่งนี้ยังคงดูไม่ดีเท่า Haskell ที่มีdoไวยากรณ์
leftaroundabout

@leftaroundabout คุณสามารถเรียงลำดับของการแกล้งทำเป็นว่าคุณมี monad ใน C # ได้โดยการใช้ฟังก์ชั่นการผูกเป็นวิธีการที่เรียกว่าSelectManyซึ่งเปิดใช้งานไวยากรณ์ของความเข้าใจแบบสอบถาม คุณยังคงไม่สามารถเขียนโปรแกรม polymorphically บน monads ได้ดังนั้นมันจึงเป็นการต่อสู้ที่ยากเย็นแสนเข็ญกับระบบแบบอ่อนแอ :(
sara

16

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

นี่เป็นหัวข้อยอดนิยมใน Reactive Functional Programming หากคุณสนใจเนื้อหาประเภทนี้อ่านสิ่งนี้: http://digitalcommons.ohsu.edu/csetech/91/ (28 หน้า)


3
และเกี่ยวข้องกับคำถามนี้อย่างไร
Nawaz

5
คำถามของคุณเกี่ยวกับการสร้างแบบจำลองพฤติกรรมขึ้นอยู่กับเวลาในการทำงานอย่างหมดจดเช่นฟังก์ชั่นที่ส่งกลับนาฬิการะบบปัจจุบัน คุณสามารถเธรดบางสิ่งที่เทียบเท่ากับ IO monad ผ่านฟังก์ชันทั้งหมดและแผนผังการพึ่งพาเพื่อรับการเข้าถึงสถานะนั้น หรือคุณสามารถจำลองสถานะโดยการกำหนดกฎการสังเกตมากกว่ากฎที่สร้างสรรค์ นี่คือเหตุผลที่การสร้างแบบจำลองของรัฐที่ซับซ้อนinductivelyในการเขียนโปรแกรมการทำงานดูเหมือนแปลกประหลาดเช่นนั้นเพราะรัฐที่ซ่อนอยู่คือจริงๆcoinductiveคุณสมบัติ
Jeffrey Aguilera

เยี่ยมมาก! มีอะไรใหม่อีกบ้างไหม? ดูเหมือนว่าชุมชน JS ยังคงดิ้นรนกับรูปแบบข้อมูลสตรีม
Dmitri Zaitsev

12

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


11

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

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


11

ใช่ฟังก์ชั่นการหาเวลาสามารถมีอยู่ในฟังก์ชั่นการเขียนโปรแกรมโดยใช้รุ่นที่แก้ไขเล็กน้อยในการเขียนโปรแกรมฟังก์ชั่นที่เรียกว่าการเขียนโปรแกรมฟังก์ชั่นที่ไม่บริสุทธิ์ (เริ่มต้นหรือหลัก

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

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

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


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

1
@ Ankur: นั่นคือสิ่งเดียวกัน หากโปรแกรมโต้ตอบกับสิ่งอื่นนอกเหนือจากตัวมันเอง (เช่นโลกผ่านแป้นพิมพ์พวกเขาพูดได้) มันก็ยังไม่บริสุทธิ์
เอกลักษณ์

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

2
การมี "วัตถุของโลก" รวมถึงจำนวนผู้คนที่อาศัยอยู่ในโลกนั้นยกคอมพิวเตอร์ที่ปฏิบัติการให้อยู่ในระดับใกล้กับรอบสัพเพเหระ ฉันคิดว่ากรณีปกติคือมันรวมถึงสิ่งต่าง ๆ เช่นจำนวนไฟล์ที่อยู่ใน HD ของคุณและสิ่งที่ไดเรกทอรีบ้านของผู้ใช้ปัจจุบัน
ziggystar

4
@ziggystar - "วัตถุของโลก" ไม่ได้รวมอะไรเลย - มันเป็นเพียงแค่พร็อกซีสำหรับการเปลี่ยนแปลงของโลกภายนอกโปรแกรม วัตถุประสงค์เพียงอย่างเดียวคือการทำเครื่องหมายสถานะที่ไม่แน่นอนอย่างชัดเจนในวิธีที่ระบบประเภทสามารถระบุได้
Kris Nuttycombe

7

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

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

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

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

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

-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
    putStrLn "Please enter the input parameter"
    inputStr <- readLine
    putStrLn "Starting time:"
    getCurrentTime >>= print
    let inputInt = read inputStr    -- this line is pure
    let result = fib inputInt       -- this is also pure
    putStrLn "Result:"
    print result
    putStrLn "Ending time:"
    getCurrentTime >>= print

4
มันจะมีประโยชน์หากคุณสามารถแก้ไขปัญหาเฉพาะของการรับเวลาและอธิบายเพียงเล็กน้อยเกี่ยวกับสิ่งที่เราพิจารณาIOคุณค่าและผลลัพธ์ที่บริสุทธิ์
AndrewC

ในความเป็นจริงแล้วโปรแกรมแท้ 100% ก็ทำให้ซีพียูร้อนขึ้นซึ่งเป็นผลข้างเคียง
Jörg W Mittag

3

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

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

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

ตัวอย่าง: clock_t c = time(NULL); printf("%d\n", c + 2);ใน C, vs. main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)ใน Haskell ผู้ประกอบการ>>=จะใช้ในการเขียนการกระทำผ่านผลการแรกที่ฟังก์ชั่นผลในการดำเนินการที่สอง สิ่งนี้ดูค่อนข้างลึกลับคอมไพเลอร์ Haskell สนับสนุนน้ำตาลประโยคที่ช่วยให้เราสามารถเขียนรหัสหลังดังนี้

type Clock = Integer -- To make it more similar to the C code

-- An action that returns nothing, but might do something
main :: IO ()
main = do
    -- An action that returns an Integer, which we view as CPU Clock values
    c <- getCPUTime :: IO Clock
    -- An action that prints data, but returns nothing
    print (c + 2*1000*1000*1000*1000) :: IO ()

หลังดูมีความจำเป็นมากใช่ไหม?


1

ถ้าใช่แล้วมันจะมีอยู่ได้อย่างไร? มันไม่ได้ละเมิดหลักการของฟังก์ชั่นการเขียนโปรแกรมหรือไม่? โดยเฉพาะอย่างยิ่งละเมิดความโปร่งใสของการอ้างอิง

มันไม่มีอยู่จริง

หรือถ้าไม่, แล้วเราจะรู้เวลาปัจจุบันในการโปรแกรมเชิงฟังก์ชันได้อย่างไร?

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


สำหรับ Haskell มีแนวคิดของ 'การกระทำของ IO' ซึ่งแสดงถึงประเภทที่สามารถดำเนินการกับกระบวนการ IO บางอย่างได้ ดังนั้นแทนที่จะอ้างอิงtimeค่าเราอ้างอิงIO Timeค่า ทั้งหมดนี้จะทำงานได้อย่างหมดจด เราไม่ได้มีการอ้างอิงถึงtimeแต่สิ่งที่ตามสายของ'อ่านค่าของเวลาที่ลงทะเบียน'

เมื่อเรารันโปรแกรม Haskell จริง ๆ แล้วการกระทำของ IO จะเกิดขึ้นจริง

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