2014-06-10 7 views
15

Sto provando a portare l'esempio Matrix dal libro Swift per essere generico.Non c'è un valore predefinito (T) in Swift?

Ecco quello che ho ottenuto finora:

struct Matrix<T> { 
    let rows: Int, columns: Int 
    var grid: T[] 

    init(rows: Int, columns: Int, repeatedValue: T) { 
     self.rows = rows 
     self.columns = columns 

     grid = Array(count: rows * columns, repeatedValue: repeatedValue) 
    } 

    func indexIsValidForRow(row: Int, column: Int) -> Bool { 
     return row >= 0 && row < rows && column >= 0 && column < columns 
    } 

    subscript(row: Int, column: Int) -> T { 
     get { 
      assert(indexIsValidForRow(row, column: column), "Index out of range") 
      return grid[(row * columns) + column] 
     } 
     set { 
      assert(indexIsValidForRow(row, column: column), "Index out of range") 
      grid[(row * columns) + column] = newValue 
     } 
    } 
} 

Nota che ho dovuto passare repeatedValue: T al costruttore.

In C#, avrei semplicemente usato default(T) che sarebbe 0 per i numeri, per false booleani e null per i tipi di riferimento. Comprendo che Swift non consente nil per i tipi non opzionali, ma sono comunque curioso di sapere se il passaggio di un parametro esplicito è l'unico modo, o se ci sono circa equivalenti a default(T).

risposta

6

Non c'è. Swift ti costringe a specificare il valore di default, proprio come quando gestisci variabili e campi. L'unico caso in cui Swift ha un concetto di valore predefinito è per i tipi facoltativi, dove è nil (Optional.None).

3

Un iffy 'SÌ'. È possibile utilizzare i vincoli del protocollo per specificare il requisito che la classe o la funzione generica funzionerà solo con i tipi che implementano la funzione di inizializzazione predefinita (senza parametri). Le implicazioni di questo saranno molto probabilmente negative (non funziona come credi), ma è la cosa più vicina a ciò che stavi chiedendo, probabilmente più vicino della risposta "NO".

Per me l'ho trovato personalmente utile durante lo sviluppo di una nuova classe generica, quindi alla fine ho rimosso il vincolo e risolto i problemi rimanenti. Richiedere solo i tipi che possono assumere un valore predefinito limiterà l'utilità del tipo di dati generico.

public protocol Defaultable 
{ 
    init() 
} 

struct Matrix<Type: Defaultable> 
{ 
    let rows: Int 
    let columns: Int 
    var grid: [Type] 

    init(rows: Int, columns: Int) 
    { 
    self.rows = rows 
    self.columns = columns 

    grid = Array(count: rows * columns, repeatedValue: Type()) 
    } 
} 
2

C'è un modo per ottenere l'equivalente di default(T) in veloce, ma non è libero e ha un rischio associato:

public func defaultValue<T>() -> T { 
    let ptr = UnsafeMutablePointer<T>.alloc(1) 
    let retval = ptr.memory 
    ptr.dealloc(1) 
    return retval; 
} 

Ora, questo è chiaramente un hack, perché non sappiamo se alloc() si inizializza su qualcosa di conoscibile. Sono tutti gli 0? Cose lasciate nel mucchio? Chissà? Inoltre, quello che è oggi potrebbe essere qualcosa di diverso domani.

In effetti, l'utilizzo del valore di ritorno per è diverso da un segnaposto pericoloso. Diciamo che si dispone di codice in questo modo:

public class Foo { /* implementation */ 
public struct Bar { public var x:Foo } 
var t = defaultValue<Bar>(); 
t = someFactoryThatReturnsBar(); // here's our problem 

Nella riga problema, Swift pensa che t è stato inizializzato perché è quello che dicono la semantica di Swift: non si può avere una variabile di un tipo di valore che non è inizializzato. Tranne che è perché default<T> rompe quella semantica. Quando esegui l'assegnazione, Swift emette una chiamata nella tabella dei valori per distruggere il tipo esistente. Questo includerà il codice che chiamerà release sul campo x, perché la semantica Swift dice che le istanze degli oggetti non sono mai nil. E poi si verifica un arresto anomalo del runtime.

Tuttavia, ho avuto motivo di interoperare con Swift da un'altra lingua e ho dovuto passare in un tipo facoltativo.Sfortunatamente, Swift non mi fornisce un modo per costruire un opzionale in fase di esecuzione per motivi (almeno non ho trovato un modo), e non posso facilmente deriderne uno perché gli optionals sono implementati in termini di un enum generico e le enumerazioni utilizzano un'implementazione di strategia 5 scarsamente documentata per imballare il carico utile di un enum.

ho lavorato intorno a questo facendo passare una tupla che ho intenzione di chiamare un tupla Medusa solo per ghigni: (value: T, present: Bool) che ha il contratto che se present è true, quindi value è garantito per essere valida, non valida in caso contrario. Posso usare questo in modo sicuro ora per l'interoperabilità:

public func toOptional<T>(optTuple: (value:T, present:Bool)) -> T? 
{ 
    if optTuple.present { return optTuple.value } 
    else { return nil } 
} 

public func fromOptional<T>(opt: T?) -> (T, Bool) 
{ 
    if opt != nil { return (opt!, true) } 
    else { 
     return (defaultValue(), false) 
    } 
} 

In questo modo, il mio codice chiamando passa in una tupla invece di un codice di ricezione opzionale e e trasformarlo in un optional (e viceversa).

Problemi correlati