แนวทางที่ดีที่สุดในการออกแบบไลบรารี F # สำหรับการใช้งานทั้ง F # และ C #


113

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

และนี่คือจุดที่ฉันติดอยู่เล็กน้อย ฉันสามารถทำให้ F # เป็นมิตรหรือฉันสามารถทำให้ C # เป็นมิตรได้ แต่ปัญหาคือจะทำให้เป็นมิตรกับทั้งคู่ได้อย่างไร

นี่คือตัวอย่าง ลองนึกภาพว่าฉันมีฟังก์ชันต่อไปนี้ใน F #:

let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a

สามารถใช้งานได้อย่างสมบูรณ์แบบจาก F #:

let useComposeInFsharp() =
    let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
    composite "foo"
    composite "bar"

ใน C # composeฟังก์ชันมีลายเซ็นดังต่อไปนี้:

FSharpFunc<T, Unit> compose<T, TResult>(FSharpFunc<T, TResult> f, FSharpFunc<TResult, Unit> a);

แต่แน่นอนฉันไม่ต้องการFSharpFuncในลายเซ็นสิ่งที่ฉันต้องการคือFuncและActionแทนเช่นนี้

Action<T> compose2<T, TResult>(Func<T, TResult> f, Action<TResult> a);

เพื่อให้บรรลุสิ่งนี้ฉันสามารถสร้างcompose2ฟังก์ชันดังนี้:

let compose2 (f: Func<'T, 'TResult>) (a : Action<'TResult> ) = 
    new Action<'T>(f.Invoke >> a.Invoke)

ตอนนี้สามารถใช้งานได้อย่างสมบูรณ์แบบใน C #:

void UseCompose2FromCs()
{
    compose2((string s) => s.ToUpper(), Console.WriteLine);
}

แต่ตอนนี้เรามีปัญหาในการใช้งานcompose2จาก F #! ตอนนี้ฉันต้องรวม F # มาตรฐานทั้งหมดfunsลงในFuncและActionดังนี้:

let useCompose2InFsharp() =
    let f = Func<_,_>(fun item -> item.ToString())
    let a = Action<_>(fun item -> printfn "%A" item)
    let composite2 = compose2 f a

    composite2.Invoke "foo"
    composite2.Invoke "bar"

คำถาม:เราจะบรรลุประสบการณ์ชั้นหนึ่งสำหรับไลบรารีที่เขียนด้วย F # สำหรับทั้งผู้ใช้ F # และ C # ได้อย่างไร

จนถึงตอนนี้ฉันไม่สามารถหาอะไรได้ดีไปกว่าสองวิธีนี้:

  1. แอสเซมบลีสองชุดแยกกัน: ชุดหนึ่งกำหนดเป้าหมายไปยังผู้ใช้ F # และชุดที่สองสำหรับผู้ใช้ C #
  2. หนึ่งแอสเซมบลี แต่เนมสเปซต่างกัน: อันหนึ่งสำหรับผู้ใช้ F # และอันที่สองสำหรับผู้ใช้ C #

สำหรับแนวทางแรกฉันจะทำสิ่งนี้:

  1. สร้างโครงการ F # เรียกว่า FooBarFs และรวบรวมเป็น FooBarFs.dll

    • กำหนดเป้าหมายห้องสมุดเป็นผู้ใช้ F # เท่านั้น
    • ซ่อนทุกสิ่งที่ไม่จำเป็นจากไฟล์. fsi
  2. สร้างโปรเจ็กต์ F # อื่นเรียก FooBarCs และคอมไพล์ลงใน FooFar.dll

    • นำโครงการ F # แรกมาใช้ซ้ำที่ระดับต้นทาง
    • สร้างไฟล์. fsi ซึ่งซ่อนทุกอย่างจากโปรเจ็กต์นั้น
    • สร้างไฟล์. fsi ซึ่งแสดงไลบรารีด้วยวิธี C # โดยใช้สำนวน C # สำหรับชื่อเนมสเปซ ฯลฯ
    • สร้าง Wrapper ที่มอบสิทธิ์ให้กับไลบรารีหลักทำการแปลงเมื่อจำเป็น

ฉันคิดว่าแนวทางที่สองกับเนมสเปซอาจสร้างความสับสนให้กับผู้ใช้ แต่คุณมีแอสเซมบลีเดียว

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

คำถาม:มีใครอีกบ้างที่พยายามบรรลุสิ่งที่คล้ายกันและถ้าเป็นเช่นนั้นคุณทำได้อย่างไร?

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


แก้ไข 2:

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

ให้ฉันยกตัวอย่างที่เป็นรูปธรรมมากขึ้น ฉันได้อ่าน เอกสารแนวทางการออกแบบส่วนประกอบ F # ที่ยอดเยี่ยมที่สุดแล้ว (ขอบคุณมาก @gradbot สำหรับสิ่งนี้!) หลักเกณฑ์ในเอกสารหากใช้จะช่วยแก้ปัญหาบางประการ แต่ไม่ใช่ทั้งหมด

เอกสารนี้แบ่งออกเป็นสองส่วนหลัก: 1) แนวทางในการกำหนดเป้าหมายผู้ใช้ F #; และ 2) แนวทางในการกำหนดเป้าหมายผู้ใช้ C # ไม่มีที่ไหนแม้แต่จะพยายามแสร้งทำเป็นว่าเป็นไปได้ที่จะมีแนวทางที่เหมือนกันซึ่งสะท้อนคำถามของฉันอย่างแน่นอน: เราสามารถกำหนดเป้าหมาย F # เราสามารถกำหนดเป้าหมาย C # ได้ แต่วิธีแก้ปัญหาที่ใช้ได้จริงสำหรับการกำหนดเป้าหมายทั้งสองคืออะไร?

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

คำหลักที่นี่คือสำนวน ปัญหาไม่ได้อยู่ที่ความสามารถในการทำงานร่วมกันโดยทั่วไปซึ่งเป็นไปได้ที่จะใช้ไลบรารีในภาษาต่างๆ

ตอนนี้เป็นตัวอย่างซึ่งฉันนำมาจาก แนวทางการออกแบบส่วนประกอบ F #โดยตรง

  1. โมดูล + ฟังก์ชัน (F #) เทียบกับ Namespaces + ประเภท + ฟังก์ชัน

    • F #: ใช้เนมสเปซหรือโมดูลเพื่อบรรจุประเภทและโมดูลของคุณ การใช้สำนวนคือการวางฟังก์ชั่นในโมดูลเช่น:

      // library
      module Foo
      let bar() = ...
      let zoo() = ...
      
      
      // Use from F#
      open Foo
      bar()
      zoo()
    • C #: ใช้เนมสเปซประเภทและสมาชิกเป็นโครงสร้างองค์กรหลักสำหรับส่วนประกอบของคุณ (ซึ่งตรงข้ามกับโมดูล) สำหรับ vanilla .NET API

      สิ่งนี้ไม่เข้ากันกับแนวทาง F # และตัวอย่างจะต้องถูกเขียนใหม่เพื่อให้เหมาะกับผู้ใช้ C #:

      [<AbstractClass; Sealed>]
      type Foo =
          static member bar() = ...
          static member zoo() = ...

      โดยการทำเช่นแม้ว่าเราจะแบ่งการใช้สำนวนจาก F # เพราะเราสามารถที่ไม่ได้ใช้barและzooโดยไม่ต้อง prefixing Fooด้วย

  2. การใช้ tuples

    • F #: ใช้ tuples เมื่อเหมาะสมกับค่าที่ส่งคืน

    • C #: หลีกเลี่ยงการใช้ tuples เป็นค่าส่งคืนใน vanilla .NET API

  3. async

    • F #: ใช้ Async สำหรับการเขียนโปรแกรม async ที่ขอบเขต F # API

    • C #: แสดงการดำเนินการแบบอะซิงโครนัสโดยใช้รูปแบบการเขียนโปรแกรมแบบอะซิงโครนัส. NET (BeginFoo, EndFoo) หรือเป็นวิธีการที่ส่งคืนงาน. NET (งาน) แทนที่จะเป็นวัตถุ F # Async

  4. การใช้ Option

    • F #: พิจารณาใช้ค่าตัวเลือกสำหรับประเภทการส่งคืนแทนการเพิ่มข้อยกเว้น (สำหรับรหัส F # -facing)

    • ลองใช้รูปแบบ TryGetValue แทนการส่งคืนค่าอ็อพชัน F # (ตัวเลือก) ใน vanilla .NET APIs และชอบที่วิธีการโอเวอร์โหลดมากกว่าการใช้ค่าอ็อพชัน F # เป็นอาร์กิวเมนต์

  5. สหภาพแรงงานที่เลือกปฏิบัติ

    • F #: ใช้สหภาพแรงงานที่เลือกปฏิบัติเป็นทางเลือกแทนลำดับชั้นของคลาสสำหรับการสร้างข้อมูลที่มีโครงสร้างแบบต้นไม้

    • C #: ไม่มีแนวทางเฉพาะสำหรับเรื่องนี้ แต่แนวคิดของสหภาพแรงงานที่เลือกปฏิบัตินั้นต่างจาก C #

  6. ฟังก์ชัน Curried

    • F #: ฟังก์ชัน curried เป็นสำนวนสำหรับ F #

    • C #: อย่าใช้การแกงของพารามิเตอร์ใน vanilla .NET API

  7. กำลังตรวจสอบค่าว่าง

    • F #: นี่ไม่ใช่สำนวนสำหรับ F #

    • C #: พิจารณาตรวจสอบค่า null บนขอบเขต vanilla .NET API

  8. การใช้ F # ประเภทlist, map, setฯลฯ

    • F #: มันเป็นสำนวนที่จะใช้สิ่งเหล่านี้ใน F #

    • C #: พิจารณาใช้อินเทอร์เฟซการรวบรวม. NET ประเภท IEnumerable และ IDictionary สำหรับพารามิเตอร์และส่งคืนค่าใน vanilla .NET APIs ( คือไม่ได้ใช้ F # list, map,set )

  9. ประเภทฟังก์ชัน (ที่ชัดเจน)

    • F #: การใช้ฟังก์ชัน F # เนื่องจากค่าเป็นสำนวนสำหรับ F # อย่างชัดเจน

    • C #: ใช้ประเภทผู้ร่วมประชุม. NET ตามความต้องการของประเภทฟังก์ชัน F # ใน vanilla .NET API

ฉันคิดว่าสิ่งเหล่านี้น่าจะเพียงพอที่จะแสดงให้เห็นถึงลักษณะของคำถามของฉัน

อนึ่งหลักเกณฑ์ยังมีคำตอบบางส่วน:

... กลยุทธ์การใช้งานทั่วไปเมื่อพัฒนาวิธีการลำดับที่สูงขึ้นสำหรับไลบรารี vanilla .NET คือการสร้างการใช้งานทั้งหมดโดยใช้ประเภทฟังก์ชัน F # จากนั้นสร้าง API สาธารณะโดยใช้ผู้รับมอบสิทธิ์เป็นส่วนหน้าบาง ๆ บนการใช้งาน F # จริง

เพื่อสรุป

มีหนึ่งคำตอบที่ชัดเจนคือไม่มีเทคนิคคอมไพเลอร์ที่ไม่ได้รับ

ตามแนวทางเอกสารดูเหมือนว่าการเขียน F # ก่อนแล้วจึงสร้างกระดาษห่อหุ้มซุ้มสำหรับ. NET เป็นกลยุทธ์ที่สมเหตุสมผล

คำถามยังคงอยู่เกี่ยวกับการนำไปใช้จริง:

  • แยกประกอบ? หรือ

  • เนมสเปซต่างกัน?

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

ฉันคิดว่าฉันจะเห็นด้วยกับเรื่องนี้เนื่องจากการเลือกเนมสเปซนั้นไม่แปลกใจหรือสับสนกับผู้ใช้. NET / C # ซึ่งหมายความว่าเนมสเปซสำหรับพวกเขาควรมีลักษณะเป็นเนมสเปซหลักสำหรับพวกเขา ผู้ใช้ F # จะต้องรับภาระในการเลือก F # - เฉพาะเนมสเปซ ตัวอย่างเช่น:

  • FSharp.Foo.Bar -> เนมสเปซสำหรับ F # หันหน้าไปทางไลบรารี

  • Foo.Bar -> เนมสเปซสำหรับ. NET wrapper สำนวนสำหรับ C #



นอกเหนือจากฟังก์ชั่นระดับเฟิร์สคลาสแล้วคุณมีข้อกังวลอะไรบ้าง? F # ไปไกลแล้วในการมอบประสบการณ์ที่ราบรื่นจากภาษาอื่น ๆ
Daniel

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

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

2
@KomradeP.: เกี่ยวกับ EDIT 2 ของคุณFSharpxมีวิธีการบางอย่างที่จะทำให้การทำงานร่วมกันดูเหมือนจะไร้สาระ คุณสามารถดูคำแนะนำบางอย่างได้ในโพสต์บล็อกที่ยอดเยี่ยมนี้
แผ่น

คำตอบ:


40

Daniel ได้อธิบายวิธีกำหนดฟังก์ชัน F # เวอร์ชัน C # -friendly ที่คุณเขียนไว้แล้วดังนั้นฉันจะเพิ่มความคิดเห็นระดับสูงขึ้น ก่อนอื่นคุณควรอ่านหลักเกณฑ์การออกแบบส่วนประกอบ F # (อ้างอิงโดย gradbot แล้ว) นี่คือเอกสารที่อธิบายวิธีการออกแบบไลบรารี F # และ. NET โดยใช้ F # และควรตอบคำถามของคุณได้หลายข้อ

เมื่อใช้ F # โดยทั่วไปมีห้องสมุดสองประเภทที่คุณสามารถเขียนได้:

  • F # ห้องสมุดถูกออกแบบมาเพื่อนำมาใช้เท่านั้นจาก F # เพื่อให้อินเตอร์เฟซที่สาธารณะมันถูกเขียนในรูปแบบการทำงาน (ใช้ประเภท F # ฟังก์ชั่น tuples, เลือกปฏิบัติสหภาพแรงงาน ฯลฯ )

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

ในคำถามของคุณคุณกำลังถามว่าจะแสดงองค์ประกอบของฟังก์ชันเป็นไลบรารี. NET ได้อย่างไร แต่ฉันคิดว่าฟังก์ชันเช่นของคุณcomposeเป็นแนวคิดระดับต่ำเกินไปจากมุมมองของไลบรารี. NET คุณสามารถแสดงเป็นวิธีการทำงานร่วมกับFuncและActionแต่นั่นอาจไม่ใช่วิธีที่คุณจะออกแบบไลบรารี. NET แบบปกติตั้งแต่แรก (บางทีคุณอาจใช้รูปแบบ Builder แทนหรืออะไรทำนองนั้น)

ในบางกรณี (เช่นเมื่อมีการออกแบบห้องสมุดเป็นตัวเลขที่ไม่ได้จริงๆพอดีกับรูปแบบห้องสมุด NET) มันทำให้ความรู้สึกที่ดีในการออกแบบห้องสมุดที่ผสมทั้งF #และ.NETรูปแบบในห้องสมุดเดียว วิธีที่ดีที่สุดคือการมี F # ปกติ (หรือ. NET) API ปกติแล้วจัดเตรียมเครื่องห่อสำหรับการใช้งานตามธรรมชาติในรูปแบบอื่น ๆ Wrapper สามารถอยู่ในเนมสเปซแยกกันได้ (เช่นMyLibrary.FSharpและMyLibrary)

ในตัวอย่างของคุณคุณสามารถปล่อยการใช้งาน F # ไว้ในMyLibrary.FSharpแล้วเพิ่ม. NET (C # -friendly) wrapper (คล้ายกับโค้ดที่ Daniel โพสต์) ในMyLibraryเนมสเปซเป็นวิธีการคงที่ของคลาสบางคลาส แต่อีกครั้งไลบรารี. NET น่าจะมี API ที่เฉพาะเจาะจงมากกว่าองค์ประกอบของฟังก์ชัน


1
ตอนนี้แนวทางการออกแบบส่วนประกอบ F # โฮสต์อยู่ที่นี่
Jan Schiefer

19

คุณต้องตัดค่าฟังก์ชันเท่านั้น(ฟังก์ชันที่ใช้บางส่วน ฯลฯ ) ด้วยFuncหรือActionส่วนที่เหลือจะถูกแปลงโดยอัตโนมัติ ตัวอย่างเช่น:

type A(arg) =
  member x.Invoke(f: Func<_,_>) = f.Invoke(arg)

let a = A(1)
a.Invoke(fun i -> i + 1)

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

นอกจากนี้ F # ยังมีความยืดหยุ่นมากกว่า C # ในแง่ของความสามารถในการทำงานร่วมกันดังนั้นโดยทั่วไปแล้วควรปฏิบัติตามรูปแบบ. NET แบบดั้งเดิมเมื่อเป็นปัญหา

แก้ไข

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

รับคะแนนของคุณในทางกลับกัน:

  1. letการผูกโมดูล + และชนิดที่ไม่มีตัวสร้าง + สมาชิกคงที่จะปรากฏเหมือนกันทุกประการใน C # ดังนั้นให้ไปกับโมดูลถ้าคุณทำได้ คุณสามารถใช้CompiledNameAttributeเพื่อตั้งชื่อสมาชิก C # -friendly

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

  3. นี่คือที่ผมคิดว่าคุณได้ทำในสิ่งที่วิธี C # F # เพราะเล่นได้ดีมีTaskแต่ C # Asyncไม่เล่นกันได้ดีกับ คุณสามารถใช้ async ภายในจากนั้นโทรAsync.StartAsTaskก่อนกลับจากวิธีสาธารณะ

  4. การยอมรับnullอาจเป็นข้อเสียเปรียบที่ใหญ่ที่สุดเพียงประการเดียวเมื่อพัฒนา API เพื่อใช้จาก C # ที่ผ่านมาฉันลองใช้กลอุบายทุกรูปแบบเพื่อหลีกเลี่ยงการพิจารณาค่าว่างในรหัส F # ภายใน แต่ในที่สุดวิธีที่ดีที่สุดคือการทำเครื่องหมายประเภทด้วยตัวสร้างสาธารณะด้วย[<AllowNullLiteral>]และตรวจสอบ args ว่าเป็นโมฆะ มันไม่เลวร้ายไปกว่า C # ในแง่นี้

  5. โดยทั่วไปสหภาพแรงงานที่เลือกปฏิบัติจะถูกรวบรวมตามลำดับชั้น แต่มักจะมีการแสดงที่ค่อนข้างเป็นมิตรใน C # ฉันจะบอกว่าทำเครื่องหมายด้วย[<AllowNullLiteral>]และใช้มัน

  6. ฟังก์ชัน Curried สร้างค่าฟังก์ชันซึ่งไม่ควรใช้

  7. ฉันพบว่ามันดีกว่าที่จะยอมรับโมฆะมากกว่าที่จะขึ้นอยู่กับว่ามันถูกจับที่อินเทอร์เฟซสาธารณะและไม่สนใจมันภายใน YMMV

  8. มันสมเหตุสมผลมากที่จะใช้list/ map/ setภายใน IEnumerable<_>พวกเขาทั้งหมดจะสามารถสัมผัสผ่านอินเตอร์เฟซที่สาธารณะ นอกจากนี้seq, dictและSeq.readonlyมีประโยชน์บ่อย

  9. ดู # 6.

กลยุทธ์ที่คุณใช้ขึ้นอยู่กับประเภทและขนาดของไลบรารีของคุณ แต่จากประสบการณ์ของฉันการค้นหาจุดที่ดีระหว่าง F # และ C # ทำให้ต้องทำงานน้อยลงในระยะยาวมากกว่าการสร้าง API แยกกัน


ใช่ฉันเห็นว่ามีบางวิธีที่จะทำให้สิ่งต่างๆทำงานได้ฉันไม่มั่นใจทั้งหมด Re โมดูล หากคุณมีmodule Foo.Bar.Zooแล้วใน C # จะเป็นclass Fooใน Foo.Barnamespace แต่ถ้าคุณมีโมดูลที่ซ้อนกันเช่นmodule Foo=... module Bar=... module Zoo==นี้จะสร้างระดับFooกับการเรียนภายในและBar Zooเรื่องIEnumerableนี้อาจไม่สามารถยอมรับได้เมื่อเราจำเป็นต้องทำงานกับแผนที่และชุดต่างๆ เรื่องnullค่านิยมและAllowNullLiteral- เหตุผลหนึ่งที่ฉันชอบ F # ก็เพราะว่าฉันเบื่อnullภาษา C # จริง ๆ ไม่อยากให้ทุกอย่างเป็นมลพิษอีกแล้ว!
Philip P.

โดยทั่วไปชอบเนมสเปซมากกว่าโมดูลที่ซ้อนกันสำหรับการทำงานร่วมกัน Re: null ฉันเห็นด้วยกับคุณมันเป็นความถดถอยที่ต้องพิจารณา แต่สิ่งที่คุณต้องจัดการหาก API ของคุณจะถูกใช้จาก C #
Daniel

2

แม้ว่าอาจจะเป็นการใช้งานมากเกินไป แต่คุณสามารถพิจารณาเขียนแอปพลิเคชันโดยใช้Mono.Cecil (มีการสนับสนุนที่ยอดเยี่ยมในรายชื่อผู้รับจดหมาย) ซึ่งจะทำให้การแปลงในระดับ IL เป็นไปโดยอัตโนมัติ ตัวอย่างเช่นคุณใช้แอสเซมบลีของคุณใน F # โดยใช้ F # -style public API จากนั้นเครื่องมือจะสร้าง C # -friendly wrapper ขึ้นมา

ตัวอย่างเช่นใน F # เห็นได้ชัดว่าคุณจะใช้option<'T>( Noneโดยเฉพาะ) แทนที่จะใช้nulllike ใน C # เขียนกำเนิดเสื้อคลุมสำหรับสถานการณ์สมมตินี้ควรจะค่อนข้างง่าย: วิธีการห่อหุ้มจะเรียกวิธีการเดิม: ถ้ามันของค่าตอบแทนที่ถูกSome xแล้วกลับมิฉะนั้นผลตอบแทนxnull

คุณจะต้องจัดการกรณีที่Tเป็นประเภทค่าเช่นไม่เป็นโมฆะ คุณจะต้องรวมค่า return ของวิธี wrapper เข้าไปNullable<T>ซึ่งจะทำให้เจ็บปวดเล็กน้อย

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


ฟังดูน่าสนใจ. จะใช้วิธีMono.Cecilการเขียนโปรแกรมโดยทั่วไปเพื่อโหลดแอสเซมบลี F # ค้นหารายการที่ต้องการการแมปและพวกเขาเปล่งแอสเซมบลีอื่นด้วย C # wrapper หรือไม่ ฉันเข้าใจถูกไหม
Philip P.

@ Komrade P. : คุณก็ทำได้เช่นกัน แต่มันซับซ้อนกว่านั้นมากเพราะคุณจะต้องปรับการอ้างอิงทั้งหมดเพื่อชี้ไปที่สมาชิกของแอสเซมบลีใหม่ จะง่ายกว่ามากถ้าคุณเพิ่มสมาชิกใหม่ (ตัวห่อ) ลงในแอสเซมบลีที่เขียนด้วย F # ที่มีอยู่
ShdNx

2

ร่างหลักเกณฑ์การออกแบบส่วนประกอบ F # (สิงหาคม 2553)

ภาพรวม เอกสารนี้กล่าวถึงปัญหาบางประการที่เกี่ยวข้องกับการออกแบบและการเข้ารหัสส่วนประกอบ F # โดยเฉพาะอย่างยิ่งมันครอบคลุม:

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