Eine Programmiersprache, mit der Sie neue Grenzen für einfache Typen definieren können

19

Viele Sprachen mögen C++, C#und Javaermöglichen es Ihnen, Objekte zu erstellen, die einfache Typen wie integeroder darstellen float. Mit einer Klassenschnittstelle können Sie Operatoren überschreiben und logisch prüfen, ob ein Wert eine Geschäftsregel von 100 überschreitet.

Ich frage mich, ob es in einigen Sprachen möglich ist, diese Regeln als Anmerkungen oder Attribute einer Variablen / Eigenschaft zu definieren.

Zum Beispiel C#könnten Sie schreiben:

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

Oder vielleicht C++könnten Sie schreiben:

int(0,100) x = 0;

Ich habe so etwas noch nie gesehen, aber angesichts der Tatsache, wie abhängig wir von der Datenvalidierung vor der Speicherung geworden sind. Es ist seltsam, dass diese Funktion nicht zu Sprachen hinzugefügt wurde.

Können Sie Beispiele für Sprachen nennen, in denen dies möglich ist?

Reactgular
quelle
14
Ist Ada nicht so etwas?
zxcdw
2
@zxcdw: Ja, Ada war die erste Sprache (wie ich weiß), die Unterstützung für solche "Typen" eingebaut hat. Benannte eingeschränkte Datentypen.
m0nhawk
4
Alle abhängig getippten Sprachen würden diese Fähigkeit haben. Es ist wesentlicher Bestandteil des Typsystems en.wikipedia.org/wiki/Dependent_type realistisch , obwohl Sie auch einen benutzerdefinierten Typ dieser Art in jedem ML schaffen könnte, in diesen Sprachen ist eine Art definiert data Bool = True | Falseund für das, was Sie wollen , könnte man sagen , data Cents = 0 | 1 | 2 | ...eine haben Blick auf „Algebraische Datentypen“ (was mehr richtig mit Typinferenz benannt werden sollte hindley-milner Typen , aber die Leute zu verwirren , dass annoyingly) en.wikipedia.org/wiki/Algebraic_data_type
Jimmy Hoffa
2
In Anbetracht dessen, wie die Sprachen, die Sie benennen, mit ganzzahligen Über- und Unterläufen umgehen, wäre eine solche Bereichseinschränkung für sich genommen nicht viel wert, wenn Sie den stillen Über- / Unterlauf beibehalten.
9
@StevenBurnap: Typen erfordern keine OO. typeSchließlich gibt es in Pascal ein Schlüsselwort. Objektorientierung ist eher ein Entwurfsmuster als eine "atomare" Eigenschaft von Programmiersprachen.
Wirrbel

Antworten:

26

Pascal hatte Unterbereichstypen, dh die Anzahl der Zahlen, die in eine Variable passen, wurde verringert.

  TYPE name = val_min .. val_max;

Ada hat auch eine Vorstellung von Bereichen: http://en.wikibooks.org/wiki/Ada_Programming/Types/range

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

kann auch tun

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

Und hier wird es cool

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

C hat keinen strengen Unterbereichstyp, aber es gibt Möglichkeiten, einen (zumindest eingeschränkten) nachzubilden, indem Bitfelder verwendet werden, um die Anzahl der verwendeten Bits zu minimieren. struct {int a : 10;} my_subrange_var;}. Dies kann als Obergrenze für variablen Inhalt dienen (im Allgemeinen würde ich sagen: Verwenden Sie keine Bitfelder , dies ist nur ein Beweis für einen Punkt).

Viele Lösungen für Integer-Typen beliebiger Länge in anderen Sprachen finden eher auf Bibliotheksebene statt, dh C ++ ermöglicht vorlagenbasierte Lösungen.

Es gibt Sprachen, mit denen variable Zustände überwacht und Zusicherungen damit verbunden werden können. Zum Beispiel in Clojurescript

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

(def A (atom 0 :validator mytest))

Die Funktion mytestwird aufgerufen, wenn asich (über reset!oder swap!) geändert hat und überprüft, ob die Bedingungen erfüllt sind. Dies könnte ein Beispiel für die Implementierung des Verhaltens von Unterbereichen in Sprachen mit später Bindung sein (siehe http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/ ).

wirrbel
quelle
2
Wenn Sie auch ein Detail über abhängige Typen hinzufügen würden, wäre dies ein schönes Problem, das den gesamten Zweck und den Grund für die abhängige Typisierung darstellt. Dies sollte zumindest erwähnt werden (auch wenn es esoterisch ist)
Jimmy Hoffa
Während ich etwas Verständnis für abhängige Typen und induktive Argumentation / Milner-Typ-Inferenz habe. Ich habe wenig Übung damit. Wenn Sie meiner Antwort Informationen hinzufügen möchten, können Sie diese jederzeit bearbeiten. Ich wollte etwas über die Peano-Axiome und die Zahlentypen in der Mathematik per induktiver Definition hinzufügen, aber ein schönes Beispiel für ML-Daten könnte sich vielleicht mehr lohnen.
Wirrbel
Sie könnten einen Bereichstyp in C mit enum kludgen
John Cartwright
1
enum ist afaik vom Typ int oder unsigned int (ich denke, es ist compilerspezifisch) und nicht gebunden geprüft.
Wirrbel
Es wird kühler als das: Die Range-Typen können in Array-Deklarationen und für Schleifen verwendet werden, for y in Year_Type loop ... wodurch Probleme wie Pufferüberläufe beseitigt werden.
Brian Drummond
8

Ada ist auch eine Sprache, die Grenzen für einfache Typen zulässt. In Ada empfiehlt es sich , eigene Typen für Ihr Programm zu definieren, um die Korrektheit zu gewährleisten.

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

myVar1 : MyType1;

Es wurde lange Zeit vom Verteidigungsministerium verwendet, ist es vielleicht immer noch, aber ich habe den Überblick über seine derzeitige Verwendung verloren.

greedybuddha
quelle
2
Ada wird immer noch häufig in sicherheitskritischen Systemen eingesetzt. Die Sprache wurde kürzlich aktualisiert, sodass sie heute zu den besten Sprachen für das Schreiben zuverlässiger und wartbarer Software gehört. Leider ist die Toolunterstützung (Compiler, IDEs-Testframeworks usw.) teuer und verzögert sich, was die Arbeit schwierig und unproduktiv macht.
Mattnz
Schade, ich erinnere mich, es zum ersten Mal benutzt zu haben und war erstaunt, wie klar und fehlerfrei es Code machte. Freut mich zu hören, dass es immer noch aktiv aktualisiert wird, immer noch eine großartige Sprache.
Greedybuddha
@mattnz: GNAT ist Teil der gcc-Suite und ist sowohl als kostenlose als auch als kostenpflichtige Version erhältlich.
Keith Thompson
@keith: GNAT Compiler ist kostenlos. IDEs und Frameworks sind immer noch teuer und funktionieren nicht.
Mattnz
7

Siehe Einschränken des Wertebereichs in C ++ Beispiele zum Erstellen eines Bereichsüberprüfung in C ++.

Zusammenfassung: Verwenden Sie eine Vorlage, um einen Wertetyp mit integrierten Mindest- und Höchstwerten zu erstellen, die Sie folgendermaßen verwenden können:

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

Sie brauchen hier nicht einmal eine Vorlage. Sie könnten eine Klasse mit ähnlichem Effekt verwenden. Mithilfe einer Vorlage können Sie den zugrunde liegenden Typ angeben. Es ist auch wichtig zu beachten, dass der oben genannte Typ percentkein float, sondern eine Instanz der Vorlage ist. Dies entspricht möglicherweise nicht dem Aspekt "einfache Typen" Ihrer Frage.

Es ist seltsam, dass diese Funktion nicht zu Sprachen hinzugefügt wurde.

Einfache Typen sind genau das - einfach. Sie werden häufig am besten als Bausteine ​​für die Erstellung der benötigten Tools verwendet, anstatt direkt verwendet zu werden.

Caleb
quelle
2
@JimmyHoffa Obwohl es einige Fälle gibt, in denen ein Compiler Bedingungen außerhalb des Bereichs erkennen kann, muss die Bereichsprüfung meist zur Laufzeit durchgeführt werden. Der Compiler kann möglicherweise nicht wissen, ob der Wert, den Sie von einem Webserver herunterladen, im Bereich liegt oder ob der Benutzer einer Liste zu viele Datensätze hinzufügt oder was auch immer.
Caleb
7

Eine eingeschränkte Form Ihrer Absicht ist meines Wissens nach in Java und C # durch eine Kombination aus Anmerkungen und dynamischem Proxy-Muster möglich (es gibt integrierte Implementierungen für dynamische Proxys in Java und C #).

Java-Version

Die Anmerkung:

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

Die Wrapper-Klasse, die die Proxy-Instanz erstellt:

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

Der InvocationHandler, der bei jedem Methodenaufruf als Bypass dient:

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

Die Beispiel-Oberfläche zur Verwendung:

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

Hauptmethode:

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

Ausgabe:

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

Die Anmerkung (in C # Attribut genannt):

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

Die DynamicObject-Unterklasse:

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

Die ExampleClass:

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

Verwendung:

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

Abschließend sehen Sie, dass Sie so etwas in Java zum Laufen bringen können , aber es ist nicht ganz praktisch, weil

  • Proxy-Klasse kann nur für Schnittstellen instanziiert werden, dh Ihre Klasse muss eine Schnittstelle implementieren
  • Zulässiger Bereich kann nur auf Schnittstellenebene deklariert werden
  • Die spätere Verwendung ist anfangs nur mit zusätzlichem Aufwand verbunden (MyInvocationHandler, bei jeder Instanziierung umbrechen), was auch die Verständlichkeit leicht beeinträchtigt

Die Funktionen der DynamicObject-Klasse in C # heben die Schnittstelleneinschränkung auf, wie Sie in der C # -Implementierung sehen. Leider wird durch dieses dynamische Verhalten die Sicherheit statischer Typen in diesem Fall aufgehoben. Daher sind Laufzeitprüfungen erforderlich, um festzustellen, ob ein Methodenaufruf für den dynamischen Proxy zulässig ist.

Wenn diese Einschränkungen für Sie akzeptabel sind, kann dies als Grundlage für das weitere Graben dienen!

McMannus
quelle
1
Danke, das ist eine großartige Antwort. Ist so etwas in C # möglich?
Reactgular
1
Habe gerade ein Beispiel für eine C # -Implementierung hinzugefügt!
McMannus
Nur zu public virtual int Min { get; private set; }Ihrer
Information
2
Dies unterscheidet sich grundlegend von dem, worum es beim Q geht. Der Grund dafür ist, dass Sie im Grunde genommen Dynamik betreiben. Dies ist die Antithese zur Typisierung, bei der diese Frage nach einem Typ fragt . Der Unterschied besteht darin, dass der Bereich für einen Typ zur Kompilierungszeit und nicht zur Laufzeit erzwungen wird. Niemand fragte, wie Bereiche zur Laufzeit validiert werden sollen, er wollte, dass sie von dem Typsystem validiert werden, das zur Kompilierungszeit überprüft wird.
Jimmy Hoffa
1
@ JimmyHoffa ah das macht Sinn. Guter Punkt :)
Reactgular
2

Bereiche sind ein Sonderfall von Invarianten. Aus Wikipedia:

Eine Invariante ist eine Bedingung, auf die man sich verlassen kann, wenn ein Programm ausgeführt wird.

Ein Bereich [a, b]kann als Variable x vom Typ Integermit den Invarianten x> = a und x <= b deklariert werden .

Daher sind Ada- oder Pascal-Unterbereichstypen nicht unbedingt erforderlich. Sie könnten mit einem Integer-Typ mit Invarianten implementiert werden.

einfach
quelle
0

Es ist seltsam, dass diese Funktion nicht zu Sprachen hinzugefügt wurde.

In C ++ und anderen Sprachen mit leistungsstarken Typsystemen sind keine speziellen Funktionen für Typen mit eingeschränktem Bereich erforderlich.

In C ++ können Ihre Ziele mit benutzerdefinierten Typen relativ einfach erreicht werden . Und in Anwendungen, in denen Typen mit begrenzter Reichweite wünschenswert sind, reichen sie kaum aus . Beispielsweise möchte man auch, dass der Compiler überprüft, ob physikalische Einheitenberechnungen korrekt geschrieben wurden, sodass Geschwindigkeit / Zeit eine Beschleunigung erzeugt und die Quadratwurzel aus Beschleunigung / Zeit eine Geschwindigkeit erzeugt. Dies erfordert die Möglichkeit, ein Typensystem zu definieren, ohne jeden Typ, der jemals in einer Formel vorkommen könnte, explizit zu benennen. Dies kann in C ++ erfolgen .

Kevin Cline
quelle