Un linguaggio di programmazione che consente di definire nuovi limiti per tipi semplici


19

Molte lingue come C++, C#e Javati consentono di creare oggetti che rappresentano tipi semplici come integero float. Utilizzando un'interfaccia di classe è possibile ignorare gli operatori ed eseguire la logica come verificare se un valore supera una regola aziendale di 100.

Mi chiedo se in alcune lingue sia possibile definire queste regole come annotazioni o attributi di una variabile / proprietà.

Ad esempio, C#potresti scrivere:

[Range(0,100)]
public int Price { get; set; }

O forse in C++te potresti scrivere:

int(0,100) x = 0;

Non ho mai visto qualcosa di simile, ma dato quanto siamo diventati dipendenti dalla convalida dei dati prima della memorizzazione. È strano che questa funzione non sia stata aggiunta alle lingue.

Puoi dare un esempio di lingue in cui ciò è possibile?


14
Ada non è qualcosa del genere?
zxcdw,

2
@zxcdw: Sì, Ada è stata la prima lingua (come so) che ha integrato il supporto per tali "tipi". Tipi di dati vincolati denominati.
m0nhawk,

4
Tutte le lingue tipicamente dipendenti avrebbero questa capacità. È intrinseco al sistema di tipi en.wikipedia.org/wiki/Dependent_type realisticamente sebbene tu possa creare un tipo personalizzato di questa natura anche in qualsiasi ML, in queste lingue un tipo è definito come data Bool = True | Falsee per quello che vuoi potresti dire data Cents = 0 | 1 | 2 | ...avere un guarda "Tipi di dati algebrici" (che dovrebbero essere chiamati in modo più appropriato tipi di hindley-mili, ma la gente lo confonde con fastidiosa inferenza di tipo) en.wikipedia.org/wiki/Algebraic_data_type
Jimmy Hoffa

2
Dato il modo in cui le lingue che nominate gestiscono l'overflow e il underflow dell'intero, una tale limitazione di intervallo da sola non varrebbe molto se mantenete il overflow / underflow silenzioso.

9
@StevenBurnap: i tipi non richiedono OO. Dopo tutto, c'è una typeparola chiave in Pascal. L'orientamento agli oggetti è più un modello di progettazione che una proprietà "atomar" dei linguaggi di programmazione.
Wirrbel,

Risposte:


26

Pascal aveva tipi di sottorange, cioè diminuendo il numero di numeri che rientrano in una variabile.

  TYPE name = val_min .. val_max;

Ada ha anche una nozione di intervalli: http://en.wikibooks.org/wiki/Ada_Programming/Types/range

Da Wikipedia ....

type Day_type   is range    1 ..   31;
type Month_type is range    1 ..   12;
type Year_type  is range 1800 .. 2100;
type Hours is mod 24;
type Weekday is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday); 

può anche fare

subtype Weekend is  Weekday (Saturday..Sunday);
subtype WorkDay is  Weekday (Monday..Friday);

Ed ecco dove fa freddo

year : Year_type := Year_type`First -- 1800 in this case...... 

C non ha un tipo di sottorange rigoroso, ma ci sono modi per imitarne uno (almeno limitato) usando i campi di bit per minimizzare il numero di bit usati. struct {int a : 10;} my_subrange_var;}. Questo può funzionare come limite superiore per il contenuto variabile (in generale direi: non usare bitfield per questo , questo è solo per provare un punto).

Molte soluzioni per tipi interi di lunghezza arbitraria in altre lingue si presentano piuttosto a livello di libreria, ovvero C ++ consente soluzioni basate su template.

Esistono lingue che consentono il monitoraggio di stati variabili e il collegamento di asserzioni ad esso. Ad esempio in Clojurescript

(defn mytest 
   [new-val] 
   (and (< new-val 10)
        (<= 0 new-val)))

(def A (atom 0 :validator mytest))

La funzione mytestviene chiamata quando aè stata modificata (tramite reset!o swap!) verifica se le condizioni sono soddisfatte. Questo potrebbe essere un esempio per l'implementazione del comportamento subrange in linguaggi in ritardo (vedi http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/ ).


2
Se aggiungessi un dettaglio sui tipi dipendenti sarebbe bello, questo problema è l'intero scopo e la ragione della digitazione dipendente, sembra che dovrebbe essere almeno menzionato (anche se è esoterico)
Jimmy Hoffa

Mentre ho una certa comprensione dei tipi dipendenti e del ragionamento induttivo / inferenza di tipo più lieve. Ho poca pratica con quelli. Se vuoi aggiungere informazioni alla mia risposta, sentiti libero di modificarle. Stavo per aggiungere qualcosa sugli assiomi di Peano e sui tipi di numeri in matematica per definizione induttiva, ma forse un buon esempio di dati ML potrebbe essere più utile.
Wirrbel

potresti inserire un tipo di intervallo in C usando enum
John Cartwright

1
enum è afaik di tipo int o unsigned int (penso che sia specifico del compilatore) e non controllato in modo limite.
Wirrbel,

Diventa più freddo di così: i tipi a distanza possono essere usati nelle dichiarazioni di array e per i loop for y in Year_Type loop ... eliminando problemi come overflow del buffer.
Brian Drummond,

8

Ada è anche un linguaggio che consente limiti per tipi semplici, infatti in Ada è buona norma definire i propri tipi affinché il programma garantisca la correttezza.

type MyType1   is range    1 .. 100;
type MyType2   is range    5 .. 15;

myVar1 : MyType1;

È stato usato a lungo dal DoD, forse lo è ancora ma ho perso la traccia del suo uso corrente.


2
Ada è ancora ampiamente utilizzato nei sistemi critici per la sicurezza. C'è un recente aggiornamento della lingua che la rende una delle migliori oggi disponibili per la scrittura di software affidabile e gestibile. Sfortunatamente il supporto degli strumenti (compilatori, framework di test IDE ecc.) È costoso e in ritardo che rende difficile e improduttivo lavorare con.
mattnz,

Peccato, ricordo di averlo usato per la prima volta e sono rimasto sorpreso da quanto sia chiaro e privo di bug il codice. Sono contento di sentire che è ancora attivamente aggiornato, ancora una grande lingua.
greedybuddha,

@mattnz: GNAT fa parte della suite gcc ed esiste sia in versione gratuita che a pagamento.
Keith Thompson,

@keith: il compilatore GNAT è gratuito. IDE e framework sono ancora costosi e mancano di funzionalità.
mattnz,

7

Vedere Limitazione dell'intervallo di tipi di valore in C ++ per esempi su come creare un tipo di valore controllato in intervallo in C ++.

Riepilogo esecutivo: utilizzare un modello per creare un tipo di valore con valori minimi e massimi incorporati, che è possibile utilizzare in questo modo:

// create a float named 'percent' that's limited to the range 0..100
RangeCheckedValue<float, 0, 100> percent(50.0);

Non hai nemmeno bisogno di un modello qui; potresti usare una classe con effetti simili. L'uso di un modello consente di specificare il tipo sottostante. Inoltre, è importante notare che il tipo dipercent sopra non sarà a float, ma piuttosto un'istanza del modello. Questo potrebbe non soddisfare l'aspetto "tipi semplici" della tua domanda.

È strano che questa funzione non sia stata aggiunta alle lingue.

I tipi semplici sono proprio questo: semplici. Spesso sono meglio utilizzati come elementi costitutivi per la creazione degli strumenti necessari invece di essere utilizzati direttamente.


2
@JimmyHoffa Mentre suppongo ci siano alcuni casi in cui un compilatore è in grado di rilevare condizioni fuori range, il controllo della gamma deve avvenire principalmente in fase di esecuzione. Il compilatore non può sapere se il valore scaricato da un server Web sarà compreso nell'intervallo o se l'utente aggiungerà un numero eccessivo di record a un elenco o altro.
Caleb,

7

Qualche forma limitata della tua intenzione è la mia conoscenza possibile in Java e C # attraverso una combinazione di Annotazioni e Dynamic Proxy Pattern (esistono implementazioni integrate per proxy dinamici in Java e C #).

Versione Java

L'annotazione:

@Target(ElementType.PARAMETER)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {
     int min ();
     int max ();
}

La classe Wrapper che crea l'istanza Proxy:

public class Wrapper {
    public static Object wrap(Object obj) {
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new MyInvocationHandler(obj));
    }
}

InvocationHandler che funge da bypass ad ogni chiamata di metodo:

public class MyInvocationHandler implements InvocationHandler {
    private Object impl;

    public MyInvocationHandler(Object obj) {
        this.impl = obj;
    }

@Override
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
    Annotation[][] parAnnotations = method.getParameterAnnotations();
    Annotation[] par = null;
    for (int i = 0; i<parAnnotations.length; i++) {
        par = parAnnotations[i];
        if (par.length > 0) {
            for (Annotation anno : par) {
                if (anno.annotationType() == IntRange.class) {
                    IntRange range = ((IntRange) anno);
                    if ((int)args[i] < range.min() || (int)args[i] > range.max()) {
                        throw new Throwable("int-Parameter "+(i+1)+" in method \""+method.getName()+"\" must be in Range ("+range.min()+","+range.max()+")"); 
                    }
                }
            }
        }
    }
    return method.invoke(impl, args);
}
}

L'interfaccia di esempio per l'uso:

public interface Example {
    public void print(@IntRange(min=0,max=100) int num);
}

Main-Metodo:

Example e = new Example() {
    @Override
    public void print(int num) {
        System.out.println(num);
    }
};
e = (Example)Wrapper.wrap(e);
e.print(-1);
e.print(10);

Produzione:

Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.print(Unknown Source)
at application.Main.main(Main.java:13)
Caused by: java.lang.Throwable: int-Parameter 1 in method "print" must be in Range (0,100)
at application.MyInvocationHandler.invoke(MyInvocationHandler.java:27)
... 2 more

C # -Version

L'annotazione (in C # chiamato attributo):

[AttributeUsage(AttributeTargets.Parameter)]
public class IntRange : Attribute
{
    public IntRange(int min, int max)
    {
        Min = min;
        Max = max;
    }

    public virtual int Min { get; private set; }

    public virtual int Max { get; private set; }
}

La sottoclasse DynamicObject:

public class DynamicProxy : DynamicObject
{
    readonly object _target;

    public DynamicProxy(object target)
    {
        _target = target;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        TypeInfo clazz = (TypeInfo) _target.GetType();
        MethodInfo method = clazz.GetDeclaredMethod(binder.Name);
        ParameterInfo[] paramInfo = method.GetParameters();
        for (int i = 0; i < paramInfo.Count(); i++)
        {
            IEnumerable<Attribute> attributes = paramInfo[i].GetCustomAttributes();
            foreach (Attribute attr in attributes)
            {
                if (attr is IntRange)
                {
                    IntRange range = attr as IntRange;
                    if ((int) args[i] < range.Min || (int) args[i] > range.Max)
                        throw new AccessViolationException("int-Parameter " + (i+1) + " in method \"" + method.Name + "\" must be in Range (" + range.Min + "," + range.Max + ")");
                }
            }
        }

        result = _target.GetType().InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, _target, args);

        return true;
    }
}

EsempioClass:

public class ExampleClass
{
    public void PrintNum([IntRange(0,100)] int num)
    {
        Console.WriteLine(num.ToString());
    }
}

Uso:

    static void Main(string[] args)
    {
        dynamic myObj = new DynamicProxy(new ExampleClass());
        myObj.PrintNum(99);
        myObj.PrintNum(-5);
    }

In conclusione, vedi che puoi ottenere qualcosa del genere per funzionare in Java , ma non è del tutto conveniente, perché

  • La classe proxy può essere solo istanziata per le interfacce, ovvero la tua classe deve implementare un'interfaccia
  • L'intervallo consentito può essere dichiarato solo a livello di interfaccia
  • L'utilizzo successivo arriva solo con uno sforzo supplementare all'inizio (MyInvocationHandler, avvolgimento ad ogni istanza) che riduce anche leggermente la comprensibilità

Le funzionalità della classe DynamicObject in C # rimuovono la restrizione dell'interfaccia, come vedi nell'implementazione di C #. Sfortunatamente, questo comportamento dinamico rimuove in questo caso la sicurezza di tipo statico, quindi sono necessari controlli di runtime per determinare se è consentita una chiamata di metodo sul proxy dinamico.

Se queste restrizioni sono accettabili per te, allora questo può servire come base per ulteriori scavi!


1
grazie, questa è una risposta fantastica. È possibile qualcosa di simile in C #?
Reactgular,

1
Ho appena aggiunto un'implementazione di esempio in C #!
McMannus,

Cordiali saluti: public virtual int Min { get; private set; }è un bel trucco che accorcerebbe significativamente il tuo codice
BlueRaja - Danny Pflughoeft

2
Questo è totalmente diverso da ciò che riguarda la Q, il motivo è quello che stai facendo è sostanzialmente la dinamica; che è l'antitesi della digitazione in cui questa domanda richiede un tipo , la differenza è quando l'intervallo è su un tipo, viene applicato in fase di compilazione e non in fase di esecuzione. Nessuno ha chiesto come convalidare gli intervalli in fase di esecuzione, lo ha voluto convalidato dal sistema di tipi che viene verificato in fase di compilazione.
Jimmy Hoffa,

1
@JimmyHoffa ah ha senso. Buon punto :)
Reactgular

2

Gli intervalli sono un caso speciale di invarianti. Da Wikipedia:

Un invariante è una condizione su cui si può fare affidamento per essere vera durante l'esecuzione di un programma.

Un intervallo [a, b]può essere dichiarato come una variabile x di tipo Integercon gli invarianti x> = a e x <= b .

Pertanto i tipi di subrange Ada o Pascal non sono strettamente necessari. Potrebbero essere implementati con un tipo intero con invarianti.


0

È strano che questa funzione non sia stata aggiunta alle lingue.

Funzionalità speciali per tipi con intervallo limitato non sono necessarie in C ++ e in altre lingue con sistemi di tipi potenti.

In C ++, i tuoi obiettivi possono essere raggiunti in modo relativamente semplice con tipi definiti dall'utente . E nelle applicazioni in cui sono desiderabili tipi a portata limitata, sono appena sufficienti . Ad esempio, si vorrebbe anche che il compilatore verifichi che i calcoli delle unità fisiche sono stati scritti correttamente, in modo che velocità / tempo producano un'accelerazione, e prendendo la radice quadrata di accelerazione / tempo produca una velocità. Fare ciò richiede convenientemente la capacità di definire un sistema di tipi, senza nominare esplicitamente ogni tipo che potrebbe mai apparire in una formula. Questo può essere fatto in C ++ .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.