Generici in lingue di basso livello

3

Sto sviluppando una lingua come Vala e OOC che ricompila in C.

Questo significa che, alla fine, ogni funzionalità deve essere adattabile al codice C in un modo o nell'altro. Generics è una delle funzionalità che vorrei implementare nella mia lingua.

Come probabilmente sapete, C è un linguaggio tipizzato rigorosamente. Ad eccezione del puntatore opaco del vuoto, non c'è modo di passare un argomento di un tipo sconosciuto. Questo perché il compilatore deve conoscere la dimensione esatta dell'argomento passato o restituito.

Le seguenti soluzioni mi vengono in mente:

  1. Boxing: crea un sindacato in grado di memorizzare tutti i tipi possibili
  2. Passa un puntatore al parametro, piuttosto che al parametro stesso.

Entrambi hanno i loro lati negativi:

    • La dimensione del tipo generico T sarà uguale al tipo più grande possibile
    • Le dimensioni sono predeterminate, una struttura con una dimensione diversa non può essere passata per valore
    • Più lento, perché ogni argomento deve essere inserito / disinserito
    • Il parametro non viene copiato nel nuovo ambito di stack
    • Problemi di gestione della memoria a causa di quanto sopra
    • Non il comportamento desiderato a causa di quanto sopra

Sono interessato a sapere come poter adottare questo principio di alto livello in un linguaggio di livello inferiore e anche in che modo altri linguaggi di alto livello hanno superato questo problema.

Modifica

Come ha sottolineato @delnan, un'altra possibilità è la monomorfizzazione, che crea una nuova funzione per ogni tipo di dati. Questo ha alcuni aspetti negativi:

  • Il tipo T deve essere definito in anticipo (non molto generico)
  • La dimensione binaria diventa più grande (che, scontato, non è molto rilevante al giorno d'oggi)
posta NSAddict 02.11.2014 - 15:02
fonte

2 risposte

9

Monomorphization. Per ogni tipo / funzione generico (polimorfico), generare una versione non generica (monomorfica) per ogni set di parametri di tipo. Date queste dichiarazioni:

struct G<T> {
  a: T,
  b: T
}

fn get_a<T>(g: G<T>) -> T {
  return g.a;
}

e questo codice:

x = G<int>(1, 2);
y = G<float>(1.0, 2.0);
get_a(x);
get_a(y);

Genera codice equivalente a:

struct G_int {
  a: int,
  b: int
}

struct G_float {
  a: float,
  b: float
}

fn get_a_int(g: G_int) -> int {
  return g.a;
}

fn get_a_float(g: G_float) -> float {
  return g.a;
}

x = G_int(1, 2);
y = G_float(1.0, 2.0);
get_a_int(x);
get_a_float(y);

Questo funziona anche attraverso le barriere della biblioteca! Richiede che il codice (almeno in formato AST / IR) di generici sia incluso nei binari della libreria, ma a parte questo il compilatore può semplicemente generare il codice allo stesso modo, semplicemente prendendo il "modello" da una fonte diversa. Le definizioni duplicate (quando due librerie creano un'istanza dello stesso generico con gli stessi parametri di tipo) possono essere unite dal linker e, nel peggiore dei casi, aumentare il tempo di compilazione.

    
risposta data 02.11.2014 - 15:13
fonte
5

attualmente ci sono 2 modi;

  1. il metodo di cancellazione del tipo di java; essenzialmente tutto diventa vuoto * (o un tipo personalizzato come struct base{void** functptrs;} e viene castato in base alle necessità, questo richiede che il tipo segua una determinata interfaccia per consentire al template di capire se i cast sono validi in fase di runtime.

  2. l'istanziazione crea una nuova definizione del modello come spiegato da delnan; questo è ciò che viene usato in C ++. Fa sì che un po 'di codice sia gonfio dato che è necessario consentire al client di ricompilare il modello ogni volta che viene utilizzato.

risposta data 02.11.2014 - 15:20
fonte

Leggi altre domande sui tag