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

19

Molte lingue come C++ , C# e Java ti consentono di creare oggetti che rappresentano tipi semplici come integer o 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 sia possibile in alcune lingue definire queste regole come annotazioni o attributi di una variabile / proprietà.

Ad esempio, in C# potresti scrivere:

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

O forse in C++ potresti scrivere:

int(0,100) x = 0;

Non ho mai visto qualcosa di simile, ma vista la nostra dipendenza dalla convalida dei dati prima della memorizzazione. È strano che questa funzione non sia stata aggiunta alle lingue.

Puoi dare un esempio di lingue dove è possibile?

    
posta cgTag 21.05.2013 - 19:03
fonte

6 risposte

26

Pascal aveva sottogruppi di tipi, ovvero riducendo il numero di numeri che si adattano a una variabile.

  TYPE name = val_min .. val_max;

Ada ha anche una nozione di intervalli: link

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 si raffredda

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

C non ha un tipo subrange stretto, ma ci sono modi per imitare uno (almeno limitato) usando bitfield per minimizzare il numero di bit usati. %codice%. Questo può funzionare come limite superiore per il contenuto variabile (in generale direi: non usare bitfield per questo , questo è solo per dimostrare un punto).

Molte soluzioni per tipi interi di lunghezza arbitraria in altre lingue avvengono invece a livello di libreria, I.e. C ++ consente soluzioni basate su modelli.

Esistono linguaggi che consentono il monitoraggio degli stati variabili e le asserzioni di connessione 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 struct {int a : 10;} my_subrange_var;} viene chiamata quando mytest è cambiata (tramite a o reset! ) controlla se le condizioni sono soddisfatte. Questo potrebbe essere un esempio per l'implementazione del comportamento di subrange nei linguaggi tardivi (vedere link ).

    
risposta data 21.05.2013 - 19:16
fonte
8

Ada è anche un linguaggio che consente limiti per i tipi semplici, infatti in Ada è buona norma definire i tuoi tipi per il tuo programma per garantire la correttezza.

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

myVar1 : MyType1;

È stato usato per molto tempo dal DoD, forse lo è ancora ma ho perso la cognizione del suo uso corrente.

    
risposta data 22.05.2013 - 00:16
fonte
7

Vedi Limitare l'intervallo di tipi di valore in C ++ per esempi su come creare un tipo di valore con controllo di intervallo in C ++.

Riepilogo esecutivo: utilizza un modello per creare un tipo di valore con valori minimi e massimi predefiniti, che puoi 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 davvero nemmeno bisogno di un modello qui; potresti usare una classe con effetti simili. L'uso di un modello ti consente di specificare il tipo sottostante. Inoltre, è importante notare che il tipo di percent sopra non sarà un float , ma piuttosto un'istanza del modello. Questo potrebbe non soddisfare l'aspetto "tipi semplici" della tua domanda.

It's strange that this feature hasn't been added to languages.

I tipi semplici sono proprio questo: semplice. Spesso vengono utilizzati al meglio come elementi costitutivi per la creazione degli strumenti necessari anziché essere utilizzati direttamente.

    
risposta data 21.05.2013 - 23:23
fonte
7

Qualche forma ristretta delle tue intenzioni è a mia conoscenza possibile in Java e C # attraverso una combinazione di annotazioni e pattern proxy dinamico (esistono implementazioni integrate per i 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 per 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'utilizzo:

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);

Output:

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; }
}

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;
    }
}

The ExampleClass:

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

Utilizzo:

    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 lavorare in Java , ma non è del tutto conveniente, perché

  • La classe proxy può essere istanziata solo per le interfacce, cioè la tua classe deve implementare un'interfaccia
  • L'intervallo consentito può essere dichiarato solo a livello di interfaccia
  • L'uso successivo viene fornito con uno sforzo extra all'inizio (MyInvocationHandler, che esegue il wrapping ad ogni istanziazione) che riduce anche leggermente la comprensibilità

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

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

    
risposta data 21.05.2013 - 22:13
fonte
2

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

An invariant is a condition that can be relied upon to be true during execution of a program.

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

Quindi i tipi di sub-serie di Ada o Pascal non sono strettamente necessari. Potrebbero essere implementati con un tipo intero con invarianti.

    
risposta data 23.05.2013 - 22:11
fonte
0

It's strange that this feature hasn't been added to languages.

Le funzionalità speciali per i tipi a gamma limitata non sono necessarie in C ++ e in altri linguaggi con potenti sistemi di tipi.

In C ++, i tuoi obiettivi possono essere soddisfatti in modo relativamente semplice con tipi definiti dall'utente . E nelle applicazioni in cui sono desiderabili tipi a gamma limitata, sono appena sufficienti . Ad esempio, si vorrebbe anche che il compilatore verificasse che i calcoli delle unità fisiche siano stati scritti correttamente, in modo che la velocità / tempo produca un'accelerazione e che 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 ++ .

    
risposta data 23.05.2013 - 00:25
fonte

Leggi altre domande sui tag