Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

Pattern matching σε C#;

Îåêßíçóå áðü ôï ìÝëïò anjelinio. Τελευταία δημοσίευση από το μέλος Παναγιώτης Καναβός στις 12-04-2010, 12:05. Υπάρχουν 8 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  03-11-2008, 14:54 45934

    Pattern matching σε C#;

    Θυμάμαι απο πέρσι που άρχισα να κοιτάω κάτι γλώσσες σαν την F# και την Erlang, πόσο εντύπωση μου είχε κάνει η
    ευκολία ενός χαρακτηριστικού τους, ονόματι pattern matching.

    Π.χ ... για να ορίσεις ένα enumeration στην F#, γράφεις κάτι σαν το παρακάτω:

    type shape = Circle | Square | Triangle | EquilateralTriangle | Rectangle | Pentagon;;

    Τώρα, σε περίπτωση που θες να εκτελέσεις κάτι διαφορετικό ανάλογα με την τιμή του shape, ένα switch statement δηλαδή, γράφεις
    το εξής:

    let numberOfSides(aShape:shape) =
    match (aShape) with
    | Circle -> 0
    | Square -> 4
    | EquilateralTriangle -> 3
    | Triangle -> 3
    | Rectangle -> 4
    | Pentagon -> 5;;


    Εδώ, match-άρει για σένα την τιμή, και εκτελεί το κατάλληλο lambda που του έχεις πεί. Δείτε όμως πόσο εύκολο και γρήγορο είναι το syntax.

    Στη C# ( και στη VB ? ) το switch είναι περιορισμένο σε συγκεκριμένους τύπους. Δε μπορείς δηλαδή εύκολα να βρείς αντίστοιχο syntax, και χάνεσαι
    σε if-else και nesting κτλ κτλ και βράστα.

    Έψαχνα λοιπόν κάποιο τρόπο που θα μπορούσε να γίνει emulate αυτό το syntax σε C#, κι αν αξίζει τον κόπο και τέτοια.
    Έφτασα λοιπόν στο παρακάτω αποτέλεσμα ...

    When<int> matchInt = new When<int>() {
                        { (i) => i == 0, (i) => Console.WriteLine("zero: {0}", i) },
                        { (i) => i > 0, (i) => Console.WriteLine("greater: {0}", i) },
                        { (i) => true, (i) => Console.WriteLine("always on ...  {0}", i) }
                    };

     matchInt[8765]();

    Η κλάσση When, είναι απλώς subclass του Dictionary<Predicate<T>, Action<T>> για το ευκολότερο syntax. Κατα τ'άλλα, έχει ένα indexer ακόμα, ο οποίος επιστρέφει - προσοχή τώρα :D - ένα function, το οποιο θα τρέξει όλα τα κλειδιά στο dictionary - τα οποία είναι functions που επιστρέφουν true/false - και αν πάρει πίσω true, θα τρέξει και το Αction<Τ> που έχει για value ...  μπερδευτικό;  Είναι το foreach στη μέθοδο Do, διαβάζεται πιο εύκολα απο ότι περιγράφεται ...

    class When<TValueType> : Dictionary<Predicate<TValueType>, Action<TValueType>> {

                protected virtual void Do(TValueType input) {
                    foreach (Predicate<TValueType> predicate in this.Keys.Where((p) => null != this[p]))
                        if (predicate(input))
                            this[predicate](input);
                }

                public Action this[TValueType input] {
                    get {
                        return () => Do(input);
                    }
                }
            }


    Όλα αυτά λοιπόν, για να μπορείς να γράψεις ένα  matchInt[123]();  ...  Τί κέρδισα τελικά;

    Νομίζω ότι είναι σημαντικό αυτό που δε φαίνεται. Μέσα σε ένα απλό dictionary, έχω αποθηκεύσει τρόπον τινά τις μεθόδους οι οποίες κάνουν validate ένα οποιοδήποτε object. Απο τη στιγμή που έχω πεί Action<int> validateOne = matchInt[1]; έχω ένα function το οποιο θα κάνει validate το 1, ή ένα οποιοδήποτε by ref type, και θα εκτελέσει conditional λογική.

    Αυτή τη μέθοδο μπορώ να την καλέσω τώρα, ή να την αποθηκεύσω σε ένα variable για να τη χρησιμοποιήσω μετά, και λίγο αργότερα και ξανά και ξανά. Κι είναι μόνο ένα variable :] ... δεν παίρνει καν παραμέτρους :D

    Και κάπου εδώ, αρχίζει η φαντασία. Αν μπορείς να κάνεις serialize αυτά τα "if-then", τότε έχεις ένα μίνι rules framework. Βάζοντας απο πάνω και μια μίνιμαλ βιβλιοθήκη απο βασικά structures - setProperty, getProperty, BelongsToRegion, whatever ..  - τότε έχεις δώσει ένα μικρό vocabulary στη "χρήστη", με το οποίο μπορεί να σχεδιάζει interactions και rules πολύ εύκολα.

    Ήδη σκεφτομαι πώς θα μπορούσα να το χρησιμοποιήσω για να κάνω display/hide/disable/validate controls σε μια ASP.NET σελίδα.
     
    ... κι αν μπορούσα απο το ίδιο xml να τα κάνω deserialize και σε javascript;
    ... αλλά εδώ πλέον αρχίζει πραγματική επιστημονική φαντασία :D
     
    Καλό μας απόγευμα ! 



    Angel
    O:]
  •  07-11-2008, 13:41 46082 σε απάντηση της 45934

    Απ: Pattern matching σε C#;

    Καλησπέρα φιλε Άγγελε, περιττό να αναφέρω ότι χαίρομαι ιδιαιτερα που ασχολείσαι με F# και FP γενικότερα. Στα δικά μου C# projects χρησιμοποιώ αρκετά Pattern Matching και έχω υλοποιήσει κάτι που πιστεύω ότι θα το βρείς ενδιαφέρον...


    Υλικά κατασκευής:

    Μια abstract class για την απαραίτητη αφαίρεση από την πραγματικότητα.

    public abstract class PatternMatch<TMatchValue, TResult>
    {
       private readonly Predicate<TMatchValue> predicateFunc;
       private readonly Func<TMatchValue, TResult> resultFunc;
       private readonly PatternMatch<TMatchValue, TResult> linkPatternMatch;


       public PatternMatch(Predicate<TMatchValue> predicateFunc, Func<TMatchValue, TResult> resultFunc)
          : this(predicateFunc, resultFunc, null)
       {
       }

       public PatternMatch(Predicate<TMatchValue> predicateFunc, Func<TMatchValue, TResult> resultFunc, PatternMatch<TMatchValue, TResult> linkPatternMatch)
       {
          if (predicateFunc == null) throw new ArgumentNullException("predicateFunc");
          if (resultFunc == null) throw new ArgumentNullException("resultFunc");

          this.predicateFunc = predicateFunc;
          this.resultFunc = resultFunc;
          this.linkPatternMatch = linkPatternMatch;
       }

       public Predicate<TMatchValue> PredicateFunc { get { return predicateFunc; } }
       public Func<TMatchValue, TResult> ResultFunc { get { return resultFunc; } }


       #region Match
       public TResult Match(TMatchValue value)
       {
          foreach (PatternMatch<TMatchValue, TResult> patternMatch in FromRootToBottom(this))
          {
             if (patternMatch.PredicateFunc(value))
                return patternMatch.ResultFunc(value);
          }
          throw new MatchNotFoundException(String.Format("Incomplete pattern match, match value {0}", value));
       }

       private IEnumerable<PatternMatch<TMatchValue, TResult>> FromRootToBottom(PatternMatch<TMatchValue, TResult> patternMatch)
       {
          if (patternMatch.linkPatternMatch == null)
             yield return patternMatch;
          else
    {
             foreach (PatternMatch<TMatchValue, TResult> enumeratedPatternMatch in FromRootToBottom(patternMatch.linkPatternMatch))
                yield return enumeratedPatternMatch;
             yield return patternMatch;

          }
       }
       #endregion
    }

    Κάποιες απλές και ταπεινές concrete classes για τις διαφορες περιπτώσεις

    public class WithPatternMatch<TMatchValue, TResult> : PatternMatch<TMatchValue, TResult>
    {

       public WithPatternMatch(Predicate<TMatchValue> predicateFunc, Func<TMatchValue, TResult> resultFunc)
          : this(predicateFunc, resultFunc, null)
       {
       }

       public WithPatternMatch(Predicate<TMatchValue> predicateFunc, Func<TMatchValue, TResult> resultFunc, PatternMatch<TMatchValue, TResult> linkPatternMatch)
          : base(predicateFunc, resultFunc, linkPatternMatch)
       {
       }

    }

    public class ElsePatternMatch<TMatchValue, TResult> : PatternMatch<TMatchValue, TResult>
    {
       public ElsePatternMatch(Func<TMatchValue, TResult> resultFunc, PatternMatch<TMatchValue, TResult> linkPatternMatch)
          : base(value => true, resultFunc, linkPatternMatch)
       {
       }
    }

    Αλλη μια ταπεινή class για τα Incompete matches

    public class MatchNotFoundException : Exception
    {
       public MatchNotFoundException(string message) : base(message) { }
    }

    Και μια static class για bootstrap και extension methods για fluent style

    public static class PatternMatch
    {
       #region With
       public static WithPatternMatch<TMatchValue, TResult> With<TMatchValue, TResult>(this WithPatternMatch<TMatchValue, TResult> patternMatch, Predicate<TMatchValue> predicateFunc, Func<TMatchValue, TResult> resultFunc)
       {
          return new WithPatternMatch<TMatchValue, TResult>(predicateFunc, resultFunc, patternMatch);
       }

       public static WithPatternMatch<TMatchValue, TResult> With<TMatchValue, TResult>(Predicate<TMatchValue> predicateFunc, Func<TMatchValue, TResult> resultFunc)
       {
          return With(null, predicateFunc, resultFunc);
       }

       public static WithPatternMatch<TMatchValue, TResult> With<TMatchValue, TResult>(this WithPatternMatch<TMatchValue, TResult> patternMatch, Predicate<TMatchValue> predicateFunc, Func<TResult> resultFunc)
       {
          return With(patternMatch, predicateFunc, value => resultFunc());
       }

       public static WithPatternMatch<TMatchValue, TResult> With<TMatchValue, TResult>(Predicate<TMatchValue> predicateFunc, Func<TResult> resultFunc)
       {
          return With(null, predicateFunc, resultFunc);
       }
       #endregion

       #region Else
       public static ElsePatternMatch<TMatchValue, TResult> Else<TMatchValue, TResult>(this WithPatternMatch<TMatchValue, TResult> patternMatch, Func<TMatchValue, TResult> resultFunc)
       {
          return new ElsePatternMatch<TMatchValue, TResult>(resultFunc, patternMatch);
       }

       public static ElsePatternMatch<TMatchValue, TResult> Else<TMatchValue, TResult>(this WithPatternMatch<TMatchValue, TResult> patternMatch, Func<TResult> resultFunc)
       {
          return Else(patternMatch, value => resultFunc());
       }
       #endregion
    }

    Τα ανακατεύουμε καλά και ορίστε το αποτέλεσμα

    var pattern =
       PatternMatch.With<int, string>(value => value == 1, value => "One ")
                                  .With(value => value == 2, value => "Two ")
                                  .With(value => value == 3, value => "Three ")
                                  .Else(() => "Else");

    Console.WriteLine(pattern.Match(4));


    Palladinos Nick
    Software Engineer
    -----------------------
    The limits of my language mean the limits of my world. (Ludwig Wittgenstein)
  •  07-11-2008, 14:01 46084 σε απάντηση της 45934

    Απ: Pattern matching σε C#;

    Εγώ δεν θα γράψω διατριβή, απλά να υπενθυμίσω ότι στη "γιαγια" C++ χρησιμοποιούνται οι τεχνικές που δείχνει ο Άγγελος με templates για να επιτευχθεί το ίδιο αποτέλεσμα. Από το 1997 Stick out tongue. Αυτή είναι και η δύναμη των generics, η δημιουργία ευέλικτων και ισχυρών αλγόριθμων, όχι η δημιουργία ενός List<int>. Και αυτό είναι που χάνει η Java με την υλοποίηση των generics που έχει.

    Από την άλλη, μόνο όσοι ασχολούνται σοβαρά με την ανάπτυξη βιβλιοθηκών μπορούν να κάνουν καλό template/generic metaprogramming. Οι βιβλιοθήκες αυτές είναι εύκολες στη χρήση, αλλά δύσκολες στη δημιουργία. Γι αυτό και δεν έχουν φανεί ακόμα ακόμα κάποιες ευρέως αποδεκτές βιβλιοθήκες generics για .NET. Από την άλλη, ο Anders Hejlsberg προσθέτει functional δυνατότητες στη γλώσσα τόσο γρήγορα, που δεν προλαβαίνει κανείς να φτιάξει τις βιβλιοθήκες και θα είναι ήδη ξεπερασμένες!

    Η ουσία πάντως παραμένει ότι οι τεχνικές αυτές έρχονται από τη C++, έχουν ήδη μία ιστορία σχεδόν 10 ετών χρήσης μέσω του STL και του Boost, οπότε μπορούμε άνετα να τις αντιγράψουμε και να τις προσαρμόσουμε στις σημερινές γλώσσες.

    Immitation is the best form of flattery!


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  09-04-2010, 11:43 58009 σε απάντηση της 46082

    Απ: Pattern matching σε C#;

    PALLADIN:

    Τα ανακατεύουμε καλά και ορίστε το αποτέλεσμα

    var pattern =
       PatternMatch.With<int, string>(value => value == 1, value => "One ")
                                  .With(value => value == 2, value => "Two ")
                                  .With(value => value == 3, value => "Three ")
                                  .Else(() => "Else");

    Console.WriteLine(pattern.Match(4));

    ... ένας διάλογος που κρατάει ... 3 χρόνια ! :D  Ωραίο syntax ρε Νικόλα.


    Angel
    O:]
  •  09-04-2010, 13:00 58010 σε απάντηση της 58009

    Απ: Pattern matching σε C#;

    Κοίτα τί πήγες και θυμήθηκες. Εν τω μεταξύ έχω χαζέψει τόσο πολύ με το Sharepoint, που μου πήρε 15 λεπτά για να καταλάβω όχι τη συζήτηση, αλλά αυτό που είχα απαντήσει τότε!

    Τώρα που τα θυμήθηκα και με κατάλαβα, διαπιστώνω ότι έχω αλλάξει άποψη. lambdas, pattern matching και τα συναφή δεν είναι μόνο για βιβλιοθήκες ούτε είναι χρήσιμα μόνο στους guru - και ειδικά λόγω προϊόντων όπως το Sharepoint, όπου ένα SPItem μπορεί να είναι διαφορετικού τύπου, με διαφορετικά πεδία και διαφορετικό χειρισμό, το schema μπορεί να αλλάξει οποιαδήποτε στιγμή κλπ κλπ κλπ ακόμα και αν προέρχεται από την ίδια λίστα με ένα άλλο. Δυναμική εφαρμογή με στατικό API, μπλιάχ!

    Τώρα που πείσθηκα κι εγώ είπα να δοκιμάσω τί θα βγάλω με τη μέθοδο του ... Resharper. Βάζουμε δηλαδή τη σύνταξη και μέσω Resharper δημιουργούμε κώδικα μέχρι να κάνει compile. Κατέληξα στο παρακάτω (έκλεψα και λίγο επειδή χρησιμοποιώ Tuple<>).

    public class MatchResult<TValue,TResult>
        {
            private readonly List<Tuple<Func<TValue, bool>, Func<TValue, TResult>>> _patterns=new List<Tuple<Func<TValue, bool>, Func<TValue, TResult>>>();
            
            public MatchResult<TValue,TResult> With(Func<TValue, bool> condition, Func<TValue, TResult> action)
            {
                _patterns.Insert( 0,Tuple.Create(condition, action));
                return this;
            }
    
            public MatchResult<TValue, TResult> Else(Func<TResult> action)
            {
                _patterns.Add(new Tuple<Func<TValue, bool>, Func<TValue, TResult>>(v=>true, v=> action()));
                return this;
            }
    
            public TResult Match(TValue value)
            {
    
                var match = (from pattern in _patterns
                             where pattern.Item1(value)
                             select pattern.Item2).FirstOrDefault();
                return match == null ? default(TResult) : match(value);
            }
        }
    
        public static class PatternMatch
        {
            public static MatchResult<TValue,TResult> With<TValue, TResult>(Func<TValue, bool> condition, Func<TValue, TResult> action)
            {
                return new MatchResult<TValue, TResult>().With(condition, action);
            }
        }

    το οποίο βγαίνει μάλλον απλούστερο από τις προηγούμενες λύσεις. Μήπως ξέχασα τίποτε ? BTW, όταν αρχίζεις να παίζεις με τα tuples, αρχίζουν να σου λείπουν τα typedef.

     


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  09-04-2010, 22:57 58014 σε απάντηση της 58009

    Απ: Pattern matching σε C#;

    Καλησπέρα στους φίλους Άγγελο-Παναγιώτη.
    Άγγελε πως και θυμήθηκες αυτό το thread?

    Palladinos Nick
    Software Engineer
    -----------------------
    The limits of my language mean the limits of my world. (Ludwig Wittgenstein)
  •  12-04-2010, 10:41 58032 σε απάντηση της 58014

    Απ: Pattern matching σε C#;

    Ρε φίλε ... βαριόμουν απίστευτα να γράψω ακόμα ένα switch statement και έψαχνα κάτι ενδιαφέρον !!! χαχαχαχααα :)

    Άντε, καλή εβδομάδα μας !


    Angel
    O:]
  •  12-04-2010, 12:05 58038 σε απάντηση της 58032

    Απ: Pattern matching σε C#;

    Τώρα με το .NET 4 μπορείς να εκτελέσεις όλα τα conditions παράλληλα με ένα Parallel.ForEach ή ένα AsParallel, αν και μπορεί να σου βγει πιό  .... αργή λύση αν έχεις λίγα patterns και απλά conditions.
    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems