Мова програмування, що дозволяє визначати нові межі для простих типів


19

Багато мов, як C++, C#і Javaдозволяють створювати об'єкти, що представляють прості типи, як integerабо float. Використовуючи інтерфейс класу, ви можете переосмислити операторів і виконувати таку логіку, як перевірка, чи значення перевищує ділове правило 100.

Мені цікаво, чи можна в деяких мовах визначати ці правила як анотації чи атрибути змінної / властивості.

Наприклад, у C#ви можете написати:

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

А може у C++вас можна було написати:

int(0,100) x = 0;

Я ніколи не бачив такого подібного, але враховуючи, наскільки ми залежні від перевірки даних перед зберіганням. Дивно, що ця функція не додана до мов.

Чи можете ви навести приклад мов, де це можливо?


14
Хіба Ада не щось подібне?
zxcdw

2
@zxcdw: Так, Ада була першою мовою (як я знаю), яка створила підтримку таких "типів". Названі обмежені типи даних.
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
Джиммі Хоффа

2
Зважаючи на те, як мови, якими ви називаєте, обробляють цілі чисельні переповнення та перетікання, таке обмеження діапазону самостійно мало би мало вартувало, якщо ви продовжуєте мовчати переповнення та переповнення.

9
@StevenBurnap: типи не потребують ОО. typeЗрештою, у Pascal є ключове слово. Об'єктна орієнтація є скоріше дизайнерською схемою, ніж властивістю "атомар" мов програмування.
wirrbel

Відповіді:


26

Паскаль мав піддиапазонні типи, тобто зменшував кількість чисел, що вписуються в змінну.

  TYPE name = val_min .. val_max;

Ада також має поняття діапазонів: 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
Якщо ви додасте також детальну інформацію про залежні типи, було б непогано, ця проблема полягає в цілі цілі та причини залежного набору тексту, здається, її слід принаймні згадати (навіть якщо це езотерика)
Джиммі Хоффа

Хоча я розумію залежні типи та спонукальні міркування / умовиводи типу мілнера. Я мало практикуюсь із ними. Якщо ви хочете додати інформацію до моєї відповіді, сміливо її відредагуйте. Я збирався додати щось про аксіоми піано та типи чисел у математиці за індуктивним визначенням, але приємний приклад даних ML може бути кориснішим, можливо.
wirrbel

ви можете вилучити тип діапазону в C, використовуючи enum
Джон Картрайт

1
enum - це afaik типу int або unsigned int (я думаю, це специфічний для компілятора) і не обмежений.
wirrbel

Він стає більш крутим, ніж це: типи діапазону можуть використовуватися в оголошеннях масиву та для циклів, for y in Year_Type loop ... усуваючи проблеми, такі як переповнення буфера.
Брайан Драммонд

8

Ada також є мовою, яка дозволяє обмежити для простих типів, адже в Ada це найкраща практика визначити власні типи для вашої програми, щоб гарантувати правильність.

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

myVar1 : MyType1;

Він довго використовувався DoD, можливо, і досі є, але я втратила інформацію про його поточне використання.


2
Ада все ще широко використовується в критичних для безпеки системах. Існує нещодавнє оновлення мови, що робить мову однією з найкращих доступних сьогодні для написання надійного та ремонтованого програмного забезпечення. На жаль, підтримка інструментів (компілятори, тестові рамки IDE тощо) є дорогою і відстає, що робить її важкою і малопродуктивною.
mattnz

Сором, я пам’ятаю, що вперше користувався ним і був вражений тим, як чітко та без помилок зробив код. Радий почути, що вона все ще активно оновлюється, все ще чудова мова.
greedybuddha

@mattnz: GNAT є частиною набору gcc і існує у безкоштовній та платній версіях.
Кіт Томпсон

@keith: Компілятор GNAT безкоштовний. ІДЕ та рамки все ще дорогі і не мають функціональних можливостей.
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 Хоча я думаю, що є деякі випадки, коли компілятор може виявляти поза умовами діапазону, перевірка діапазону в основному повинна відбуватися під час виконання. Компілятор, можливо, не може знати, чи буде значення, яке ви завантажуєте з веб-сервера, в діапазоні, чи користувач додасть до списку одну занадто багато записів, або будь-яку іншу.
Калеб

7

Якоюсь обмеженою формою вашого наміру є, наскільки мені відомо, на Java та C # за допомогою комбінації анотацій та динамічного проксі-шаблону (існують вбудовані реалізації для динамічних проксі-серверів у Java та C #).

Версія Java

Анотація:

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

Клас Wrapper, що створює екземпляр Proxy:

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 #?
Реакційний

1
Щойно додано зразок реалізації C #!
Макманус

Just FYI: public virtual int Min { get; private set; }це приємний трюк, який значно скоротить ваш код
BlueRaja - Danny Pflughoeft

2
Це абсолютно відрізняється від того, що стосується Q, тому що ви робите, це в основному динаміка; що є антитезою введення тексту, коли це питання запитує тип , різниця полягає в тому, що діапазон знаходиться на типі, він застосовується під час компіляції, а не під час виконання. Ніхто не питав про те, як перевірити діапазони під час виконання, він хотів, щоб це було підтверджено системою типів, яка перевіряється під час компіляції.
Джиммі Хоффа

1
@JimmyHoffa ах, це має сенс. Хороший момент :)
Реакційний

2

Діапазони - особливий випадок інваріантів. З Вікіпедії:

Інваріант є умовою , що можна покластися , щоб бути правдою під час виконання програми.

Діапазон [a, b]може бути оголошений змінною х типу Integerз інваріантами x> = a і x <= b .

Тому типи піддіапазонів Ada або Pascal не є строго необхідними. Вони можуть бути реалізовані з цілим типом з інваріантами.


0

Дивно, що ця функція не додана до мов.

Спеціальні функції для типів з обмеженим діапазоном не потрібні для C ++ та інших мов із потужними системами типу.

У C ++ ваші цілі можна досягти порівняно просто за допомогою визначених користувачем типів . А в додатках, де бажані типи обмеженого діапазону, їх навряд чи достатньо . Наприклад, хочеться також, щоб компілятор переконався, що обчислення фізичних одиниць написані правильно, так що швидкість / час виробляє прискорення, а прийняття квадратного кореня прискорення / часу створює швидкість. Для цього зручно потрібно вміти визначати систему типів, не чітко називаючи кожен тип, який коли-небудь міг з’явитися у формулі. Це можна зробити на C ++ .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.