2013-05-16 9 views
6

Sono nuovo di D, e mi chiedevo se è possibile eseguire comodamente la digitazione anatra in fase di compilazione.anatra digitando D

Per esempio, mi piacerebbe definire un insieme di metodi e richiedere che tali metodi siano definiti per il tipo che viene passato in una funzione. È leggermente diverso da interface in D perché non dovrei dichiarare che "tipo X implementa l'interfaccia Y" da qualche parte - i metodi verrebbero trovati, o la compilazione fallirebbe. Inoltre, sarebbe opportuno consentire che ciò avvenga su qualsiasi tipo, non solo su strutture e classi. L'unica risorsa ho trovato è stato this email thread, il che suggerisce che il seguente approccio sarebbe un modo decente per fare questo:

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

... e suggerisce che si potrebbe fare in una chiamata libreria implementa in modo che il seguente sarebbe possibile:

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)) { ... } 

È possibile farlo per funzioni che non sono definite in una classe o in una struttura? Ci sono altri/nuovi modi per farlo? È stato fatto qualcosa di simile?

Ovviamente, questa serie di vincoli è simile al sistema di tipi di Go. Non sto cercando di iniziare nessuna guerra di fiamme - sto solo usando D in un modo in cui anche Go funzionerebbe.

+0

Stai cercando di creare un'unica funzione per gestire tutti i casi d'uso? Penso che ciò richiederebbe [static foreach] (http://d.puremagic.com/issues/show_bug.cgi?id=4085), ma non ne sono sicuro. Forse un po 'di magia CTFE funzionerebbe? Anche di interesse: http://www.digitalmars.com/d/archives/digitalmars/D/static_foreach_108369.html – tjameson

+0

@tjameson Sì, credo che per l'esempio precedente dovresti scorrere tutti i metodi definiti nella tua interfaccia 'struct in fase di compilazione. Tuttavia, sono aperto anche ad altri modi per raggiungere lo stesso obiettivo, se esistono. – Dan

+0

D ha wrap e unwrap (http://dlang.org/phobos-prerelease/std_typecons.html#.wrap e http://dlang.org/phobos-prerelease/std_typecons.html#.unwrap) fornisce funzionalità simili a quelle di Go digitando anatra. – DejanLekic

risposta

7

Questa è una cosa molto comune in D. È come funzionano le gamme. Ad esempio, il tipo più semplice di gamma - gamma di ingresso - deve avere 3 funzioni:

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 

funzioni su modelli quindi utilizzare std.range.isInputRange per verificare se un tipo è un intervallo valido. Per esempio, il sovraccarico di più fondamentale di std.algorithm.find assomiglia

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 è true se R è un intervallo di input valido, e is(typeof(binaryFun!pred(haystack.front, needle)) : bool) è true se pred accetta haystack.front e needle e restituisce un tipo che è implicitamente convertibile in bool. Quindi, questo sovraccarico si basa interamente sulla tipizzazione anatra statica.

Per quanto riguarda isInputRange sé, sembra qualcosa di simile

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 
    })); 
} 

Si tratta di un modello omonimo, in modo da quando viene utilizzato, esso viene sostituito con il simbolo con il suo nome, che in questo caso è un enum di tipo bool . E che bool è true se il tipo dell'espressione non è void. typeof(x) risultati in void se l'espressione non è valida; in caso contrario, è il tipo di espressione x. E is(y) risultati in true se non è void. Quindi, isInputRange finirà per essere true se il codice nell'espressione typeof viene compilato e false in caso contrario.

L'espressione nella isInputRange verifica che è possibile dichiarare una variabile di tipo R, che R ha un membro (sia esso una funzione, variabile o qualsiasi altra cosa) di nome empty che può essere utilizzato in una condizione, che R ha una funzione chiamato popFront che non accetta argomenti e che R ha un membro front che restituisce un valore. Questa è l'API prevista per un intervallo di input e l'espressione all'interno di typeof verrà compilata se l'API R segue tale API e, pertanto, true per tale tipo sarà true. Altrimenti, sarà false.

La libreria standard di D contiene alcuni di questi modelli omonimi (in genere chiamati tratti) e ne fa un uso pesante nei vincoli del modello. std.traits in particolare ne ha parecchi. Quindi, se vuoi altri esempi di come sono scritti tali tratti, puoi guardare lì (anche se alcuni di essi sono abbastanza complicati). Gli interni di tali tratti non sono sempre particolarmente belli, ma incapsulano bene i test di battitura delle anatre in modo che i vincoli dei template siano molto più puliti e comprensibili (sarebbero molto, molto più brutti se tali test fossero inseriti direttamente in essi).

Quindi, questo è il normale approccio per la digitazione statica di anatra in D. Ci vuole un po 'di pratica per capire come scriverli bene, ma questo è il modo standard per farlo e funziona. Ci sono state persone che hanno suggerito di provare a proporre qualcosa di simile al tuo suggerimento Implements!(S, Interface), ma non è ancora arrivato nulla di simile, e un tale approccio sarebbe in realtà meno flessibile, rendendolo inadatto per molti tratti (anche se potrebbe certamente essere fatto per funzionare con quelli di base). Indipendentemente da ciò, l'approccio che ho descritto qui è attualmente il modo standard per farlo.

Inoltre, se non si conoscono molto gli intervalli, suggerirei di leggere this.

+0

In realtà è una delle cose più semplici e allo stesso tempo sconvenienti della meta-programmazione in D. Non c'è alcun problema nell'implementazione di "Implements" (vedi la mia risposta) ma per essere davvero utile, deve essere supportata tramite sintassi nativa template tmpl (T: DuckTypeInterface). Un po 'più flessibile che è XRange, ma messaggi di errore molto più leggibili e molto migliori. Non pensiamo che possiamo aspettarci presto qualcosa del genere. –

2

Gli strumenti! (S, Interfaccia) sono possibili ma non hanno ricevuto sufficiente attenzione per accedere alla libreria standard o ottenere un supporto linguistico migliore. Probabilmente se non sarò il solo dicendo che è la strada da percorrere per la digitazione di anatra, avremo la possibilità di avere it :)

Proof of concept esecuzione ad armeggiare intorno:

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() 
{ 
} 
Problemi correlati