2016-03-10 4 views
11

मैं this F# ROP article का पालन कर रहा हूं, और इसे देखने के लिए मुख्य रूप से यह देखने के लिए सी # में इसे पुन: पेश करने का निर्णय लिया। इस सवाल की लंबाई के लिए माफ़ी, लेकिन यदि आप आरओपी से परिचित हैं, तो इसका पालन करना बहुत आसान होगा।सी # में रेलवे ओरिएंटेड प्रोग्रामिंग - मैं स्विच फ़ंक्शन कैसे लिखूं?

उन्होंने साथ एक एफ # शुरू संघ भेदभाव ...

type Result<'TSuccess, 'TFailure> = 
    | Success of 'TSuccess 
    | Failure of 'TFailure 

... जो मैं एक सार RopValue वर्ग में अनुवाद किया है, और दो ठोस कार्यान्वयन (ध्यान दें कि मैं लोगों के वर्ग नाम बदल गए हैं कि मैं बेहतर समझा) ...

public abstract class RopValue<TSuccess, TFailure> { 
    public static RopValue<TSuccess, TFailure> Create<TSuccess, TFailure>(TSuccess input) { 
    return new Success<TSuccess, TFailure>(input); 
    } 
} 

public class Success<TSuccess, TFailure> : RopValue<TSuccess, TFailure> { 
    public Success(TSuccess value) { 
    Value = value; 
    } 
    public TSuccess Value { get; set; } 
} 

public class Failure<TSuccess, TFailure> : RopValue<TSuccess, TFailure> { 
    public Failure(TFailure value) { 
    Value = value; 
    } 
    public TFailure Value { get; set; } 
} 

मैं तुम्हें एक TSuccess वस्तु है, जो मान्यता कार्यों के पहले में खिलाया जाएगा, जो कि एक RopValue बनाने के लिए अनुमति देने के लिए एक स्थिर बनाने के लिए विधि गयी।

मैं फिर एक बाध्यकारी समारोह लिखने के बारे में गया। एफ # संस्करण निम्नानुसार था ...

let bind switchFunction twoTrackInput = 
    match twoTrackInput with 
    | Success s -> switchFunction s 
    | Failure f -> Failure f 

... जो सी # समकक्ष की तुलना में पढ़ने के लिए एक डोडल था! यदि इस लिखने के लिए एक सरल तरीका है, लेकिन यहाँ है कि मैं क्या के साथ आया है मैं नहीं जानता ...

public static RopValue<TSuccess, TFailure> Bind<TSuccess, TFailure>(this RopValue<TSuccess, TFailure> input, Func<RopValue<TSuccess, TFailure>, RopValue<TSuccess, TFailure>> switchFunction) { 
    if (input is Success<TSuccess, TFailure>) { 
    return switchFunction(input); 
    } 
    return input; 
} 

ध्यान दें कि मैं एक विस्तार समारोह के रूप में यह लिखा है, कि के रूप में मेरे लिए इसका इस्तेमाल करने की अनुमति दी एक और अधिक कार्यात्मक तरीके से।

एक व्यक्ति को मान्य करने के अपने उपयोग के मामले में लेते हुए, मैं तो एक व्यक्ति वर्ग ...

public class Person { 
    public string Name { get; set; } 
    public string Email { get; set; } 
    public int Age { get; set; } 
} 

लिखा था ... और एक के साथ मेरी पहली मान्यता समारोह ...

public static RopValue<Person, string> CheckName(RopValue<Person, string> res) { 
    if (res.IsSuccess()) { 
    Person person = ((Success<Person, string>)res).Value; 
    if (string.IsNullOrWhiteSpace(person.Name)) { 
     return new Failure<Person, string>("No name"); 
    } 
    return res; 
    } 
    return res; 
} 

लिखा था ईमेल और उम्र के लिए समान सत्यापन के कुछ जोड़े, मैं निम्नानुसार एक सत्यापन प्रमाणीकरण समारोह लिख सकता हूं ...

private static RopValue<Person, string> Validate(Person person) { 
    return RopValue<Person, string> 
    .Create<Person, string>(person) 
    .Bind(CheckName) 
    .Bind(CheckEmail) 
    .Bind(CheckAge); 
} 

यह ठीक काम करता है, और मुझे इस तरह कुछ करने के लिए सक्षम बनाता है ...

Person jim = new Person {Name = "Jim", Email = "", Age = 16}; 
RopValue<Person, string> jimChk = Validate(jim); 
Debug.WriteLine("Jim returned: " + (jimChk.IsSuccess() ? "Success" : "Failure")); 

हालांकि, मैं जिस तरह से मैं यह कर दिया है के साथ कुछ मुद्दे हैं। सबसे पहले यह है कि सत्यापन कार्यों के लिए आपको रोपवैल्यू में पास करने की आवश्यकता होती है, सफलता या विफलता के लिए इसे जांचें, अगर सफलता हो, तो व्यक्ति को खींचें और फिर उसे सत्यापित करें। अगर विफलता है, तो बस इसे वापस करें।

इसके विपरीत, उसकी मान्यता कार्यों ले लिया एक व्यक्ति (के बराबर), और लौट आए (एक परिणाम है, जो के बराबर है) एक RopValue ...

let validateNameNotBlank person = 
    if person.Name = "" then Failure "Name must not be blank" 
    else Success person 

यह बहुत सरल है, लेकिन मैं सी # में यह कैसे करना है यह काम करने में असमर्थ था।

एक और मुद्दा यह है कि हम एक सफलता <> के साथ मान्यता श्रृंखला शुरू करते हैं, इसलिए सबसे पहले मान्यता समारोह हमेशा से कुछ वापस आ जाएगी है "अगर" ब्लॉक, या तो एक विफलता <> यदि सत्यापन विफल हुआ, या एक सफलता <> अगर हम चेक पिछले कर चुके हैं। यदि कोई फ़ंक्शन विफलता <> लौटाता है, तो सत्यापन श्रृंखला में अगला फ़ंक्शन कभी भी नहीं कहा जाता है, इसलिए यह पता चला है कि हम जानते हैं कि इन विधियों को कभी विफल नहीं किया जा सकता है <>।इसलिए, इन कार्यों में से प्रत्येक की अंतिम पंक्ति कभी भी नहीं पहुंच सकती है (अजीब मामले के अलावा, जिसे आपने मैन्युअल रूप से विफलता <> बनाया है और इसे शुरुआत में पास कर दिया है, लेकिन यह व्यर्थ होगा)।

फिर उन्होंने सत्यापन कार्यों को जोड़ने के लिए एक स्विच ऑपरेटर (> =>) बनाया। मैंने ऐसा करने की कोशिश की, लेकिन इसे काम नहीं कर सका। फ़ंक्शन पर लगातार कॉल करने के लिए, ऐसा लगता है कि मुझे एक Func <> पर एक एक्सटेंशन विधि रखना होगा, जो मुझे नहीं लगता कि आप कर सकते हैं। मुझे इस तक मिल गया ...

public static RopValue<TSuccess, TFailure> Switch<TSuccess, TFailure>(this Func<TSuccess, RopValue<TSuccess, TFailure>> switch1, Func<TSuccess, RopValue<TSuccess, TFailure>> switch2, TSuccess input) { 
    RopValue<TSuccess, TFailure> res1 = switch1(input); 
    if (res1.IsSuccess()) { 
    return switch2(((Success<TSuccess, TFailure>)res1).Value); 
    } 
    return new Failure<TSuccess, TFailure>(((Failure<TSuccess, TFailure>)res1).Value); 
} 

... लेकिन इसका उपयोग कैसे किया जा सकता है।

तो, क्या कोई यह समझा सकता है कि मैं बाइंड फ़ंक्शन कैसे लिखूंगा ताकि वह एक व्यक्ति ले सके और रोपवैल्यू (जैसे उसके) को वापस कर सके? मैं एक स्विच फ़ंक्शन कैसे लिखूं जो मुझे सरल सत्यापन कार्यों को जोड़ने की अनुमति देगा?

मेरे कोड पर कोई अन्य टिप्पणी स्वागत है। मुझे यकीन नहीं है कि यह कहीं भी साफ और सरल के रूप में कहीं भी हो सकता है।

+0

यह सब मुझे 'त्रुटि ' monad के समान होने पर हमला करता है। क्या आपको लगता है कि यह 'TFailure' प्रकार के साथ प्रभावी रूप से' अपवाद 'के बराबर है? – Enigmativity

+0

@ मार्क ट्वेन उद्धृत करने के लिए निष्क्रियता, "मैं जल्दी जवाब देने में सक्षम होने से प्रसन्न था, मैंने कहा कि मुझे नहीं पता था!" - मैं एक सी # प्रोग्रामर हूं, एक ही समय में एफ # और कार्यात्मक प्रोग्रामिंग सीख रहा हूं। मैंने मोनैड पर चर्चा की है, लेकिन अभी तक काम नहीं किया है कि वे क्या हैं। –

+0

आपका 'रोपवेल्यू' प्रकार एक मोनड है। मूल रूप से monads सामान्य प्रकार सुपर शक्तियों देते हैं। – Enigmativity

उत्तर

4

आपका Bind समारोह गलत प्रकार है, यह होना चाहिए:

public static RopValue<TOut, TFailure> Bind<TSuccess, TFailure, TOut>(this RopValue<TSuccess, TFailure> input, Func<TSuccess, RopValue<TOut, TFailure>> switchFunction) { 
    if (input is Success<TSuccess, TFailure>) { 
    return switchFunction(((Success<TSuccess, TFailure>)input).Value); 
    } 
    return new Failure<TOut, TFailure>(((Failure<TSuccess, TFailure>)input).Value); 
} 

Func पैरामीटर Bind अपने क्रियान्वयन के लिए पारित सिर्फ TSuccess के बजाय एक RopValue<TSuccess, TFailure> पैरामीटर लेता है। इसका अर्थ यह है कि फ़ंक्शन को इनपुट पर उसी मिलान को दोहराने की आवश्यकता है जो Bind विधि आपके लिए करना चाहिए।

ताकि आप इसे आधार वर्ग को स्थानांतरित कर सकता है इस प्रकार के पैरामीटर की संख्या की वजह से थोड़ा बोझल हो सकता है:

public abstract RopValue<TOut, TFailure> Bind<TOut>(Func<TSuccess, RopValue<TOut, TFailure> f); 

public class Success<TSuccess, TFailure> : RopValue<TSuccess, TFailure> { 
    public override RopValue<TOut, TFailure> Bind<TOut>(Func<TSuccess, RopValue<TOut, TFailure> f) { 
     return f(this.Value); 
    } 
} 

public class Failure<TSuccess, TFailure> : RopValue<TSuccess, TFailure> { 
    public override RopValue<TOut, TFailure> Bind<TOut>(Func<TSuccess, RopValue<TOut, TFailure> f) { 
     return new Failure<TOut, TFailure>(this.Value); 
    } 
} 

फिर आप श्रृंखला के शुरू में एक डमी मूल्य बनाने से बचना कर सकते हैं:

private static RopValue<Person, string> Validate(Person person) { 
    return CheckName(person) 
    .Bind(CheckEmail) 
    .Bind(CheckAge); 
} 
+0

अब इसे देखने का उचित मौका था। जब आप कहते हैं कि मेरे बाइंड फ़ंक्शन में गलत प्रकार था, जहां तक ​​मैं देख सकता हूं, केवल वास्तविक अंतर यह है कि आपने इनपुट और आउटपुट प्रकार को मानने के मेरे अवचेतन सरलीकरण को ठीक किया होगा। निश्चित नहीं है कि वह मेरे मामले में इसका उपयोग करने में सक्षम क्यों होगा, यह देखते हुए कि मैं एक व्यक्ति वर्ग के माध्यम से सभी तरह से काम कर रहा हूं। क्या आप इसे थोड़ा और समझा सकते हैं? –

+0

इसके अलावा, मैंने बाइंड फ़ंक्शन को बेस क्लास में ले जाने का प्रयास किया, जो सफलता के लिए ठीक था, क्योंकि मैंने अभी स्विचफंक्शन (वैल्यू) वापस कर दिया था, लेकिन मैं विफलता वर्ग में क्या काम नहीं कर सका। मेरा विचार था "यह" (विफलता ऑब्जेक्ट स्वयं होने के नाते, जो निश्चित रूप से एक रोपवैल्यू ऑब्जेक्ट भी है) को वापस करना था, लेकिन इसने एक कंपाइलर त्रुटि दी कि यह एक विफलता को रोपवैल में परिवर्तित नहीं कर सका। कास्टिंग ने या तो मदद नहीं की। मुझे यहाँ क्या याद आया? धन्यवाद फिर से –

+0

@AvrohomYisroel - अद्यतन देखें। – Lee

1

ली सही है कि आपके बाइंड फ़ंक्शन को गलत तरीके से परिभाषित किया गया है।

बाइंड हमेशा एक प्रकार हस्ताक्षर कि लगता है कि होना चाहिए: m<'a> -> ('a -> m<'b>) -> m<'b>

मैं इसे इस तरह से परिभाषित किया लेकिन ली के कार्यात्मक रूप से समान है:

public static RopValue<TSuccess2, TFailure> Bind<TSuccess, TSuccess2, TFailure>(this RopValue<TSuccess, TFailure> input, Func<TSuccess, RopValue<TSuccess2, TFailure>> switchFunction) 
{ 
    if (input.IsSuccess) 
    { 
     return switchFunction(((Success<TSuccess,TFailure>)input).Value); 
    } 
    return new Failure<TSuccess2, TFailure>(((Failure<TSuccess, TFailure>)input).Value); 
} 

Kleisli संरचना (>=>) हस्ताक्षर की तरह लग रहा टाइप होते हैं:

public static Func<TSuccess, RopValue<TSuccess2, TFailure>> Kleisli<TSuccess, TSuccess2, TFailure>(this Func<TSuccess, RopValue<TSuccess, TFailure>> switch1, Func<TSuccess, RopValue<TSuccess2, TFailure>> switch2) 
{ 
    return (inp => switch1(inp).Bind(switch2)); 
} 
: ('a -> m<'b>) -> ('b -> m<'c>) -> 'a -> m<'c>

आपको लगता है कि बाँध का उपयोग कर परिभाषित कर सकते हैं

आप Func पर विस्तार तरीकों को परिभाषित कर सकते हैं, लेकिन चाल संकलक है कि उन विस्तार तरीके उपलब्ध हैं देखने के लिए हो रही है, कुछ इस तरह काम करेगा:

Func<Entry, RopValue<Request, string>> checkEmail = CheckEmail; 
var combined = checkEmail.Kleisli(CheckAge); 
RopValue<Request, string> result = combined(request); 

कहाँ request मान्य करने के लिए अपने डेटा है।

ध्यान दें कि Func प्रकार का एक प्रकार बनाकर, यह हमें विस्तार विधि का उपयोग करने की अनुमति देता है।

+0

मेरा उत्तर सफलता प्रकार को बदलने की अनुमति देता है, यही कारण है कि 'TOUT' पैरामीटर है। – Lee

+0

@Lee आप सही हैं, मैं क्षमा चाहता हूं, मैं स्पष्ट रूप से पागल हो रहा हूं। – TheInnerLight

संबंधित मुद्दे