一种编程语言,允许您为简单类型定义新的限制


19

许多语言一样C++C#Java允许你创建表示简单类型,如对象integerfloat。使用类接口,您可以覆盖运算符并执行逻辑,例如检查值是否超过业务规则100。

我想知道在某些语言中是否可以将这些规则定义为变量/属性的注释或属性。

例如,C#您可能会写:

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

或者也许C++您可以这样写:

int(0,100) x = 0;

我从未见过这样的事情,但是鉴于我们在存储之前对数据验证的依赖程度。奇怪的是,该功能尚未添加到语言中。

您能否举例说明可能的语言?


14
艾达不是这样吗?
zxcdw

2
@zxcdw:是的,Ada是(据我所知)第一种内置了对此类“类型”的支持的语言。命名受约束的数据类型。
m0nhawk

4
所有从属类型的语言都将具有此功能。它实际上是类型系统en.wikipedia.org/wiki/Dependent_type的固有内容,尽管您也可以在任何ML中创建这种性质的自定义类型,在这些语言中,类型也被定义为data Bool = True | False您想要说data Cents = 0 | 1 | 2 | ...的类型看看“代数数据类型”(应该更恰当地命名为hindley-milner类型,但人们会讨厌地将其与类型推论混淆)en.wikipedia.org/wiki/Algebraic_data_type
Jimmy Hoffa,

2
考虑到您所命名的语言如何处理整数上溢和下溢,如果您保持无声上溢/下溢,那么仅靠这种范围限制就不值钱了。

9
@StevenBurnap:类型不需要OO。type毕竟Pascal中有一个关键字。面向对象更多是一种设计模式,而不是编程语言的“原子”属性。
维尔贝尔

Answers:


26

Pascal具有子范围类型,即减少适合变量的数字数量。

  TYPE name = val_min .. val_max;

Ada也有范围的概念:http : //en.wikibooks.org/wiki/Ada_Programming/Types/range

来自维基百科。

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

也可以做

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

这里很酷

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

C没有严格的子范围类型,但是有一些方法可以通过使用位域来最小化所使用的位数来模仿一个(至少是有限的)。struct {int a : 10;} my_subrange_var;}。这可以作为变量内容的上限(通常我会说:不要为此使用位域,这只是为了证明这一点)。

许多其他语言的任意长度整数类型的解决方案都发生在库级别,例如C ++允许基于模板的解决方案。

有些语言允许监视变量状态并将断言连接到该状态。例如在Clojurescript中

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

(def A (atom 0 :validator mytest))

该函数mytest时被调用a已经改变(通过reset!swap!)检查条件是否被满足。这可能是在后期绑定语言中实现子范围行为的示例(请参阅http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/)。


2
如果您还添加有关依赖类型的详细信息会很好,那么这个问题就是依赖类型的整个用途和原因,似乎至少应该提到(即使它很深奥)
Jimmy Hoffa

虽然我对依赖类型和归纳推理/米尔纳类型推断有所了解。这些我很少练习。如果您想在我的答案中添加信息,请随时对其进行编辑。我打算通过归纳定义在数学中添加有关Peano公理和数字类型的信息,但是一个不错的ML数据示例也许更值得。
维尔贝尔

您可以使用枚举在C语言中混淆范围类型
John Cartwright

1
枚举是int或unsigned int类型的afaik(我认为它是特定于编译器的),并且未进行绑定检查。
维尔贝尔

它比这更酷:可以在数组声明和循环中使用范围类型,从而for y in Year_Type loop ... 消除诸如缓冲区溢出之类的问题。
Brian Drummond 2014年

8

Ada也是一种允许限制简单类型的语言,实际上,在Ada中,为程序定义自己的类型以确保正确性是一种很好的做法。

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

myVar1 : MyType1;

它被国防部使用了很长一段时间,也许仍然使用,但是我忘记了它的当前用途。


2
Ada仍广泛用于安全关键系统中。该语言的最新更新使该语言成为当今编写可靠和可维护的软件时可用的最佳语言之一。不幸的是,工具支持(编译器,IDE测试框架等)价格昂贵,并且落后于使用它的难度和生产率。
mattnz

遗憾,我记得第一次使用它,并且惊讶于它使代码变得如此清晰和无错误。很高兴听到它仍在积极更新,仍然是一种很棒的语言。
greedybuddha

@mattnz:GNAT是gcc套件的一部分,并且存在于免费和付费版本中。
基思·汤普森

@keith:GNAT编译器是免费的。IDE和框架仍然很昂贵并且缺乏功能。
mattnz

7

有关如何在C ++中创建经过范围检查的值类型的示例,请参见在C ++中限制值类型的范围。

摘要:使用模板创建具有内置最小值和最大值的值类型,您可以像这样使用:

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

您甚至根本不需要模板。您可以使用一个类来达到类似的效果。使用模板可以指定基础类型。另外,请务必注意,上述类型percent将不是float,而是模板的实例。这可能无法满足您问题的“简单类型”方面。

奇怪的是,该功能尚未添加到语言中。

简单的类型就这么简单。通常,最好将它们用作创建所需工具的基础,而不是直接使用。


2
@JimmyHoffa虽然我认为在某些情况下编译器可以检测到超出范围的条件,但范围检查通常需要在运行时进行。编译器可能无法知道您从Web服务器下载的值是否在范围内,或者用户是否会将太多记录添加到列表中,等等。
加勒布

7

据我所知,您的意图的某种受限形式是通过注释和动态代理模式(在Java和C#中存在用于动态代理的内置实现)在Java和C#中可能实现的。

Java版本

注释:

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

创建代理实例的Wrapper类:

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

InvocationHandler在每个方法调用时都充当旁路:

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

用法示例界面:

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

主要方法:

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

输出:

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#版本

注释(在C#中称为属性):

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

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

ExampleClass:

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

用法:

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

总之,您看到可以在Java中使用类似的方法,但是这样做并不完全方便,因为

  • 代理类只能为接口实例化,即您的类必须实现一个接口
  • 允许范围只能在接口级别上声明
  • 以后的使用在开始时就付出了额外的努力(MyInvocationHandler,在每个实例化时都进行了包装),这也稍微降低了可理解性

在DynamicObject类的功能C#删除接口的限制,如你在C#实现见。不幸的是,这种动态行为在这种情况下消除了静态类型的安全性,因此必须进行运行时检查以确定是否允许在动态代理上进行方法调用。

如果您可以接受这些限制,则可以作为进一步挖掘的基础!


1
谢谢,这是一个了不起的答案。在C#中这样的事情可能吗?
Reactgular

1
刚刚添加了一个示例C#实现!
McMannus

仅供参考:这public virtual int Min { get; private set; }是一个很好的技巧,可以大大缩短您的代码
BlueRaja-Danny Pflughoeft13年

2
这与Q的含义完全不同,原因在于您所做的基本上是动态的。这是打字的地方这个问题是要求一个对立面类型,不同之处时,范围是一个类型,它是在不运行时编译时执行。没有人问过如何在运行时验证范围,他希望通过在编译时检查的类型系统对其进行验证。
吉米·霍法

1
@JimmyHoffa啊,这很有道理。好点子:)
Reactgular

2

范围是不变式的特例。从维基百科:

一个不变的是,可以依靠一个程序的执行期间是真实的条件。

甲范围[a, b]可以声明为一个变量XInteger与不变量X> = AX <= B

因此,Ada或Pascal子范围类型不是严格必需的。它们可以用不变量的整数类型实现。


0

奇怪的是,该功能尚未添加到语言中。

在C ++和具有强大类型系统的其他语言中,不需要范围限制类型的特殊功能。

在C ++中,使用用户定义的类型可以相对简单地实现您的目标。 并且在需要范围限制类型的应用中,它们几乎是不够的。例如,还希望编译器验证物理单位计算是否正确编写,以便速度/时间产生加速度,而取加速度/时间的平方根则产生速度。要方便地执行此操作,需要能够定义类型系统,而不必明确命名公式中可能出现的每种类型。 这可以在C ++中完成

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.