เป็นเวลาหนึ่งปีแล้วที่ฉันโพสต์คำถามนี้ หลังจากโพสต์แล้วฉันก็ไปที่ Haskell สักสองสามเดือน ฉันสนุกกับมันอย่างมาก แต่ฉันก็เก็บมันไว้เหมือนที่ฉันพร้อมที่จะเจาะเข้าไปใน Monads ฉันกลับไปทำงานและมุ่งเน้นไปที่เทคโนโลยีที่โครงการของฉันต้องการ
นี่มันเท่ห์ดี มันเป็นนามธรรมเล็กน้อย ฉันนึกภาพคนที่ไม่รู้ว่าพระสงฆ์สับสนอะไรอยู่แล้วเนื่องจากขาดตัวอย่างที่แท้จริง
ดังนั้นให้ฉันพยายามที่จะปฏิบัติตามและเพื่อให้ชัดเจนจริงๆฉันจะทำตัวอย่างใน C # ถึงแม้ว่ามันจะดูน่าเกลียด ฉันจะเพิ่ม Haskell ที่เทียบเท่ากันในตอนท้ายและแสดงให้คุณเห็นน้ำตาล Haskell syntactic ที่ยอดเยี่ยมซึ่งเป็นที่ที่ IMO พระเริ่มเริ่มมีประโยชน์จริงๆ
โอเคดังนั้นหนึ่งใน Monads ที่ง่ายที่สุดจึงถูกเรียกว่า "บางที monad" ใน Haskell ใน C # Nullable<T>
ชนิดที่อาจจะเรียกว่า โดยพื้นฐานแล้วมันเป็นคลาสเล็ก ๆ ที่เพิ่งสรุปแนวคิดของค่าที่ใช้ได้และมีค่าหรือเป็น "โมฆะ" และไม่มีค่า
สิ่งที่มีประโยชน์ที่จะติดอยู่ภายใน monad สำหรับการรวมค่าประเภทนี้คือแนวคิดของความล้มเหลว นั่นคือเราต้องการให้สามารถดูค่า nullable หลาย ๆ ค่าและส่งคืนได้null
ทันทีที่ค่าใดค่าหนึ่งเป็นโมฆะ สิ่งนี้อาจมีประโยชน์หากคุณค้นหาคีย์จำนวนมากในพจนานุกรมหรือบางสิ่งและท้ายที่สุดคุณต้องการประมวลผลผลลัพธ์ทั้งหมดและรวมเข้าด้วยกัน แต่ถ้าปุ่มใด ๆ ไม่อยู่ในพจนานุกรม คุณต้องการที่จะกลับมาnull
สำหรับสิ่งทั้งหมด มันน่าเบื่อที่ต้องตรวจสอบการค้นหาแต่ละครั้งnull
และส่งคืนด้วยตนเอง
ดังนั้นเราจึงสามารถซ่อนการตรวจสอบนี้ภายในตัวดำเนินการเชื่อมโยง (ซึ่งเป็นประเภทของพระสงฆ์เราซ่อนการเก็บหนังสือไว้ในตัวดำเนินการผูกซึ่งทำให้รหัสง่ายขึ้น ใช้เนื่องจากเราสามารถลืมรายละเอียด)
นี่คือโปรแกรมที่กระตุ้นทุกสิ่ง (ฉันจะให้คำจำกัดความ
Bind
ต่อมานี่เป็นเพียงการแสดงให้คุณเห็นว่าทำไมมันถึงดีมาก)
class Program
{
static Nullable<int> f(){ return 4; }
static Nullable<int> g(){ return 7; }
static Nullable<int> h(){ return 9; }
static void Main(string[] args)
{
Nullable<int> z =
f().Bind( fval =>
g().Bind( gval =>
h().Bind( hval =>
new Nullable<int>( fval + gval + hval ))));
Console.WriteLine(
"z = {0}", z.HasValue ? z.Value.ToString() : "null" );
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
ตอนนี้ไม่สนใจสักครู่ว่ามีการสนับสนุนสำหรับการทำเช่นนี้Nullable
ใน C # (คุณสามารถเพิ่ม ints nullable ด้วยกันและคุณจะได้รับ null ถ้าทั้งสองเป็นโมฆะ) สมมติว่าไม่มีคุณสมบัติดังกล่าวและเป็นเพียงคลาสที่ผู้ใช้กำหนดเองโดยไม่มีเวทมนต์พิเศษ ประเด็นก็คือเราสามารถใช้Bind
ฟังก์ชั่นในการผูกตัวแปรกับเนื้อหาที่มีNullable
ค่าของเราแล้วแกล้งทำเป็นว่าไม่มีอะไรแปลก ๆ เกิดขึ้นและใช้พวกมันเหมือน int ปกติและเพิ่มพวกมันเข้าด้วยกัน เราห่อผลใน nullable ที่สิ้นสุดและ nullable ที่อาจจะเป็นโมฆะ (ถ้ามีของf
, g
หรือh
ผลตอบแทน null) หรือมันจะเป็นผลมาจากข้อสรุปf
, g
และh
ด้วยกัน. (นี่คล้ายกับวิธีที่เราสามารถผูกแถวในฐานข้อมูลกับตัวแปรใน LINQ และทำสิ่งต่าง ๆ กับมันปลอดภัยในความรู้ที่Bind
ผู้ประกอบการจะทำให้แน่ใจว่าตัวแปรจะผ่านค่าแถวที่ถูกต้องเท่านั้น)
คุณสามารถเล่นกับสิ่งนี้และเปลี่ยนแปลงใด ๆ ของf
, g
และh
เพื่อคืนค่าว่างและคุณจะเห็นว่าสิ่งทั้งหมดจะคืนค่าว่าง
เห็นได้ชัดว่าผู้ประกอบการเชื่อมต้องทำการตรวจสอบนี้กับเราและประกันการคืนค่าว่างถ้าพบค่า Null และส่งผ่านค่าภายในNullable
โครงสร้างลงในแลมบ์ดา
นี่คือBind
ผู้ประกอบการ:
public static Nullable<B> Bind<A,B>( this Nullable<A> a, Func<A,Nullable<B>> f )
where B : struct
where A : struct
{
return a.HasValue ? f(a.Value) : null;
}
ประเภทที่นี่เหมือนกับในวิดีโอ มันใช้เวลาM a
( Nullable<A>
ในไวยากรณ์ C # สำหรับกรณีนี้) และฟังก์ชั่นจากa
ถึง
M b
( Func<A, Nullable<B>>
ในไวยากรณ์ C #) และมันส่งกลับM b
( Nullable<B>
)
รหัสจะตรวจสอบว่าค่า nullable นั้นมีค่าหรือไม่และถ้าแยกออกมาและส่งไปยังฟังก์ชันนั้นมิฉะนั้นจะส่งคืนค่า null ซึ่งหมายความว่าBind
ผู้ประกอบการจะจัดการกับตรรกะการตรวจสอบโมฆะทั้งหมดสำหรับเรา ถ้าหากว่าค่าที่เราเรียก
Bind
ใช้นั้นไม่ใช่ค่า Null ค่านั้นจะถูก "ส่งไปตาม" ไปยังฟังก์ชั่นแลมบ์ดาเราจะประกันตัวก่อนเวลาและการแสดงออกทั้งหมดจะเป็นโมฆะ นี้จะช่วยให้โค้ดที่เราเขียนโดยใช้ monad ที่จะเป็นอิสระโดยสิ้นเชิงของพฤติกรรม null การตรวจสอบนี้เราเพียงแค่ใช้Bind
และได้รับตัวแปรที่ถูกผูกไว้กับค่าภายในค่าเอก ( fval
,
gval
และhval
ในรหัสตัวอย่าง) และเราสามารถใช้พวกเขาปลอดภัย ในความรู้ที่Bind
จะตรวจสอบพวกเขาเป็นโมฆะก่อนที่จะผ่านพวกเขาไป
มีตัวอย่างอื่น ๆ ของสิ่งที่คุณสามารถทำได้กับ Monad ตัวอย่างเช่นคุณสามารถทำให้Bind
โอเปอเรเตอร์ดูแลอินพุตสตรีมของตัวละครและใช้มันเพื่อเขียนตัวแยกวิเคราะห์ parser แต่ละ Combinator parser แล้วได้อย่างสมบูรณ์ลบเลือนไปสิ่งที่ต้องการกลับมาติดตามความล้มเหลว parser ฯลฯ และเพียงแค่รวม parsers ขนาดเล็กด้วยกันราวกับว่าสิ่งที่จะไม่ผิดไป, ตู้เซฟในความรู้ว่าการดำเนินงานที่ฉลาดของBind
ทุกประเภทออกทั้งหมดตรรกะที่อยู่เบื้องหลัง บิตยาก หลังจากนั้นอาจมีบางคนเพิ่มการบันทึกลงใน Monad แต่รหัสที่ใช้ monad จะไม่เปลี่ยนแปลงเนื่องจากเวทมนตร์ทั้งหมดเกิดขึ้นในคำจำกัดความของBind
ผู้ปฏิบัติงานส่วนที่เหลือของรหัสไม่เปลี่ยนแปลง
ในที่สุดนี่คือการใช้รหัสเดียวกันใน Haskell ( --
เริ่มบรรทัดความคิดเห็น)
-- Here's the data type, it's either nothing, or "Just" a value
-- this is in the standard library
data Maybe a = Nothing | Just a
-- The bind operator for Nothing
Nothing >>= f = Nothing
-- The bind operator for Just x
Just x >>= f = f x
-- the "unit", called "return"
return = Just
-- The sample code using the lambda syntax
-- that Brian showed
z = f >>= ( \fval ->
g >>= ( \gval ->
h >>= ( \hval -> return (fval+gval+hval ) ) ) )
-- The following is exactly the same as the three lines above
z2 = do
fval <- f
gval <- g
hval <- h
return (fval+gval+hval)
อย่างที่คุณเห็นdo
สัญกรณ์ที่ดีในตอนท้ายทำให้มันดูเหมือนรหัสบังคับตรง และนี่คือการออกแบบ Monads สามารถใช้ห่อหุ้มสิ่งที่มีประโยชน์ทั้งหมดในการเขียนโปรแกรมที่จำเป็น (สถานะที่ไม่แน่นอน, IO ฯลฯ ) และใช้โดยใช้ไวยากรณ์ที่คล้ายกับคำสั่งที่ดี แต่อยู่หลังม่านมันเป็นเพียง monads และการใช้งานที่ชาญฉลาดของผู้ประกอบการผูกมัด! สิ่งดีๆที่คุณสามารถใช้ monads ของคุณเองโดยการดำเนินการและ>>=
return
และถ้าคุณทำเช่นนั้นพระเหล่านั้นก็จะสามารถใช้do
สัญกรณ์ซึ่งหมายความว่าคุณสามารถเขียนภาษาเล็ก ๆ ของคุณโดยเพียงแค่กำหนดสองฟังก์ชั่น!