2013-05-16 12 views
6

Jestem nowy w D i zastanawiałem się, czy można wygodnie wykonywać pisanie kaczkami w czasie kompilacji.pisanie w kaczce w D

Na przykład chciałbym zdefiniować zestaw metod i wymagać zdefiniowania tych metod dla typu przekazywanego do funkcji. Jest nieco inny niż interface w D, ponieważ nie musiałbym zadeklarować, że "typ X implementuje interfejs Y" w dowolnym miejscu - metody zostałyby znalezione, lub kompilacja zakończyłaby się niepowodzeniem. Poza tym dobrze byłoby pozwolić na to, aby działo się to na dowolnym typie, a nie tylko na klasach i klasach. Jedynym źródłem mogłyby znaleźć był this email thread, co sugeruje, że następujące podejście byłoby przyzwoity sposób, aby to zrobić:

void process(T)(T s) 
    if(__traits(hasMember, T, "shittyNameThatProbablyGetsRefactored")) 
    // and presumably something to check the signature of that method 
{ 
    writeln("normal processing"); 
} 

... i sugeruje, że można uczynić go Narzędziami wywołania biblioteki tak, że po będzie możliwe:

struct Interface { 
    bool foo(int, float); 
    static void boo(float); 
    ... 
} 

static assert (Implements!(S, Interface)); 
struct S { 
    bool foo(int i, float f) { ... } 
    static void boo(float f) { ... } 
    ... 
} 

void process(T)(T s) if (Implements!(T, Interface)) { ... } 

Czy jest to możliwe w przypadku funkcji, które nie są zdefiniowane w klasie lub strukturze? Czy są inne/nowe sposoby na zrobienie tego? Czy zrobiono coś podobnego?

Oczywiście ten zestaw ograniczeń jest podobny do systemu typu Go. Nie próbuję rozpoczynać żadnych wojen płomieniowych - używam D tylko w taki sposób, w jaki Go również by się dobrze sprawdził.

+0

Czy próbujesz utworzyć jedną funkcję do obsługi wszystkich przypadków użycia? Myślę, że wymagałoby to [statycznego foreach] (http://d.puremagic.com/issues/show_bug.cgi?id=4085), ale nie jestem pewien. Może zadziała jakaś magia CTFE? Interesuje mnie również: http://www.digitalmars.com/d/archives/digitalmars/D/static_foreach_108369.html – tjameson

+0

@tjameson Tak, wierzę, że dla powyższego przykładu musiałbyś przechodzić przez wszystkie metody zdefiniowane w twoim interfejsie 'struct w czasie kompilacji. Jestem jednak otwarty na inne sposoby osiągnięcia tego samego celu, jeśli istnieją. – Dan

+0

D ma wrap i unwrap (http://dlang.org/phobos-prerelease/std_typecons.html#.wrap i http://dlang.org/phobos-prerelease/std_typecons.html#.unwrap) udostępnia funkcję podobną do Go's pisanie kaczek. – DejanLekic

Odpowiedz

7

Jest to bardzo często spotykana rzecz w D. To jak zakresy działają. Na przykład, najbardziej podstawowy typ zakresu - zakres wejściowy - musi mieć 3 funkcje:

bool empty(); //Whether the range is empty 
T front(); // Get the first element in the range 
void popFront(); //pop the first element off of the range 

funkcje matrycy następnie użyć std.range.isInputRange celu sprawdzenia, czy dany typ jest prawidłowy zakres. Na przykład, najbardziej podstawowe przeciążenie std.algorithm.find wygląda

R find(alias pred = "a == b", R, E)(R haystack, E needle) 
if (isInputRange!R && 
    is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) 
{ ... } 

isInputRange!R jest true jeśli R jest prawidłowy zakres wejściowy i is(typeof(binaryFun!pred(haystack.front, needle)) : bool) jest true jeśli pred akceptuje haystack.front i needle i zwraca typ, który jest niejawnie zamienny do bool. Tak więc to przeciążenie opiera się wyłącznie na statycznym pisaniu kaczek.

Co do samego isInputRange, wygląda mniej więcej tak

template isInputRange(R) 
{ 
    enum bool isInputRange = is(typeof(
    { 
     R r = void;  // can define a range object 
     if (r.empty) {} // can test for empty 
     r.popFront();  // can invoke popFront() 
     auto h = r.front; // can get the front of the range 
    })); 
} 

Jest to tytułowa szablon, więc kiedy jest używany, to jest zastępowany symbolem z nazwy, która w tym przypadku jest enum typu bool . I że bool jest true, jeśli typ wyrażenia jest inny niż void. typeof(x) wyniki w void, jeśli wyrażenie jest nieprawidłowe; w przeciwnym razie jest to typ wyrażenia x. I is(y) wyniki w true jeśli y jest non-void. Tak więc isInputRange kończy się na true, jeśli kod w kompilacji wyrażeń typeof, i false w przeciwnym razie.

Wyrażenie w isInputRange sprawdza, można zadeklarować zmienną typu R, że R ma człon (czy to funkcja, zmienna, lub cokolwiek) o nazwie empty który może być używany w warunkach, które R posiada funkcję o nazwie popFront, która nie przyjmuje żadnych argumentów, i że R ma członka front, który zwraca wartość. Jest to oczekiwane API z zakresu wejściowego, a wyrażenie wewnątrz typeof skompiluje się, jeśli R będzie podążać za tym API, a zatem isInputRange będzie dla tego typu true. W przeciwnym razie będzie to false.

Standardowa biblioteka D's ma wiele takich tytułowych szablonów (zazwyczaj zwanych cechami) i intensywnie wykorzystuje je w swoich ograniczeniach szablonowych. std.traits w szczególności ma ich sporo. Tak więc, jeśli chcesz więcej przykładów tego, jak takie cechy są napisane, możesz tam zajrzeć (chociaż niektóre z nich są dość skomplikowane). Wewnętrzne cechy takich cech nie zawsze są szczególnie ładne, ale ładnie zamykają typowanie kaczek, więc ograniczenia szablonów są znacznie bardziej zrozumiałe (byłyby dużo, dużo brzydsze, gdyby takie testy zostały w nich wstawione bezpośrednio).

To normalne podejście do typowania kaczych liter w D. To wymaga trochę praktyki, aby wymyślić, jak dobrze je napisać, ale jest to standardowy sposób, aby to zrobić i działa. Byli ludzie, którzy zasugerowali próbę wymyślenia czegoś podobnego do twojej sugestii, ale nic tak naprawdę nie pochodzi z tego, a takie podejście byłoby w rzeczywistości mniej elastyczne, co czyni go niedostosowanym do wielu cech (choć z pewnością można by z niego korzystać z podstawowymi). Niezależnie od tego podejście, które tu opisałem, jest obecnie standardowym sposobem.

Ponadto, jeśli nie wiesz zbyt wiele o zakresach, proponuję przeczytać this.

+0

Jest to jedna z najprostszych i jednocześnie niewygodnych rzeczy związanych z metaprogramowaniem w D. Nie ma problemu z implementacją "Narzędzi" (zobacz moją odpowiedź), ale aby była naprawdę użyteczna, musi być obsługiwana przez natywną składnię szablon tmpl (T: DuckTypeInterface). Nieco bardziej elastyczny niż XRange, ale o wiele bardziej czytelny i o wiele lepszy komunikat o błędzie. Nie myśl, że wkrótce możemy się czegoś takiego spodziewać. –

2

Implementacja! (S, Interface) jest możliwa, ale nie poświęcono wystarczającej uwagi, aby uzyskać standardową bibliotekę lub uzyskać lepszą obsługę języków. Prawdopodobnie, jeśli nie będzie jedyną informacją jest to droga do kaczki typowania, będziemy mieli szansę na to :)

dowód wykonania koncepcji majstrować wokół:

http://dpaste.1azy.net/6d8f2dc4

import std.traits; 

bool Implements(T, Interface)() 
    if (is(Interface == interface)) 
{ 
    foreach (method; __traits(allMembers, Interface)) 
    { 
     foreach (compareTo; MemberFunctionsTuple!(Interface, method)) 
     { 
      bool found = false; 

      static if (!hasMember!(T, method)) 
      { 
       pragma(msg, T, " has no member ", method); 
       return false; 
      } 
      else 
      {    
       foreach (compareWhat; __traits(getOverloads, T, method)) 
       { 
        if (is(typeof(compareTo) == typeof(compareWhat))) 
        { 
         found = true; 
         break; 
        } 
       } 

       if (!found) 
       { 
        return false; 
       } 
      } 
     } 
    } 
    return true; 
} 

interface Test 
{ 
    bool foo(int, double); 
    void boo(); 
} 

struct Tested 
{ 
    bool foo(int, double); 
// void boo(); 
} 

pragma(msg, Implements!(Tested, Test)()); 

void main() 
{ 
}