ฉันไม่ได้รับสิ่งที่พวกเขาแก้ปัญหา
ฉันไม่ได้รับสิ่งที่พวกเขาแก้ปัญหา
คำตอบ:
พระไม่ดีหรือไม่ดี พวกเขาเป็นเพียงแค่ เป็นเครื่องมือที่ใช้ในการแก้ปัญหาเช่นการสร้างโปรแกรมภาษาอื่น ๆ อีกมากมาย แอปพลิเคชั่นที่สำคัญอย่างหนึ่งของพวกเขาคือการทำให้ชีวิตง่ายขึ้นสำหรับโปรแกรมเมอร์ที่ทำงานด้วยภาษาที่ใช้งานได้จริง แต่มีประโยชน์ในภาษาที่ไม่สามารถใช้งานได้ เป็นเพียงการที่คนไม่ค่อยรู้ว่าพวกเขากำลังใช้ Monad
Monad คืออะไร วิธีที่ดีที่สุดในการคิดถึง Monad คือรูปแบบการออกแบบ ในกรณีของ I / O คุณอาจจะคิดว่ามันเป็นมากกว่าท่อส่งเกียรติที่รัฐโลกเป็นสิ่งที่ได้รับผ่านระหว่างขั้นตอน
ตัวอย่างเช่นลองโค้ดที่คุณเขียน:
do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Nice to meet you, " ++ name ++ "!")
มีอะไรอีกมากมายเกิดขึ้นที่นี่มากกว่าสบตา ตัวอย่างเช่นคุณจะสังเกตเห็นว่ามีลายเซ็นต่อไปนี้:putStrLn
putStrLn :: String -> IO ()
ทำไมนี้
ลองคิดแบบนี้: มาเสแสร้ง (เพื่อความเรียบง่าย) ว่า stdout และ stdin เป็นไฟล์เดียวที่เราสามารถอ่านและเขียนได้ ในภาษาที่จำเป็นนี้จะไม่มีปัญหา แต่ในภาษาที่ใช้งานได้คุณไม่สามารถเปลี่ยนแปลงสถานะโกลบอลได้ ฟังก์ชั่นเป็นเพียงสิ่งที่ใช้ค่า (หรือค่า) และส่งกลับค่า (หรือค่า) วิธีหนึ่งในการทำเช่นนี้คือการใช้สถานะโกลบอลเป็นค่าที่ส่งผ่านเข้าและออกจากแต่ละฟังก์ชัน ดังนั้นคุณสามารถแปลรหัสบรรทัดแรกเป็นอะไรแบบนี้:
global_state <- (\(stdin, stdout) -> (stdin, stdout ++ "What is your name?")) global_state
... global_state
และคอมไพเลอร์จะได้รู้ว่าจะพิมพ์อะไรที่ของการเพิ่มในองค์ประกอบที่สองของ ตอนนี้ฉันไม่รู้เกี่ยวกับคุณ แต่ฉันก็เกลียดที่จะเขียนโปรแกรมเช่นนั้น วิธีนี้ทำให้ง่ายขึ้นคือการใช้ Monads ใน Monad คุณผ่านค่าที่แสดงถึงสถานะบางอย่างจากการกระทำหนึ่งไปยังอีกการกระทำหนึ่ง นี่คือสาเหตุที่putStrLn
มีประเภทส่งคืนIO ()
: กำลังคืนสถานะโกลบอลใหม่
แล้วทำไมคุณถึงสนใจ ดีข้อดีของการเขียนโปรแกรมการทำงานมากกว่าโปรแกรมความจำเป็นที่ได้รับการถกเถียงกันไปสู่ความตายในหลายสถานที่ดังนั้นฉันไม่ได้ไปที่จะตอบคำถามโดยทั่วไปว่า ( แต่เห็นบทความนี้ถ้าคุณต้องการที่จะได้ยินกรณีสำหรับการเขียนโปรแกรมการทำงาน) สำหรับกรณีเฉพาะนี้อาจช่วยได้ถ้าคุณเข้าใจว่า Haskell พยายามทำอะไร
โปรแกรมเมอร์จำนวนมากรู้สึกว่า Haskell พยายามป้องกันไม่ให้เขียนโค้ดที่จำเป็นหรือใช้ผลข้างเคียง นั่นไม่เป็นความจริงเลย ลองคิดดูด้วยวิธีนี้: ภาษาที่จำเป็นคือภาษาที่อนุญาตให้มีผลข้างเคียงโดยค่าเริ่มต้น แต่อนุญาตให้คุณเขียนโค้ดที่ใช้งานได้หากคุณต้องการ (และยินดีที่จะจัดการกับสิ่งที่ต้องการ) Haskell นั้นทำงานได้อย่างหมดจดโดยค่าเริ่มต้น แต่ช่วยให้คุณสามารถเขียนโค้ดที่จำเป็นถ้าคุณต้องการ (ซึ่งคุณทำถ้าโปรแกรมของคุณมีประโยชน์) ประเด็นก็คืออย่าทำให้มันยากที่จะเขียนโค้ดที่มีผลข้างเคียง เพื่อให้แน่ใจว่าคุณมีความชัดเจนเกี่ยวกับผลข้างเคียง (ด้วยระบบการบังคับใช้สิ่งนี้)
goto
(เป็นข้อโต้แย้งสำหรับการเขียนโปรแกรมเชิงโครงสร้าง) เล็กน้อยในภายหลังในกระดาษลักษณะลักษณะข้อโต้แย้งเช่น "ไร้ผล" และยังไม่มีพวกเราคนใดที่ปรารถนาจะgoto
กลับมาอย่างลับๆ เป็นเพียงการที่คุณไม่สามารถโต้แย้งได้ว่าgoto
ไม่จำเป็นสำหรับผู้ที่ใช้มันอย่างกว้างขวาง
ฉันจะกัด !!! Monads ด้วยตัวเองไม่ได้เป็น raison d'etre สำหรับ Haskell (Haskell รุ่นแรก ๆ ไม่มีแม้แต่พวกเขา)
คำถามของคุณเป็นเหมือนการพูดว่า "C ++ เมื่อฉันดูที่ไวยากรณ์ฉันรู้สึกเบื่อ แต่เทมเพลตเป็นคุณลักษณะที่โฆษณาอย่างสูงของ C ++ ดังนั้นฉันจึงดูการใช้งานในภาษาอื่น"
วิวัฒนาการของโปรแกรมเมอร์ Haskell เป็นเรื่องตลกมันไม่ได้ตั้งใจจะเอาจริงเอาจัง
Monad สำหรับจุดประสงค์ของโปรแกรมใน Haskell เป็นตัวอย่างของ class ประเภท Monad ซึ่งก็คือมันเป็นประเภทที่เกิดขึ้นเพื่อรองรับชุดปฏิบัติการขนาดเล็ก Haskell มีการสนับสนุนพิเศษสำหรับประเภทที่ใช้คลาสประเภท Monad ซึ่งรองรับการสร้างประโยคโดยเฉพาะ สิ่งที่เป็นผลลัพธ์ในทางนี้คือสิ่งที่ถูกเรียกว่า "โปรแกรมกึ่ง - ลำไส้ใหญ่" เมื่อคุณรวมฟังก์ชั่นนี้เข้ากับคุณสมบัติอื่น ๆ ของ Haskell (ฟังก์ชั่นชั้นหนึ่งความเกียจคร้านโดยค่าเริ่มต้น) สิ่งที่คุณได้รับคือความสามารถในการใช้งานบางอย่างเช่นไลบรารีที่ได้รับการพิจารณาคุณสมบัติทางภาษาแบบดั้งเดิม ตัวอย่างเช่นคุณสามารถใช้กลไกการยกเว้น คุณสามารถใช้การสนับสนุนสำหรับการต่อเนื่องและ coroutines เป็นห้องสมุด Haskell ภาษาไม่รองรับตัวแปรที่ไม่แน่นอน:
คุณถามเกี่ยวกับ "อาจ / Identity / Safe Division monads ???" อาจจะเป็นตัวอย่างของวิธีที่คุณสามารถใช้การจัดการข้อยกเว้น (ง่ายมากเพียงข้อยกเว้นเดียว) ในการเป็นห้องสมุด
คุณพูดถูกการเขียนข้อความและการอ่านข้อมูลผู้ใช้นั้นไม่เหมือนใคร IO เป็นตัวอย่างที่น่ารังเกียจของ "monads as a feature"
แต่เพื่อย้ำอีกครั้งหนึ่ง "คุณสมบัติ" ด้วยตัวเอง (เช่น Monads) ในการแยกจากส่วนที่เหลือของภาษาไม่จำเป็นต้องปรากฏขึ้นทันทีที่มีประโยชน์ (คุณลักษณะใหม่ที่ยอดเยี่ยมของ C ++ 0x คือการอ้างอิงค่าไม่ได้หมายความว่าคุณสามารถทำได้ ไม่อยู่ในบริบท C ++ เนื่องจากเป็นไวยากรณ์ที่ทำให้คุณเบื่อและจำเป็นต้องเห็นอรรถประโยชน์) ภาษาการเขียนโปรแกรมไม่ใช่สิ่งที่คุณได้รับโดยการใส่คุณสมบัติมากมายลงในที่เก็บข้อมูล
โปรแกรมเมอร์ทุกคนเขียนโปรแกรม แต่ความคล้ายคลึงกันสิ้นสุดลงที่นั่น ฉันคิดว่าโปรแกรมเมอร์แตกต่างไปจากที่โปรแกรมเมอร์ส่วนใหญ่นึกออก ใช้ "การต่อสู้" ที่ยาวนานมายาวนานเช่นการพิมพ์ตัวแปรแบบคงที่เทียบกับประเภทรันไทม์อย่างเดียวการเขียนสคริปต์ vs เรียบเรียงสคริปต์สไตล์ C กับวัตถุเชิง คุณจะพบว่ามันเป็นไปไม่ได้ที่จะให้เหตุผลว่าค่ายใดค่ายหนึ่งไม่ดีนักเพราะบางคนก็ปั่นโค้ดที่ยอดเยี่ยมในระบบการเขียนโปรแกรมบางอย่างที่ดูเหมือนไร้สาระหรือไร้ประโยชน์เลยสำหรับฉัน
ฉันคิดว่าคนที่แตกต่างกันแค่คิดแตกต่างกันและถ้าคุณไม่ได้ถูกล่อลวงด้วยน้ำตาล syntactic หรือนามธรรมโดยเฉพาะอย่างยิ่งที่มีอยู่เพื่อความสะดวกสบายและมีค่าใช้จ่ายจริงที่สำคัญแล้วโดยทั้งหมดอยู่ห่างจากภาษาดังกล่าว
อย่างไรก็ตามฉันขอแนะนำให้คุณอย่างน้อยพยายามทำความคุ้นเคยกับแนวคิดที่คุณกำลังจะยอมแพ้ ฉันไม่มีอะไรกับคนที่โกรธแค้นอย่างแท้จริงตราบใดที่พวกเขาเข้าใจว่าเรื่องใหญ่เกี่ยวกับการแสดงออกแลมบ์ดา ฉันสงสัยว่าส่วนใหญ่จะไม่กลายเป็นแฟนทันที แต่อย่างน้อยมันก็จะอยู่ที่นั่นที่ด้านหลังของจิตใจของพวกเขาเมื่อพวกเขาพบปัญหาที่สมบูรณ์แบบสิ่งที่จะเป็นไปได้ง่ายกว่าที่จะแก้ด้วยแลมบ์ดา
และเหนือสิ่งอื่นใดพยายามหลีกเลี่ยงการถูกแฟนบอยพูดโดยเฉพาะกับคนที่ไม่รู้จริง ๆ ว่าพวกเขากำลังพูดถึงอะไร
Haskell บังคับใช้Referential Transparency : กำหนดพารามิเตอร์เดียวกันทุกฟังก์ชั่นส่งคืนผลลัพธ์เดียวกันเสมอไม่ว่าคุณจะเรียกใช้ฟังก์ชันนั้นกี่ครั้งก็ตาม
นั่นหมายความว่าตัวอย่างเช่นใน Haskell (และไม่มี Monads) คุณไม่สามารถใช้เครื่องกำเนิดตัวเลขแบบสุ่มได้ ใน C ++ หรือ Java คุณสามารถทำได้โดยใช้ตัวแปรส่วนกลางเก็บค่า "seed" ระดับกลางของเครื่องกำเนิดแบบสุ่ม
บน Haskell ตัวแปรทั่วโลกเป็น Monads
Random
วัตถุ
เป็นคำถามแบบเก่า แต่เป็นคำถามที่ดีจริงๆดังนั้นฉันจะตอบ
คุณสามารถคิดว่า monads เป็นกลุ่มของรหัสที่คุณสามารถควบคุมการใช้งานได้อย่างสมบูรณ์: สิ่งที่แต่ละบรรทัดของโค้ดควรกลับมาไม่ว่าการดำเนินการควรจะหยุดที่จุดใด ๆ ไม่ว่าการประมวลผลอื่น ๆ ควรเกิดขึ้นระหว่างแต่ละบรรทัด
ฉันจะให้ตัวอย่างของสิ่งที่พระเปิดใช้งานซึ่งอาจเป็นเรื่องยาก ไม่มีตัวอย่างเหล่านี้อยู่ใน Haskell เพียงเพราะความรู้ของ Haskell ของฉันสั่นเล็กน้อย แต่พวกเขาทั้งหมดเป็นตัวอย่างของ Haskell ที่เป็นแรงบันดาลใจให้ใช้ monads
parsers
โดยปกติถ้าคุณต้องการเขียนโปรแกรมแยกวิเคราะห์บางส่วนเพื่อใช้ภาษาการเขียนโปรแกรมคุณจะต้องอ่านข้อกำหนดของ BNFและเขียนรหัส loopy ทั้งกลุ่มเพื่อแยกวิเคราะห์หรือคุณต้องใช้คอมไพเลอร์คอมไพเลอร์เช่น Flex, Bison, yacc เป็นต้น แต่ด้วย monads คุณสามารถสร้าง "compars parser" ใน Haskell ได้
โปรแกรมแยกวิเคราะห์ไม่สามารถทำได้โดยไม่ต้องใช้พระหรือภาษาวัตถุประสงค์พิเศษเช่น yacc, bison เป็นต้น
ตัวอย่างเช่นฉันใช้ข้อกำหนดภาษา BNF สำหรับโปรโตคอล IRC :
message = [ ":" prefix SPACE ] command [ params ] crlf
prefix = servername / ( nickname [ [ "!" user ] "@" host ] )
command = 1*letter / 3digit
params = *14( SPACE middle ) [ SPACE ":" trailing ]
=/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
nospcrlfcl = %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
; any octet except NUL, CR, LF, " " and ":"
middle = nospcrlfcl *( ":" / nospcrlfcl )
trailing = *( ":" / " " / nospcrlfcl )
SPACE = %x20 ; space character
crlf = %x0D %x0A ; "carriage return" "linefeed"
และกระทืบมันลงไปประมาณ 40 บรรทัดของรหัสใน F # (ซึ่งเป็นภาษาอื่นที่รองรับพระ):
type UserIdentifier = { Name : string; User: string; Host: string }
type Message = { Prefix : UserIdentifier option; Command : string; Params : string list }
let space = character (char 0x20)
let parameters =
let middle = parser {
let! c = sat <| fun c -> c <> ':' && c <> (char 0x20)
let! cs = many <| sat ((<>)(char 0x20))
return (c::cs)
}
let trailing = many item
let parameter = prefixed space ((prefixed (character ':') trailing) +++ middle)
many parameter
let command = atLeastOne letter +++ (count 3 digit)
let prefix = parser {
let! name = many <| sat (fun c -> c <> '!' && c <> '@' && c <> (char 0x20)) //this is more lenient than RFC2812 2.3.1
let! uh = parser {
let! user = maybe <| prefixed (character '!') (many <| sat (fun c -> c <> '@' && c <> (char 0x20)))
let! host = maybe <| prefixed (character '@') (many <| sat ((<>) ' '))
return (user, host)
}
let nullstr = function | Some([]) -> null | Some(s) -> charsString s | _ -> null
return { Name = charsString name; User = nullstr (fst uh); Host = nullstr (snd uh) }
}
let message = parser {
let! p = maybe (parser {
let! _ = character ':'
let! p = prefix
let! _ = space
return p
})
let! c = command
let! ps = parameters
return { Prefix = p; Command = charsString c; Params = List.map charsString ps }
}
ไวยากรณ์ของ monad ของ F # นั้นค่อนข้างน่าเกลียดเมื่อเทียบกับ Haskell และฉันน่าจะได้รับการปรับปรุงให้ดีขึ้นเล็กน้อย - แต่จุดที่จะกลับบ้านก็คือโครงสร้างรหัส parser นั้นเหมือนกับ BNF สิ่งนี้ไม่เพียง แต่จะทำงานได้มากขึ้นโดยไม่ต้อง monads (หรือตัวแยกวิเคราะห์) มันก็แทบจะไม่มีความคล้ายคลึงกับสเปคและมันก็แย่มากทั้งในการอ่านและการบำรุงรักษา
มัลติทาสกิ้งที่กำหนดเอง
โดยปกติแล้วมัลติทาสกิ้งคิดว่าเป็นคุณลักษณะของระบบปฏิบัติการ - แต่ด้วย monads คุณสามารถเขียนกำหนดการของคุณเองซึ่งหลังจากแต่ละคำสั่ง monad โปรแกรมจะผ่านการควบคุมไปยังตัวกำหนดตารางเวลาซึ่งจะเลือก monad อื่นเพื่อดำเนินการ
ผู้ชายคนหนึ่งทำ"งาน" monadเพื่อควบคุมลูปเกม (อีกครั้งใน F #) ดังนั้นแทนที่จะต้องเขียนทุกอย่างเป็นเครื่องรัฐที่ทำหน้าที่ในการUpdate()
โทรแต่ละครั้งเขาก็สามารถเขียนคำแนะนำทั้งหมดราวกับว่าพวกเขาเป็นฟังก์ชั่นเดียว .
กล่าวอีกนัยหนึ่งแทนที่จะต้องทำสิ่งที่ชอบ:
class Robot
{
enum State { Walking, Shooting, Stopped }
State state = State.Stopped;
public void Update()
{
switch(state)
{
case State.Stopped:
Walk();
state = State.Walking;
break;
case State.Walking:
if (enemyInSight)
{
Shoot();
state = State.Shooting;
}
break;
}
}
}
คุณสามารถทำสิ่งที่ชอบ:
let robotActions = task {
while (not enemyInSight) do
Walk()
while (enemyInSight) do
Shoot()
}
LINQ เป็น SQL
LINQ to SQL เป็นตัวอย่างของ monad และฟังก์ชันการทำงานที่คล้ายกันสามารถนำมาใช้ใน Haskell ได้อย่างง่ายดาย
ฉันจะไม่ได้ลงรายละเอียดตั้งแต่ผมจำไม่ได้ว่าทุกสิ่งที่ถูกต้อง แต่เอริคเมย์เยอร์อธิบายได้ค่อนข้างดี
หากคุณคุ้นเคยกับรูปแบบ GoF พระจะเปรียบเหมือนรูปแบบมัณฑนากรและรูปแบบตัวสร้างรวมกันบนสเตียรอยด์กัดโดยตัวรบกวนกัมมันตภาพรังสี
มีคำตอบที่ดีกว่าด้านบน แต่ประโยชน์บางอย่างที่ฉันเห็นคือ:
พระตกแต่งรูปแบบแกนกลางที่มีคุณสมบัติเพิ่มเติมโดยไม่ต้องเปลี่ยนประเภทแกน ตัวอย่างเช่น monad อาจ "ยก" สตริงและเพิ่มค่าเช่น "isWellFormed", "isProfanity" หรือ "isPalindrome" เป็นต้น
ในทำนองเดียวกัน monads อนุญาตให้กลุ่มที่เรียบง่ายเป็นประเภทคอลเลกชัน
Monads อนุญาตให้มีการผูกฟังก์ชั่นในพื้นที่ที่มีลำดับสูงกว่านี้ได้
monads อนุญาตให้ผสมฟังก์ชั่นโดยพลการและข้อโต้แย้งกับชนิดข้อมูลเองในพื้นที่ที่สูงขึ้น
Monads อนุญาตให้ผสมผสานฟังก์ชั่นบริสุทธิ์ไร้สัญชาติด้วยฐานที่ไม่บริสุทธิ์และไร้รัฐเพื่อให้คุณสามารถติดตามว่าปัญหาอยู่ที่ไหน
ตัวอย่างที่คุ้นเคยของ monad ใน Java คือ List ใช้คลาสหลักบางอย่างเช่นสตริงและ "ยก" ลงในพื้นที่ monad ของ List เพิ่มข้อมูลเกี่ยวกับรายการ จากนั้นจะผูกฟังก์ชันใหม่ลงในพื้นที่นั้นเช่น get (), getFirst (), add (), empty () ฯลฯ
ในขนาดใหญ่ลองจินตนาการว่าแทนที่จะเขียนโปรแกรมคุณเพียงแค่เขียน Builder ขนาดใหญ่ (เป็นรูปแบบ GoF) และวิธีการ build () ในท้ายที่สุดจะถกเถียงกันว่าอะไรก็ตามที่โปรแกรมควรจะสร้าง และคุณสามารถเพิ่มวิธีการใหม่ใน ProgramBuilder ของคุณโดยไม่ต้องคอมไพล์โค้ดต้นฉบับใหม่ นั่นเป็นเหตุผลว่าทำไม monads จึงเป็นรูปแบบการออกแบบที่ทรงพลัง