c# - एफ#और सी#दोनों के उपयोग के लिए एफ#पुस्तकालयों को डिजाइन करने के लिए सबसे अच्छा तरीका




f# (3)

आपको केवल Func या Action साथ फ़ंक्शन वैल्यू (आंशिक रूप से लागू फ़ंक्शंस इत्यादि) को लपेटना होगा, शेष स्वचालित रूप से परिवर्तित हो Func । उदाहरण के लिए:

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 # से दर्द मुक्त कर सकते हैं (मैं इसे हर समय करता हूं)।

साथ ही, एफ # इंटरऑपरेबिलिटी के मामले में सी # से अधिक लचीला है, इसलिए यह एक चिंता का विषय पारंपरिक पारंपरिक .NET शैली का पालन करना सबसे अच्छा है।

संपादित करें

मुझे लगता है कि अलग-अलग नामस्थानों में दो सार्वजनिक इंटरफेस बनाने के लिए आवश्यक काम, मुझे लगता है कि जब वे पूरक होते हैं तो केवल वांछित होता है या F # कार्यक्षमता सी # (जैसे कि इनलाइन फ़ंक्शंस, जो F #-विशिष्ट मेटाडेटा पर निर्भर करती है) से उपयोग करने योग्य नहीं है।

बदले में अपने अंक लेना:

  1. मॉड्यूल + बाइंडिंग और कन्स्ट्रक्टर-कम प्रकार + स्थिर सदस्य सी # में बिल्कुल समान दिखाई देते हैं, इसलिए यदि आप कर सकते हैं तो मॉड्यूल के साथ जाएं। आप सदस्य सी # मित्रतापूर्ण नाम देने के लिए CompiledNameAttribute का उपयोग कर सकते हैं।

  2. मैं गलत हो सकता हूं, लेकिन मेरा अनुमान है कि घटक दिशानिर्देश सिस्टम से पहले लिखे गए थे। ढांचे में जोड़ा जा रहा है। (पिछले संस्करणों में एफ # ने इसका स्वयं का ट्यूपल प्रकार परिभाषित किया था।) यह तब से छोटे प्रकार के लिए सार्वजनिक इंटरफ़ेस में Tuple का उपयोग करने के लिए अधिक स्वीकार्य हो गया है।

  3. यह वह जगह है जहां मुझे लगता है कि आपने सी # तरीके से काम किया है क्योंकि एफ # Task साथ अच्छी तरह से खेलता है लेकिन सी # Async साथ अच्छा नहीं खेलता है। आप सार्वजनिक विधि से लौटने से पहले Async.StartAsTask आंतरिक रूप से async का उपयोग कर सकते हैं।

  4. सी # से उपयोग के लिए एपीआई विकसित करते समय null गले लगाना सबसे बड़ी कमी हो सकती है। अतीत में, मैंने आंतरिक एफ # कोड में शून्य पर विचार करने से बचने के लिए सभी प्रकार की चाल की कोशिश की, लेकिन अंत में, सार्वजनिक कन्स्ट्रक्टरों के साथ प्रकारों को चिह्नित करना सर्वोत्तम था [<AllowNullLiteral>] और शून्य के लिए [<AllowNullLiteral>] जांचें। इस संबंध में यह सी # से भी बदतर नहीं है।

  5. भेदभाव वाले संघ आमतौर पर कक्षा पदानुक्रमों के लिए संकलित होते हैं लेकिन हमेशा सी # में अपेक्षाकृत अनुकूल प्रतिनिधित्व करते हैं। मैं कहूंगा, उन्हें [<AllowNullLiteral>] साथ चिह्नित करें और उनका उपयोग करें।

  6. करीबी कार्य फ़ंक्शन मान उत्पन्न करते हैं , जिनका उपयोग नहीं किया जाना चाहिए।

  7. मैंने पाया कि सार्वजनिक इंटरफ़ेस पर पकड़े जाने पर इसे निर्भर करने के लिए शून्य से गले लगाने के लिए बेहतर था और इसे आंतरिक रूप से अनदेखा कर दिया गया था। YMMV।

  8. यह आंतरिक रूप से list / map / set का उपयोग करने के लिए बहुत समझ में आता है। वे सभी सार्वजनिक इंटरफ़ेस के माध्यम से IEnumerable<_> रूप में उजागर किए जा सकते हैं। इसके अलावा, seq , dict , और Seq.readonly अक्सर उपयोगी होते हैं।

  9. # 6 देखें।

आप कौन सी रणनीति लेते हैं, आपकी लाइब्रेरी के प्रकार और आकार पर निर्भर करता है, लेकिन, मेरे अनुभव में, एफ # और सी # के बीच मीठा स्थान ढूंढने के लिए लंबे समय तक अलग-अलग एपीआई बनाने के बजाय कम काम की आवश्यकता होती है।

मैं एफ # में एक पुस्तकालय डिजाइन करने की कोशिश कर रहा हूँ। पुस्तकालय एफ # और सी # दोनों के उपयोग के लिए अनुकूल होना चाहिए।

और यह वह जगह है जहां मैं थोड़ा फंस गया हूँ। मैं इसे एफ # दोस्ताना बना सकता हूं, या मैं इसे सी # अनुकूल बना सकता हूं, लेकिन समस्या यह है कि यह दोनों के लिए अनुकूल कैसे बनें।

यहाँ एक उदाहरण है। कल्पना करें कि मेरे पास F # में निम्न कार्य है:

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

यह एफ # से पूरी तरह प्रयोग योग्य है:

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

सी # में, 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)

अब यह सी # में पूरी तरह प्रयोग योग्य है:

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

लेकिन अब हमें F # से compose2 का उपयोग करने में समस्या है! अब मुझे Func और Action में सभी मानक एफ # funs लपेटना है, जैसे:

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 # और C # उपयोगकर्ताओं दोनों के लिए F # में लिखी गई लाइब्रेरी के लिए प्रथम श्रेणी के अनुभव को कैसे प्राप्त कर सकते हैं?

अब तक, मैं इन दो दृष्टिकोणों से बेहतर कुछ भी नहीं कर सका:

  1. दो अलग-अलग असेंबली: एक एफ # उपयोगकर्ताओं को लक्षित, और सी # उपयोगकर्ताओं के लिए दूसरा।
  2. एक असेंबली लेकिन अलग-अलग नामस्थान: एफ # उपयोगकर्ताओं के लिए, और सी # उपयोगकर्ताओं के लिए दूसरा।

पहले दृष्टिकोण के लिए, मैं ऐसा कुछ करूंगा:

  1. एफ # प्रोजेक्ट बनाएं, इसे FooBarFs पर कॉल करें और इसे FooBarFs.dll में संकलित करें।

    • पुस्तकालय को पूरी तरह से F # उपयोगकर्ताओं को लक्षित करें।
    • .fsi फ़ाइलों से अनावश्यक सब कुछ छुपाएं।
  2. एक और एफ # प्रोजेक्ट बनाएं, अगर FooBarC को कॉल करें और इसे FooFar.dll में संकलित करें

    • स्रोत स्तर पर पहली एफ # परियोजना का पुन: उपयोग करें।
    • .fsi फ़ाइल बनाएं जो उस प्रोजेक्ट से सबकुछ छुपाती है।
    • .fsi फ़ाइल बनाएं जो सी # तरीके से लाइब्रेरी का खुलासा करती है, नाम, नामस्थान आदि के लिए सी # मुहावरे का उपयोग कर।
    • जहां आवश्यक हो वहां रूपांतरण कर, मूल पुस्तकालय में प्रतिनिधि रैपर बनाएं।

मुझे लगता है कि नामस्थान के साथ दूसरा दृष्टिकोण उपयोगकर्ताओं को भ्रमित कर सकता है, लेकिन फिर आपके पास एक असेंबली है।

प्रश्न: इनमें से कोई भी आदर्श नहीं है, शायद मुझे किसी प्रकार का कंपाइलर ध्वज / स्विच / अट्रिबेट या किसी प्रकार की चाल याद आ रही है और ऐसा करने का एक बेहतर तरीका है?

प्रश्न: क्या किसी और ने कुछ हासिल करने की कोशिश की है और यदि ऐसा है तो आप इसे कैसे करते हैं?

संपादित करें: स्पष्टीकरण के लिए, प्रश्न न केवल कार्यों और प्रतिनिधियों के बारे में है बल्कि एक एफ # पुस्तकालय के साथ सी # उपयोगकर्ता का समग्र अनुभव है। इसमें नामस्थान, नामकरण सम्मेलन, मुहावरे और ऐसे ही शामिल हैं जो सी # के मूल निवासी हैं। असल में, एक सी # उपयोगकर्ता यह पता लगाने में सक्षम नहीं होना चाहिए कि पुस्तकालय एफ # में लिखा गया था। और इसके विपरीत, एक एफ # उपयोगकर्ता को सी # लाइब्रेरी से निपटने की तरह महसूस करना चाहिए।

संपादित करें 2:

मैं अब तक उत्तर और टिप्पणियों से देख सकता हूं कि मेरे प्रश्न में आवश्यक गहराई की कमी है, शायद अधिकतर केवल एक उदाहरण के उपयोग के कारण जहां एफ # और सी # के बीच इंटरऑपरेबिलिटी मुद्दे उत्पन्न होते हैं, कार्यों का मुद्दा मूल्यों का मुद्दा होता है। मुझे लगता है कि यह सबसे स्पष्ट उदाहरण है और इसलिए इसने मुझे सवाल पूछने के लिए इसका इस्तेमाल करने का नेतृत्व किया, लेकिन उसी टोकन ने इंप्रेशन दिया कि यह एकमात्र मुद्दा है जिसके बारे में मैं चिंतित हूं।

मुझे और ठोस उदाहरण प्रदान करने दें। मैंने सबसे उत्कृष्ट एफ # घटक डिजाइन दिशानिर्देश दस्तावेज़ के माध्यम से पढ़ा है (इसके लिए बहुत धन्यवाद @gradbot!)। दस्तावेज़ में दिशानिर्देश, यदि उपयोग किए जाते हैं, तो कुछ मुद्दों को हल करें लेकिन सभी नहीं।

दस्तावेज़ को दो मुख्य भागों को विभाजित किया गया है: 1) एफ # उपयोगकर्ताओं को लक्षित करने के लिए दिशानिर्देश; और 2) सी # उपयोगकर्ताओं को लक्षित करने के लिए दिशानिर्देश। कहीं भी यह नाटक करने का प्रयास नहीं करता है कि एक समान दृष्टिकोण होना संभव है, जो वास्तव में मेरे प्रश्न का प्रतीक है: हम एफ # को लक्षित कर सकते हैं, हम सी # को लक्षित कर सकते हैं, लेकिन दोनों को लक्षित करने के लिए व्यावहारिक समाधान क्या है?

याद दिलाने के लिए, लक्ष्य F # में लिखी गई लाइब्रेरी है, और जिसे F # और C # भाषाओं दोनों से idiomatically उपयोग किया जा सकता है।

यहां कीवर्ड मूर्खतापूर्ण है । मुद्दा सामान्य अंतःक्रियाशीलता नहीं है जहां विभिन्न भाषाओं में पुस्तकालयों का उपयोग करना संभव है।

अब उदाहरणों के लिए, जो मैं सीधे एफ # घटक डिजाइन दिशानिर्देशों से लेता हूं।

  1. मॉड्यूल + फ़ंक्शंस (एफ #) बनाम नेमस्पेस + प्रकार + फ़ंक्शंस

    • एफ #: अपने प्रकार और मॉड्यूल रखने के लिए नामस्थान या मॉड्यूल का उपयोग करें। बेवकूफ उपयोग मॉड्यूल में कार्यों को रखना है, उदाहरण के लिए:

      // library
      module Foo
      let bar() = ...
      let zoo() = ...
      
      
      // Use from F#
      open Foo
      bar()
      zoo()
      
    • सी #: वेनिला .NET एपीआई के लिए, अपने घटकों (मॉड्यूल के विपरीत) के लिए प्राथमिक संगठनात्मक संरचना के रूप में नामस्थान, प्रकार और सदस्यों का उपयोग करें।

      यह एफ # दिशानिर्देश के साथ असंगत है, और सी # उपयोगकर्ताओं को फिट करने के लिए उदाहरण को पुनः लिखे जाने की आवश्यकता होगी:

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

      ऐसा करने से, हम एफ # से बेवकूफ उपयोग तोड़ते हैं क्योंकि हम अब Foo साथ उपसर्ग किए बिना bar और zoo उपयोग नहीं कर सकते हैं।

  2. Tuples का उपयोग करें

    • एफ #: वापसी मूल्यों के लिए उपयुक्त होने पर tuples का उपयोग करें।

    • सी #: वेनिला .NET एपीआई में वापसी मान के रूप में tuples का उपयोग करने से बचें।

  3. async

    • एफ #: एफ # एपीआई सीमाओं पर एसिंक प्रोग्रामिंग के लिए Async का उपयोग करें।

    • सी #: .NET एसिंक्रोनस प्रोग्रामिंग मॉडल (BeginFoo, EndFoo) का उपयोग करके एसिंक्रोनस ऑपरेशंस का खुलासा करें, या F # Async ऑब्जेक्ट्स की बजाय .NET कार्यों (कार्य) को वापस करने के तरीके के रूप में करें।

  4. Option उपयोग

    • एफ #: अपवादों को बढ़ाने के बजाय रिटर्न प्रकारों के लिए विकल्प मानों का उपयोग करने पर विचार करें (एफ # -फेसिंग कोड के लिए)।

    • वेनिला .NET API में F # विकल्प मान (विकल्प) को वापस करने के बजाय TryGetValue पैटर्न का उपयोग करने पर विचार करें, और F # विकल्प मानों को तर्क के रूप में लेने के लिए विधि ओवरलोडिंग को प्राथमिकता दें।

  5. भेदभाव संघ

    • एफ #: पेड़-संरचित डेटा बनाने के लिए वर्ग पदानुक्रमों के विकल्प के रूप में भेदभाव वाले संघों का उपयोग करें

    • सी #: इसके लिए कोई विशिष्ट दिशानिर्देश नहीं है, लेकिन भेदभाव वाले संघों की अवधारणा सी #

  6. करीबी कार्य

    • एफ #: करीबी कार्यों एफ # के लिए idiomatic हैं

    • सी #: वेनिला .NET एपीआई में पैरामीटर की करी का उपयोग न करें।

  7. शून्य मानों के लिए जाँच कर रहा है

    • एफ #: यह एफ # के लिए मूर्ख नहीं है

    • सी #: वेनिला .NET एपीआई सीमाओं पर शून्य मानों की जांच करने पर विचार करें।

  8. एफ # प्रकार list , map , set , आदि का उपयोग

    • एफ #: एफ # में इनका उपयोग करने के लिए मूर्खतापूर्ण है

    • सी #: पैरामीटर के लिए .NET संग्रह इंटरफ़ेस प्रकार IENumerable और IDictionary का उपयोग करने पर विचार करें और वेनिला .NET API में वापसी मान। ( यानी एफ # list , map , set उपयोग न करें )

  9. कार्य प्रकार (स्पष्ट एक)

    • एफ #: एफ # कार्यों का उपयोग मूल्यों के रूप में एफ # के लिए मूर्खतापूर्ण है

    • सी #: वेनिला .NET एपीआई में एफ # फ़ंक्शन प्रकारों के वरीयता में .NET प्रतिनिधि प्रकार का उपयोग करें।

मुझे लगता है कि ये मेरे प्रश्न की प्रकृति को प्रदर्शित करने के लिए पर्याप्त होना चाहिए।

संयोग से, दिशानिर्देशों का आंशिक उत्तर भी है:

... वेनिला .NET पुस्तकालयों के लिए उच्च-ऑर्डर विधियों को विकसित करते समय एक सामान्य कार्यान्वयन रणनीति F # फ़ंक्शन प्रकारों का उपयोग करके सभी कार्यान्वयन को लेखक बनाना है, और फिर वास्तविक F # कार्यान्वयन के ऊपर प्रतिनिधियों का उपयोग करके पतली मुखौटा के रूप में सार्वजनिक API बनाते हैं।

संक्षेपित करते हुए।

एक निश्चित उत्तर है: कोई संकलक चाल नहीं है जिसे मैंने याद किया

दिशानिर्देश दस्तावेज़ के अनुसार, ऐसा लगता है कि पहले एफ # के लिए संलेखन और फिर .NET के लिए एक मुखौटा रैपर बनाना उचित रणनीति है।

सवाल तब व्यावहारिक कार्यान्वयन के संबंध में बना हुआ है:

  • अलग विधानसभाएं? या

  • अलग-अलग नामस्थान?

अगर मेरी व्याख्या सही है, तो टॉमस सुझाव देता है कि अलग-अलग नामस्थानों का उपयोग करना पर्याप्त होना चाहिए, और एक स्वीकार्य समाधान होना चाहिए।

मुझे लगता है कि मैं इस बात से सहमत हूं कि नामस्थानों की पसंद ऐसा है कि यह .NET / C # उपयोगकर्ताओं को आश्चर्यचकित या भ्रमित नहीं करता है, जिसका अर्थ है कि उनके लिए नामस्थान शायद ऐसा दिखाना चाहिए कि यह उनके लिए प्राथमिक नामस्थान है। एफ # उपयोगकर्ताओं को एफ # -स्पेसिफिक नेमस्पेस चुनने का बोझ उठाना होगा। उदाहरण के लिए:

  • FSharp.Foo.Bar -> F # सामना पुस्तकालय के लिए नामस्थान

  • Foo.Bar -> .NET wrapper के लिए नेमस्पेस, सी # के लिए idiomatic


डैनियल ने पहले से ही बताया है कि आपके द्वारा लिखे गए एफ # फ़ंक्शन के सी # मित्रतापूर्ण संस्करण को कैसे परिभाषित किया जाए, इसलिए मैं कुछ उच्च-स्तरीय टिप्पणियां जोड़ूंगा। सबसे पहले, आपको research.microsoft.com/en-us/um/cambridge/projects/fsharp/… पढ़ना चाहिए (पहले से ही ग्रेडबॉट द्वारा संदर्भित)। यह एक दस्तावेज़ है जो बताता है कि एफ # और .NET पुस्तकालयों को F # का उपयोग करके कैसे डिजाइन किया जाए और इसे आपके कई प्रश्नों का उत्तर देना चाहिए।

एफ # का उपयोग करते समय, मूल रूप से दो प्रकार के पुस्तकालय होते हैं जिन्हें आप लिख सकते हैं:

  • एफ # लाइब्रेरी को केवल एफ # से उपयोग करने के लिए डिज़ाइन किया गया है, इसलिए यह सार्वजनिक इंटरफ़ेस एक कार्यात्मक शैली में लिखा गया है (एफ # फ़ंक्शन प्रकार, टुपल्स, भेदभाव संघों आदि का उपयोग करके)

  • .NET लाइब्रेरी को किसी भी .NET भाषा (सी # और एफ # सहित) से उपयोग करने के लिए डिज़ाइन किया गया है और यह आमतौर पर .NET ऑब्जेक्ट-उन्मुख शैली का पालन करता है। इसका अर्थ यह है कि आप विधि के साथ कक्षाओं (और कभी-कभी विस्तार विधियों या स्थिर तरीकों के रूप में अधिकांश कार्यक्षमता का पर्दाफाश करेंगे, लेकिन ज्यादातर कोड ओओ डिजाइन में लिखा जाना चाहिए)।

आपके प्रश्न में, आप पूछ रहे हैं कि फ़ंक्शन संरचना को .NET लाइब्रेरी के रूप में कैसे बेनकाब करना है, लेकिन मुझे लगता है कि आपकी compose जैसे फ़ंक्शन .NET लाइब्रेरी के दृष्टिकोण से बहुत कम स्तर की अवधारणाएं हैं। आप उन्हें Func और Action साथ काम करने के तरीकों के रूप में बेनकाब कर सकते हैं, लेकिन शायद यह नहीं है कि आप सामान्य .NET लाइब्रेरी को पहली जगह कैसे डिज़ाइन करेंगे (शायद आप इसके बजाय बिल्डर पैटर्न का उपयोग करेंगे या ऐसा कुछ करेंगे)।

कुछ मामलों में (यानी संख्यात्मक पुस्तकालयों को डिजाइन करते समय जो वास्तव में .NET लाइब्रेरी शैली के साथ अच्छी तरह से फिट नहीं होते हैं), यह एक लाइब्रेरी को डिजाइन करने के लिए एक अच्छी समझ बनाता है जो एक पुस्तकालय में F # और .NET शैलियों दोनों को मिश्रित करता है। ऐसा करने का सबसे अच्छा तरीका सामान्य F # (या सामान्य .NET) API होना है और फिर अन्य शैली में प्राकृतिक उपयोग के लिए रैपर प्रदान करना है। रैपर अलग नामस्थान में हो सकते हैं (जैसे MyLibrary.FSharp और MyLibrary )।

आपके उदाहरण में, आप MyLibrary.FSharp में F # कार्यान्वयन छोड़ सकते हैं और उसके बाद MyLibrary नामस्थान में कुछ वर्ग की स्थैतिक विधि के रूप में .NET (C #-friendly) wrappers (कोड के समान डैनियल पोस्ट किया गया) जोड़ सकते हैं। लेकिन फिर, .NET लाइब्रेरी में फ़ंक्शन संरचना की तुलना में शायद अधिक विशिष्ट API होगा।


research.microsoft.com/en-us/um/cambridge/projects/fsharp/…

अवलोकन यह दस्तावेज़ F # घटक डिज़ाइन और कोडिंग से संबंधित कुछ मुद्दों को देखता है। विशेष रूप से, इसमें शामिल हैं:

  • किसी भी .NET भाषा से उपयोग के लिए "वेनिला" .NET पुस्तकालयों को डिजाइन करने के लिए दिशानिर्देश।
  • एफ # -to-F # पुस्तकालयों और एफ # कार्यान्वयन कोड के लिए दिशानिर्देश।
  • एफ # कार्यान्वयन कोड के लिए कोडिंग सम्मेलनों पर सुझाव




f#