Un "sistema tipo" secondario per riferimenti?

1

Sto progettando un linguaggio e mi stavo chiedendo come incorporare riferimenti simili a C ++ per quanto riguarda la loro posizione nel sistema di tipi. Penso che siano utili per operazioni come indicizzazione e dereferenziazione ( v[i] restituisce un riferimento che può essere assegnato a, *pointer_type<a> restituisce un riferimento a un a che si comporta come un a ).

Nominalmente in C ++, un tipo di riferimento come int& e il suo tipo originale int sono due tipi distinti. Tuttavia, ritengo che questo sia incoerente con il resto del sistema di tipi: un int& può essere sostituito per int e a volte si comportano come se fossero dello stesso tipo, ea volte no, senza conversioni esplicite (che è uno dei motivi per cui i riferimenti sono utilizzati in primo luogo.

La mia soluzione sarebbe che i binding e gli argomenti delle funzioni abbiano essenzialmente un "tipo secondario", o piuttosto un'altra qualifica con regole speciali, determinando se sono riferimenti o meno. Ci sarebbero due tipi in questo sistema di tipi secondari: ref e non- ref (default), e queste etichette verrebbero scritte in posti dove sarebbero presenti altre etichette simili come const (o mut ) - principalmente quando si introducono nuovi collegamenti o in tipi di funzioni.

Quindi, insieme agli errori di disallineamento di tipo classico, il compilatore avrebbe segnalato qualcosa come meccanismo di associazione non corrispondente . Ad esempio ciò accadrebbe se il programmatore tentasse di inserire una funzione che accetti un singolo intero per riferimento in un elenco di funzioni che lo accettano per copia ( ref a -> b vs. a -> b ). Anche se le funzioni avrebbero lo stesso tipo : a -> b , i meccanismi di associazione sono incompatibili (perché se vengono passati gli lvalues che verranno successivamente assegnati, potrebbe verificarsi un comportamento non compatibile imprevisto).

Questa soluzione ha senso o è meglio avvolgerla nel sistema dei tipi e soffrire di incoerenze? Ci sono alcuni effetti collaterali negativi che non sto anticipando?

Per chiarire: i punti toccanti tra questi due sistemi di tipi avrebbero regole speciali. Ad esempio, ci sarebbero conversioni implicite da ref a non- ref mantenendo il tipo invariante.

    
posta jcora 30.07.2015 - 15:16
fonte

4 risposte

3

C ++ tratta int , const int , int& e const int& come tipi separati con modi per convertire in / da ciascun tipo (tranne che in const int o in const int& ). Se sai quali tipi di file hai e quali tipi sono previsti, allora, dato un elenco di convertitori, sai se puoi passare ogni parametro passato come parametro di argomento.

Poiché potrebbero anche esserci convertitori personalizzati (overloading operator =), puoi semplicemente aggiungerlo all'elenco dei convertitori e applicare le stesse regole. Il più grande vantaggio di questo approccio è la semplicità.

Se decidi che int , const int , int& e const int& sono tutti uguali, allora const e & diventano modificatori di quel tipo. Potrebbe essere una buona idea inventare un modo per creare modificatori di tipo personalizzato che dichiarino come quel tipo può essere convertito al tipo normale, e quindi usare questo metodo per creare modificatori di tipi predefiniti come & e const .

Il compilatore tenterebbe quindi di convertire da un modificatore a un tipo "normale" e dal tipo normale al modificatore presente nell'argomento metodo. Se manca la conversione corretta, non viene chiamato il metodo (e se non viene trovato alcun metodo, il compilatore genera un errore).

Ad esempio, per chiamare un metodo void doSomething(int& val) assegnandogli un const int valToPass , il compilatore tenterebbe di convertire prima const int valToPass in int valToPass , ma non esisterebbe alcuna conversione, quindi non lo chiameresti metodo. Tuttavia, chiamare doSomething(const int val) con int& valToPass verrebbe chiamato perché esisterebbe un convertitore da int& valToPass a int valToPass e da int valToPass a const int valToPass .

Un tipo const int& sarebbe semplicemente connesso al int & esistente che si comporta in modo simile, tranne che dal normale tipo int , deve prima essere convertibile in int & prima di poterlo convertire in% % co_de.

In tutti questi esempi sto usando int, ma queste dichiarazioni sarebbero valide per tutti i tipi. Il punto è che il programmatore che usa la tua lingua potrebbe creare estensioni personalizzate usando le loro parole chiave (purché non sia in conflitto con i modificatori o le parole chiave esistenti).

Io starei lontano da un tipo secondario, perché ciò implica che ci saranno necessariamente esattamente due, e c'è già const e & da considerare, ma penso che ti stia muovendo nella giusta direzione.

Spero che ti aiuti!

    
risposta data 30.07.2015 - 17:00
fonte
2

Penso che sia utile considerare come i riferimenti mutabili funzionino nella famiglia di linguaggi ML / Haskell. In questi linguaggi esistono tipi generici espliciti i cui valori sono riferimenti mutabili . Userò Haskell perché è quello che so meglio, ma ML ha idee simili.

Prendi ad esempio il tipo STRef in Haskell:

Possiamo usare questo (e alcuni altri strumenti) per, per esempio, sommare gli elementi di una lista nello stesso modo in cui faresti in una lingua imperativa:

import Data.Foldable (Foldable, for_)
import Data.IORef

imperativeSum :: (Foldable t, Num a) => t a -> IO a
imperativeSum numbers = do
    ref <- newIORef 0        -- Allocate mutable cell, initialize with '0'
    for_ numbers $ \i -> do  -- Loop over the numbers, with 'i' as loop var
      modifyIORef' ref (+i)  -- Mutate the cell by adding 'i' to its content
    readIORef ref            -- Read final value of the cell and return it

Qui, il tipo della variabile ref è Num a => IORef a -un riferimento mutabile a un valore di tipo a (che deve essere un Num ber). Haskell è completamente rigido sul fatto che IORef e il suo contenuto sono valori diversi con tipi diversi: un IORef Int non è dello stesso tipo di Int . Quindi il sistema tipo ti impedisce automaticamente di utilizzarne uno in un contesto in cui è previsto l'altro.

Nota che le operazioni disponibili per IORef sono:

  1. Creazione di un nuovo IORef , con un valore iniziale (obbligatorio)
  2. Lettura del contenuto corrente di IORef
  3. Mutamento del contenuto di IORef
  4. Confronto di due IORef s per l'uguaglianza ("sono questi due riferimenti alla stessa posizione di memoria").

Si noti inoltre che IORef è solo un tipo generico con alcune operazioni e il linguaggio non lo privilegia affatto. Esistono altri tipi simili che forniscono diversi tipi di riferimenti:

  • STRef : tipo di riferimento speciale da utilizzare nei calcoli che utilizzano la mutazione internamente ma non hanno effetti collaterali osservabili esternamente.
  • TVar : tipo di riferimento utilizzato nei calcoli della memoria transazionale del software. Vedi articolo di Simon Peyton Jones su Haskell STM per una dimostrazione.
risposta data 23.09.2015 - 22:58
fonte
1

Ci sono alcune cose che non possono essere considerate separatamente. In altre parole, ci sono alcune cose che non sono affatto ortogonali.

In primo luogo, pensa a come il tuo sistema di tipi consentirà l'implementazione di pure funzioni.

Una funzione pura non dovrebbe preoccuparsi se gli input sono valori, ref o altre funzioni pure che promettono di fornire lo stesso tipo di valori.

Una funzione pura non sarà in grado di chiamare funzioni non pure. Non permettere in alcun modo questo.

In secondo luogo, pensa a come il tuo sistema di tipi fornirà informazioni utili al tuo compilatore per consentirgli di dedurre la durata degli oggetti - per verificare che sia valido per tutta una chiamata di funzione, per prenderlo in prestito, per estenderlo al di fuori dello scope corrente , per raccogliere i rifiuti o fare un conteggio dei riferimenti, o qualcos'altro. Nel caso del multithreading, potresti anche dover monitorare se potrebbero esserci modifiche simultanee (sovrapposte).

Il sistema di tipo C ++ è il risultato del prendere in considerazione la maggior parte di quanto sopra (escluso il threading). È il risultato dell'osservazione di ciò che avrebbe senso e cosa no. Tuttavia, molte persone possono scoprire e lamentarsi dei modi in cui la sicurezza del sistema di tipo C ++ può essere sconfitta.

Dopo aver preso alcune decisioni su questi due aspetti, puoi pensare al sistema dei tipi per la gestione degli argomenti per le funzioni non pure.

    
risposta data 04.08.2015 - 02:21
fonte
0

they sometimes behave as they were the same type, and sometimes they don't, without explicit conversions (which is one of the reasons why references are used in the first place).

No, non lo fanno. Un int & (in pratica un int lvalue) può essere convertito implicitamente in un int (un valore rval), ma non il contrario.

    
risposta data 04.08.2015 - 01:52
fonte

Leggi altre domande sui tag