2016-02-25 20 views
5

Si tratta di un tentativo di semplificare una parte della questione ho chiesto here:Come posso scrivere un tratto in Julia con tipi aperti?

voglio scrivere del codice che è garantito per lavorare sui tipi che soddisfano determinati criteri. Diciamo che oggi ho scrivere del codice:

immutable Example 
    whatever::ASCIIString 
end 
function step_one(x::Example) 
    length(x.whatever) 
end 
function step_two(x::Int64) 
    (x * 2.5)::Float64 
end 
function combine_two_steps{X}(x::X) 
    middle = step_one(x) 
    result = step_two(middle) 
    result 
end 
x = Example("Hi!") 
combine_two_steps(x) 

L'esecuzione di questo funziona:

julia> x = Example("Hi!") 
Example("Hi!") 

julia> combine_two_steps(x) 
7.5 

Poi un altro giorno ho scrivere del codice più:

immutable TotallyDifferentExample 
    whatever::Bool 
end 
function step_one(x::TotallyDifferentExample) 
    if x.whatever 
     "Hurray" 
    else 
     "Boo" 
    end 
end 
function step_two(x::ASCIIString) 
    (Int64(Char(x[end])) * 1.5)::Float64 
end 

E che ne sai, la mia generica la funzione combinata funziona ancora!

julia> y = TotallyDifferentExample(false) 
TotallyDifferentExample(false) 

julia> combine_two_steps(y) 
166.5 

Evviva! Ma, diciamo che è una notte tarda e sto cercando di farlo di nuovo su un terzo esempio. Mi ricordo di implementare step_one, ma ho dimenticato di implementare step_two!

immutable ForgetfulExample 
    whatever::Float64 
end 
function step_one(x::ForgetfulExample) 
    x.whatever+1.0 
end 

Ora, quando eseguo questo, otterrò un errore in fase di esecuzione!

julia> z = ForgetfulExample(1.0) 
ForgetfulExample(1.0) 

julia> combine_two_steps(z) 
ERROR: MethodError: `step_two` has no method matching step_two(::Float64) 

Ora, io lavoro per un manager che ucciderà me se mai avrò un errore di run-time. Quindi quello che devo fare per salvarmi è scrivere un Tratto che dice essenzialmente "se il tipo implementa questa caratteristica, allora è sicuro chiamare combine_two_steps".

voglio scrivere qualcosa di simile

using Traits 
@traitdef ImplementsBothSteps{X} begin 
    step_one(X) -> Y 
    step_two(Y) -> Float64 
end 
function combine_two_steps{X;ImplementsBothSteps{X}}(x::X) 
    middle = step_one(x) 
    result = step_two(middle) 
    result 
end 

b/c allora io so che se combine_two_steps è mai spedito, allora sarà corsa senza sollevare un errore che questi metodi don esiste

Equivalentemente, istrait(ImplementsBothSteps{X}) (essere vero) è equivalente a combine_two_steps verrà eseguito senza errori da non-inesistenza-di-requisiti-metodi.

Ma, come tutti sanno, non posso usare quella definizione di tratto, perché Y non ha significato. (In realtà, stranamente il codice viene compilato senza errori,

julia> @traitdef ImplementsBothSteps{X} begin 
      step_one(X) -> Y 
      step_two(Y) -> Float64 
     end 

julia> immutable Example 
      whatever::ASCIIString 
     end 

julia> function step_one(x::Example) 
      length(x.whatever)::Int64 
     end 
step_one (generic function with 1 method) 

julia> function step_two(x::Int64) 
      (x * 2.5)::Float64 
     end 
step_two (generic function with 1 method) 

julia> istrait(ImplementsBothSteps{Example}) 
false 

ma i tipi non soddisfano la caratteristica, anche se esistono metodi per qualche Y.) Il mio primo pensiero è che posso cambiare Y a qualcosa come Any

using Traits 
@traitdef ImplementsBothSteps{X} begin 
    step_one(X) -> Any 
    step_two(Any) -> Float64 
end 

ma questo non troppo b/c il Any davvero si suppone che sia qualcosa di simile Some, non letteralmente il tipo Any (dal momento che non ho mai implementato un metodo step_two che potrebbe assumere qualsiasi tipo di ingresso), ma qualche particolaredigita condiviso su entrambe le righe!

Quindi, la domanda è: cosa faresti in questa situazione?Si vuole passare attorno a una "specifica" (qui nella forma del contratto espressa dal Tratto) in modo tale che qualsiasi programmatore che soddisfi le specifiche sia garantito per poter utilizzare la propria funzione combine_two_steps, ma la specifica ha essenzialmente un quantificatore esistenziale nella sua definizione.

C'è una soluzione? Un approccio migliore alla scrittura delle "specifiche" (es. "Non usare i tratti, usa qualcos'altro"?) Etc.

A proposito, può sembrare forzato, ma la domanda di cui sopra e questa domanda stanno arrivando regolarmente su un progetto a cui sto lavorando. Sono essenzialmente bloccato a un posto di blocco causato da questo problema e ho brutte soluzioni alternative che funzionano caso per caso, ma nessun approccio al caso generale.

risposta

1

Generalizzare sul suggerimento nella mia domanda sull'uso di Any può funzionare anche, anche se è brutto e non arriva al punto. Supponiamo di aver già implementato i metodi

step_one(X) -> Y 
step_two(Y) -> Z 

Poi si può scrivere il tratto come

@traitdef implements_both_steps begin 
    step_one(X) -> Any 
    step_two(Any) -> Z 
end 

e basta aggiungere un metodo fittizio

function step_two(x::Any) 
    typeof(x)==Y ? step_two(x::Y) : error("Invalid type") 
end 

Questo può essere avvolto in una macro pure per risparmiare ripetendo il pattern, e quindi una volta implementato quel metodo, il tratto è soddisfatto. È un hack che sto usando (e funziona) b/c è abbastanza semplice, ma la soluzione non è nello spirito della mia domanda.

+0

Potete per favore pubblicare i dettagli. –

1

È questo il soddisfacente:

@traitdef ImplementsStep2{Y} begin 
    step_two(Y) -> Float64 
end 

# consider replacing `any` with `all` 
@traitdef AnotherImplementsBothSteps{X} begin 
    step_one(X) 
    @constraints begin 
     any([istrait(ImplementsStep2{Y}) for Y in Base.return_types(step_one,(X,))]) 
    end 
end 

Con queste definizioni di tratti che abbiamo:

julia> istrait(ImplementsStep2{Int64}) 
true 

julia> istrait(AnotherImplementsBothSteps{Example}) 
true 

Il trucco è quello di utilizzare @constraints da fare fondamentalmente la roba non-lineare. E per usare Base.return_types per ottenere i tipi di ritorno per un metodo. Questo è un po 'un trucco, ma questo è ciò che mi è venuto in mente. Forse una versione futura di Traits.jl avrà strumenti migliori per questo.

Ho usato any nella definizione di tratto. Questo è un po 'negligente. L'utilizzo di all potrebbe essere più rigido ma rappresentare meglio il vincolo, a seconda del livello di controllo della compilazione desiderato.

Naturalmente, la buona introspezione di Julia e try ... catch consente di eseguire tutto questo controllo in fase di esecuzione.

+0

La strategia di utilizzo di Base.return_types è interessante. Hai scritto in modo efficace codice che implementa in modo riflessivo il passaggio del "quantificatore esistenziale". Mi piace questo. Ci penserò un po 'di più. Questa intera cosa potrebbe essere racchiusa in una macro "meta", in effetti, per semplificare il processo all'utente finale. Voglio indagare sull'esecuzione di questa strategia però. Lo sai dalla parte superiore della tua testa se questo farà sì che il dispatch di metodo sui tratti subisca un calo di prestazioni? – Philip

+0

Se il compilatore può dedurre i tipi di parametri durante la compilazione, non ci dovrebbe essere un hit di prestazioni (eccetto per la generazione iniziale e la compilazione delle funzioni coinvolte). D'altra parte, se i tipi sono sconosciuti, potrebbe essere un colpo di prestazioni inferire la funzione giusta, ma la funzione generata è memorizzata nella cache, quindi, si spera, sarebbe ancora una volta eseguita.Questa è la solita storia di Julia, quando i tipi sono stabili e dedotti, si ottiene il massimo delle prestazioni (a proposito di "mauro3" che ha scritto "Traits.jl"). –

+0

I metodi usati da 'Traits.jl' sono spiegati nel' README.md' del repository github abbastanza approfonditamente (Link: https://github.com/mauro3/Traits.jl) –

Problemi correlati