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

19

Многие языки нравится C++, C#и Javaпозволяют создавать объекты , которые представляют собой простые типы , такие как integerили float. Используя интерфейс класса, вы можете переопределить операторы и выполнить логику, например, проверку, превышает ли значение бизнес-правило 100.

Мне интересно, возможно ли в некоторых языках определить эти правила как аннотации или атрибуты переменной / свойства.

Например, C#вы можете написать:

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

Или, может быть, C++вы могли бы написать:

int(0,100) x = 0;

Я никогда не видел подобного, но учитывая, насколько мы зависим от проверки данных перед хранением. Странно, что эта функция не была добавлена ​​в языки.

Можете ли вы привести пример языков, где это возможно?

Reactgular
источник
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/ ).

wirrbel
источник
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, может быть, до сих пор, но я потерял его текущее использование.

greedybuddha
источник
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 #. К сожалению, это динамическое поведение устраняет статическую безопасность типов в этом случае, поэтому проверки во время выполнения необходимы, чтобы определить, разрешен ли вызов метода на динамическом прокси.

Если эти ограничения приемлемы для вас, это может послужить основой для дальнейших копаний!

McMannus
источник
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 не являются строго необходимыми. Они могут быть реализованы с целочисленным типом с инвариантами.

nalply
источник
0

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

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

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

Кевин Клайн
источник