Язык программирования, который позволяет вам определять новые ограничения для простых типов


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: Да, Ada был первым языком (насколько я знаю), который встроил поддержку для таких «типов». Именованные ограниченные типы данных.
m0nhawk

4
Все зависимые от типов языки будут иметь эту возможность. Это реально присуще системе типов en.wikipedia.org/wiki/Dependent_type, хотя вы можете создать пользовательский тип такого типа и в любом ML, в этих языках тип определяется как data Bool = True | Falseи для того, что вы хотите, можно сказать, data Cents = 0 | 1 | 2 | ...есть взгляд на «тип Алгебраических данных» (которые должны быть более правильно названными типами Хиндли-Милнера , но люди путают , что с типом выводом раздражающе) en.wikipedia.org/wiki/Algebraic_data_type
Хофф

2
Учитывая то, как языки, которые вы называете, обрабатывают целочисленное переполнение и переполнение, само по себе такое ограничение диапазона не будет иметь большого значения, если вы сохраните бесшумное переполнение / переполнение.

9
@StevenBurnap: типы не требуют ОО. В typeконце концов, в Паскале есть ключевое слово. Ориентация объекта - это скорее шаблон проектирования, чем свойство "atomar" языков программирования.
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 Compiler бесплатно. 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 Хотя я предполагаю, что в некоторых случаях компилятор может обнаруживать условия вне диапазона, проверка диапазона в основном должна выполняться во время выполнения. Компилятор не может знать, будет ли значение, которое вы загружаете с веб-сервера, находиться в диапазоне, или пользователь добавит слишком много записей в список, или что-то еще.
Калеб

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

Main-метод:

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 # -Version

Аннотация (в 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;
    }
}

The 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 #!
Макманнус

Просто к сведению: public virtual int Min { get; private set; }это хороший трюк, который значительно сократит ваш код
BlueRaja - Дэнни Пфлугхофт

2
Это полностью отличается от того, о чем Q, причина в том, что вы делаете, в основном динамика; что является противоположностью типизации, когда этот вопрос задает тип , с той разницей, что когда диапазон относится к типу, он применяется во время компиляции, а не во время выполнения. Никто не спрашивал о том, как проверять диапазоны во время выполнения, он хотел, чтобы он проверялся системой типов, которая проверяется во время компиляции.
Джимми Хоффа

1
@ JimmyHoffa ах, это имеет смысл. Хороший вопрос :)
Reactgular

2

Диапазоны являются частным случаем инвариантов. Из Википедии:

Инвариант является условием , что можно положиться , чтобы быть правдой во время выполнения программы.

Диапазон [a, b]может быть объявлен как переменная типа xInteger с инвариантами x> = a и x <= b .

Поэтому типы поддиапазонов Ada или Pascal не являются строго необходимыми. Они могут быть реализованы с целочисленным типом с инвариантами.


0

Странно, что эта функция не была добавлена ​​в языки.

Специальные функции для типов с ограниченным диапазоном не нужны в C ++ и других языках с мощными системами типов.

В C ++ ваши цели могут быть достигнуты относительно просто с помощью пользовательских типов . А в тех случаях, когда желательны типы с ограниченным диапазоном, их вряд ли достаточно . Например, можно также хотеть, чтобы компилятор проверил, что вычисления физических единиц были записаны правильно, так что скорость / время вызывает ускорение, а получение квадратного корня из ускорения / времени дает скорость. Для этого удобно иметь возможность определять систему типов, без явного присвоения имени каждому типу, который может появиться в формуле. Это можно сделать в C ++ .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.