A programming language that allows you to define new limits for simple types

Many languages like C++, C#, and Java allow you to create objects that represent simple types like integer or float. Using a class interface you can override operators and perform logic like checking if a value exceeds a business rule of 100.

I’m wondering if it’s possible in some languages to define these rules as annotations or attributes of a variable/property.

For example, in C# you might write:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>[Range(0,100)]
public int Price { get; set; }
</code>
<code>[Range(0,100)] public int Price { get; set; } </code>
[Range(0,100)]
public int Price { get; set; }

Or maybe in C++ you could write:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>int(0,100) x = 0;
</code>
<code>int(0,100) x = 0; </code>
int(0,100) x = 0;

I’ve never seen something like this done, but given how dependent we have become on data validation before storage. It’s strange that this feature hasn’t been added to languages.

Can you give example of languages where this is possible?

14

Pascal had subrange types, i.e. decreasing the number of numbers that fit into a variable.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> TYPE name = val_min .. val_max;
</code>
<code> TYPE name = val_min .. val_max; </code>
  TYPE name = val_min .. val_max;

Ada also has a notion of ranges: http://en.wikibooks.org/wiki/Ada_Programming/Types/range

From Wikipedia….

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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);
</code>
<code>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); </code>
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); 

can also do

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>subtype Weekend is Weekday (Saturday..Sunday);
subtype WorkDay is Weekday (Monday..Friday);
</code>
<code>subtype Weekend is Weekday (Saturday..Sunday); subtype WorkDay is Weekday (Monday..Friday); </code>
subtype Weekend is  Weekday (Saturday..Sunday);
subtype WorkDay is  Weekday (Monday..Friday);

And here’s where it gets cool

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>year : Year_type := Year_type`First -- 1800 in this case......
</code>
<code>year : Year_type := Year_type`First -- 1800 in this case...... </code>
year : Year_type := Year_type`First -- 1800 in this case...... 

C does not have a strict subrange type, but there are ways to mimic one (at least limited) by using bitfields to minimize the number of bits used. struct {int a : 10;} my_subrange_var;}. This can work as an upper bound for variable content (in general I would say: don’t use bitfields for this, this is just to proof a point).

A lot of solutions for arbitrary-length integer types in other languages rather happen on the library-level, I.e. C++ allows for template based solutions.

There are languages that allow for monitoring of variable states and connecting assertions to it. For example in Clojurescript

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>(defn mytest
[new-val]
(and (< new-val 10)
(<= 0 new-val)))
(def A (atom 0 :validator mytest))
</code>
<code>(defn mytest [new-val] (and (< new-val 10) (<= 0 new-val))) (def A (atom 0 :validator mytest)) </code>
(defn mytest 
   [new-val] 
   (and (< new-val 10)
        (<= 0 new-val)))

(def A (atom 0 :validator mytest))

The function mytest is called when a has changed (via reset! or swap!) checks whether conditions are met. This could be an example for implementing subrange behaviour in late-binding languages (see http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/ ).

5

Ada also is a language that allows limits for simple types, in fact in Ada it’s good practice to define your own types for your program to guarantee correctness.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>type MyType1 is range 1 .. 100;
type MyType2 is range 5 .. 15;
myVar1 : MyType1;
</code>
<code>type MyType1 is range 1 .. 100; type MyType2 is range 5 .. 15; myVar1 : MyType1; </code>
type MyType1   is range    1 .. 100;
type MyType2   is range    5 .. 15;

myVar1 : MyType1;

It was used for a long time by the DoD, maybe still is but I’ve lost track of it’s current use.

4

See Limiting range of value types in C++ for examples of how to create a range-checked value type in C++.

Executive summary: Use a template to create a value type that has built-in minimum and maximum values, which you can use like this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>// create a float named 'percent' that's limited to the range 0..100
RangeCheckedValue<float, 0, 100> percent(50.0);
</code>
<code>// create a float named 'percent' that's limited to the range 0..100 RangeCheckedValue<float, 0, 100> percent(50.0); </code>
// create a float named 'percent' that's limited to the range 0..100
RangeCheckedValue<float, 0, 100> percent(50.0);

You don’t really even need a template here; you could use a class to similar effect. Using a template lets you specify the underlying type. Also, it’s important to note that the type of percent above won’t be a float, but rather an instance of the template. This may not satisfy the ‘simple types’ aspect of your question.

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

Simple types are just that — simple. They’re often best used as the building blocks for creating the tools you need instead of being used directly.

1

Some restricted form of your intention is to my knowledge possible in Java and C# through a combination of Annotations and Dynamic Proxy Pattern (there exist built-in implementations for dynamic proxies in Java and C#).

Java version

The annotation:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>@Target(ElementType.PARAMETER)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {
int min ();
int max ();
}
</code>
<code>@Target(ElementType.PARAMETER) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface IntRange { int min (); int max (); } </code>
@Target(ElementType.PARAMETER)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {
     int min ();
     int max ();
}

The Wrapper class creating the Proxy instance:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class Wrapper {
public static Object wrap(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new MyInvocationHandler(obj));
}
}
</code>
<code>public class Wrapper { public static Object wrap(Object obj) { return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new MyInvocationHandler(obj)); } } </code>
public class Wrapper {
    public static Object wrap(Object obj) {
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new MyInvocationHandler(obj));
    }
}

The InvocationHandler serving as bypass at every method call:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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);
}
}
</code>
<code>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); } } </code>
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);
}
}

The Example-Interface for Usage:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public interface Example {
public void print(@IntRange(min=0,max=100) int num);
}
</code>
<code>public interface Example { public void print(@IntRange(min=0,max=100) int num); } </code>
public interface Example {
    public void print(@IntRange(min=0,max=100) int num);
}

Main-Method:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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);
</code>
<code>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); </code>
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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
</code>
<code>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 </code>
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

The annotation (in C# called attribute):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>[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; }
}
</code>
<code>[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; } } </code>
[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; }
}

The DynamicObject Sub-Class:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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;
}
}
</code>
<code>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; } } </code>
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class ExampleClass
{
public void PrintNum([IntRange(0,100)] int num)
{
Console.WriteLine(num.ToString());
}
}
</code>
<code>public class ExampleClass { public void PrintNum([IntRange(0,100)] int num) { Console.WriteLine(num.ToString()); } } </code>
public class ExampleClass
{
    public void PrintNum([IntRange(0,100)] int num)
    {
        Console.WriteLine(num.ToString());
    }
}

Usage:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> static void Main(string[] args)
{
dynamic myObj = new DynamicProxy(new ExampleClass());
myObj.PrintNum(99);
myObj.PrintNum(-5);
}
</code>
<code> static void Main(string[] args) { dynamic myObj = new DynamicProxy(new ExampleClass()); myObj.PrintNum(99); myObj.PrintNum(-5); } </code>
    static void Main(string[] args)
    {
        dynamic myObj = new DynamicProxy(new ExampleClass());
        myObj.PrintNum(99);
        myObj.PrintNum(-5);
    }

In conclusion, you see that you can get something like that to work in Java, but it’s not entirely convenient, because

  • Proxy class can just be instantiated for interfaces, i.e. your class has to implement an interface
  • Allowed Range can only be declared on interface level
  • Later usage comes just with extra effort in the beginning (MyInvocationHandler, wrapping at every instantiation) which also slightly reduces understandability

The capabilities of DynamicObject class in C# remove the interface restriction, as you see in the C# implementation. Unfortunately, this dynamic behavior removes static type safety in in this case, so runtime checks are necessary to determine if a method call on the dynamic proxy is allowed.

If those restrictions are acceptable for you, then this can serve as a basis for further digging!

9

Ranges are a special case of invariants. From Wikipedia:

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

A range [a, b] can be declared as a variable x of type Integer with the invariants x >= a and x <= b.

Therefore Ada or Pascal subrange types aren’t strictly neccessary. They could be implemented with an integer type with invariants.

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

Special features for range-limited types are not needed in C++ and other languages with powerful type systems.

In C++, your goals can be met relatively simply with user-defined types. And in applications where range-limited types are desirable, they are hardly sufficient. For example, one would also want the compiler to verify that physical unit computations were written correctly, so that velocity / time produces an acceleration, and taking the square root of acceleration / time produces a velocity. Doing this conveniently requires the ability to define a system of types, without explicitly naming every type that could ever appear in a formula. This can be done in C++.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật