Currying howCzęść NR.8 W poprzednim wpisie widzieliśmy przykłady jak Currying działa. Przykłady te były opisane na zmiennych zadeklarowanych, albo jako anonimowe metody, albo jako wyrażenia lambda. Zmieniliśmy te funkcje do postaci łańcucha wywołań funkcji, które przyjmują zawsze tylko jeden parametr.

Jak to jednak by wyglądało w C# , w kontekście klas. Jak mieć metodę Currying w stylu języków funkcjonalnych.

Jakie ja napotkałem problemy pisząc ten prosty przykład edukacyjny.

Mam więc klasę storage, która posiada pole statyczne reprezentujące metodę dodawania wpisów po Currying. Pola statyczne moim zadaniem w tym wypadku są akceptowalne, chociaż spróbujemy potem zrobić to samo przy użyciu metody.

public class Post { }
public class Comment { }
public class Autor { }
public static class Storage
{
    static Storage()
    {

    }
    public static bool AddPost(Post post, 
        List<Comment> comment, Autor autor)
    {
        //coś się dzieje, 
        //ale nas to nie interesuje
        return true;
    }

    public static readonly dynamic AddPostC =
        Functional.Curry<Post, List<Comment>, Autor, bool>(AddPost);
}

Ręczny Currying wydaje się stratą czasu dlatego skorzystałem z metody pomocniczej Curry z biblioteki FCSlib.

Dla ułatwienia skorzystałem ze słowa dynamic. Nie muszę więc pisać dokładnie zwracanego typu funkcji po Currying.

image

Ma to jednak swoje wady, typ dynamiczny utworzy się w trakcie działania programu i Visual Studio na tym etapie nie jest w stanie mi powiedzieć, co to jest.

Oznacza to, że nie będę mógł skorzystać z pomocy podpowiadacza kodu.

public static readonly dynamic AddPostC =
    Functional.Curry<Post, List<Comment>, Autor, bool>(AddPost);

Pierwszy problem jednak już się pojawił. Otóż ważna jest kolejność i ponieważ korzystam z metody Currying z FCLib, mam teraz dwa wyjścia.

albo…

  • zmienię kolejność parametrów w metodzie
  • napiszę swoją składnię metody Currying, która zwróci odpowiednią kolejność wywołań
  • napiszę swoją funkcję transformującą, która zmieni mi kolejność parametrów

Postanowiłem zmienić szyk parametrów w metodzie. W C# istnieje mechanizm zmiany kolejności parametrów w trakcie uruchamiania metody, ale nie ma możliwości dynamicznej zmiany ciała metody na taki, aby można było wrzucić ją do odpowiedniej delegaty.

image

Po tej zmianie postanowiłem się zmierzyć z faktem, że typ dynamiczny nie daje mi pomocnej ręki, co do tego, jak mam wywołać tę lawinę połączonych ze sobą funkcji.

Gdyby C# był językiem funkcjonalnym być może taki mechanizm istniałby w kompilatorze, w Visual Studio co do typów dynamic, ale na razie możemy sobie pomarzyć.

public static class Storage
{
    static Storage()
    {

    }
    public static bool AddPost(List<Comment> comment, Autor autor,
        Post post)
    {
        //coś się dzieje, 
        //ale nas to nie interesuje
        return true;
    }

    public static readonly dynamic AddPostC =
        Functional.Curry<List<Comment>,Autor, Post, bool>(AddPost);
}

Zapominam więc o typie Dynamic i piszemy uciążliwy typy generycznej delegaty po Curryingu. 

image

Teraz Visual Studio wie jak mi pomóc. Kod wygląda tak.

public static class Storage
{
    static Storage()
    {

    }
    public static bool AddPost(List<Comment> comment, Autor autor,
        Post post)
    {
        //coś się dzieje, 
        //ale nas to nie interesuje
        return true;
    }

    public static readonly Func<List<Comment>,Func<Autor,Func<Post,bool>>> 
        AddPostC =
        Functional.Curry<List<Comment>,Autor, Post, bool>(AddPost);

    public static readonly dynamic AddPostC2 =
        Functional.Curry<List<Comment>, Autor, Post, bool>(AddPost);
}

Zakładając, że autor  i  lista komentarzy zawsze będzie taka sama mogę stworzyć definicję funkcji, która zawsze będzie miała te domyślne parametry.

var addPostwithOnlyPost = Storage.AddPostC(new List<Comment>())
(new Autor());

var addPostwithOnlyPost2 = Storage.AddPostC2(new List<Comment>())
(new Autor());

To byłoby wspaniałe, gdyby metoda i pole AddPost mogły się nazywać tak samo. Miałbyś wtedy dwie metody, jedną normalną z parametrami, a drugą z techniką Currying.

Jeśli chciałbyś coś takiego mieć możesz rzucić okiem na rozwiązanie tego problemu pisząc metody Currying.

image

Kto wie może w C# 8.0 lub 9.0 pojawi się jakaś dziwna składnia dająca możliwość tworzenia metod Currying statyczno - dynamicznie.

Aby kod był ładniejszy i nie tylko, warto wszystkie deklaracje pól statycznych wrzucić do statycznego konstruktora. Dlaczego i nie tylko. Statyczny kompilator daje nam gwarancję kolejności wykonywania deklaracji pól statycznych, co w wypadku zależności funkcji przez inne mogłoby spowodować dziwne błędy.

public static class Storage
{
    static Storage()
    {
        AddPostC = Functional.Curry<List<Comment>, 
            Autor, Post, bool>(AddPost);
        AddPostC2 = AddPostC;
    }
    public static bool AddPost(List<Comment> comment, Autor autor,
        Post post)
    {
        //coś się dzieje, 
        //ale nas to nie interesuje
        return true;
    }

    public static readonly Func<List<Comment>, Func<Autor, Func<Post, bool>>>
        AddPostC;

    public static readonly dynamic AddPostC2;
}

Rozwiązanie z metodami

Do problemu można też podjeść inaczej i napisać swoje własne metody.

public static Func<Autor, Func<Post, bool>> AddPost(List<Comment> comm)
{
    return x => y => AddPost(comm, x, y);
}

Problem leżałby w tym, że straciłbym automatyczny Currying z drugiej strony mogąc pisać kod ręcznie mógłbym napisać wiele metod decydujących chociażby o pierwszym podanym parametrze w kolejności.

public static class Storage2
{

    public static bool AddPost(List<Comment> comment, Autor autor,
        Post post)
    {
        //coś się dzieje, 
        //ale nas to nie interesuje
        return true;
    }

    public static Func<Autor, Func<Post, bool>> AddPost(List<Comment> comm)
    {
        return x => y => AddPost(comm, x, y);
    }


    public static Func<List<Comment>, Func<Post, bool>> AddPost(Autor autor)
    {
        return x => y => AddPost(x, autor, y);
    }

    public static Func<List<Comment>, Func<Autor, bool>> AddPost(Post post)
    {
        return x => y => AddPost(x, y, post);
    }

}

Niestety, ale nie można definiować w C# wielu metod z tymi samymi parametrami. Co oznacza, że nie mam wpływu na kolejność drugiego parametru i każdego następnego. Tak jak zdefiniuję w metodzie, tak będzie.

image

Największa zaleta oczywiście polega na tym, że masz teraz normalną metodę Currying i metody po Currying-u.

image

var addpost = Storage2.AddPost(new List<Comment>())
(new Autor());

var addpost2 = Storage2.AddPost(new Autor())
(new List<Comment>());

var addautor = Storage2.AddPost(new Post())
(new List<Comment>());

Rozwiązanie moim zdaniem wydaje się być trochę bardziej ciekawsze niż pola statyczne, ale moim zdaniem to też kwestia gustu.

Deklaracja metod niestety trochę mnie ogranicza, jeśli chodzi o tworzenie różnych metod z różnymi kolejnościami wywołań Currying. Z drugiej strony tworzenie wielu pól deklaracji generycznych delegat byłoby bardzo nie intuicyjne. Bo skąd byś wiedział, które pole przyjmuje jakie parametry i w jakiej kolejności – chyba nie po nazwie pola. To by było bardzo głupie.

Trzeba przyznać, że każda z tych technik Currying-u w kontekście klasy tworzy inny zestaw pytań.

Coś w tym rozwiązaniu z metodami poważnie śmierdzi, ale nie bardzo wiem co. Być może wynika to z tego, że jak patrzę na te metody, to coraz bardziej zadaję sobie pytanie dlaczego nie napisałem normalnych obiektowych metod przyjmujących inne parametry. Oczywiście w tym ćwiczeniu nie o to chodziło. Musimy myśleć funkcjonalnie.

Przejdźmy więc do, moim zdaniem najlepszego rozwiązania.

Rozwiązanie z właściwościami

public static class Storage3
{
    private static Func<List<Comment>, Func<Autor, Func<Post, bool>>>
        _addPost;

    public static Func<List<Comment>, Func<Autor, Func<Post, bool>>> AddPost
    {
        get
        {
            if (_addPost == null)
                _addPost = Functional.Curry<List<Comment>,
                    Autor, Post, bool>(_AddPost);

            return _addPost;
        }
    }

    private static  bool _AddPost(List<Comment> comment, Autor autor,
        Post post)
    {
        //coś się dzieje, 
        //ale nas to nie interesuje
        return true;
    }

}

Zastosowanie właściwości jest podobne do rozwiązania z polami statycznymi. Istnieją jednak pewne zalety:

  • Po pierwsze tworzenie funkcji Currying odbywa się dopiero wtedy, gdy funkcja jest potrzebna. Statyczny konstruktor w przypadku dużej deklaracji obciążyłby na początku program.
  • Statyczny konstruktor nie jest już potrzebny
  • Tworzenie funkcji po jeszcze innych transformatach może się okazać jeszcze bardziej skomplikowane, co oznacza, że ten mechanizm właściwości byłby jeszcze bardziej na miejscu niż statyczny konstruktor.
  • Właściwości zostały stworzone z myślą o hermetyzacji logiki. Co daje przewagę nad polami.

Istnieje jednak jedna wada tego rozwiązania wynikająca z napisania większej ilości kodu, wynikającej chociażby ze sprawdzenia czy pole prywatne zostało już zadeklarowane.

W tej implementacji też nie ma żadnej normalnej funkcji, co sprawia, że cała ta klasa statyczna zawiera w sobie i będzie zawierała tylko funkcję napisaną w stylu Currying.

To by było wszystko na temat Currying. W następnym wpisie omówimy wywoływania rekurencyjne metod i funkcji w C#.