Một ngôn ngữ lập trình cho phép bạn xác định các giới hạn mới cho các loại đơn giản


19

Nhiều ngôn ngữ như C++, C#Javacho phép bạn tạo các đối tượng đại diện cho các loại đơn giản như integerhoặc float. Sử dụng giao diện lớp, bạn có thể ghi đè các toán tử và thực hiện logic như kiểm tra xem giá trị có vượt quá quy tắc kinh doanh không.

Tôi tự hỏi liệu trong một số ngôn ngữ có thể định nghĩa các quy tắc này là chú thích hoặc thuộc tính của một biến / thuộc tính.

Ví dụ: trong C#bạn có thể viết:

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

Hoặc có thể trong C++bạn có thể viết:

int(0,100) x = 0;

Tôi chưa bao giờ thấy một cái gì đó như thế này được thực hiện, nhưng đưa ra mức độ phụ thuộc của chúng tôi vào việc xác thực dữ liệu trước khi lưu trữ. Thật kỳ lạ khi tính năng này chưa được thêm vào ngôn ngữ.

Bạn có thể cho ví dụ về các ngôn ngữ mà điều này là có thể?


14
Không phải Ada là cái gì đó như thế này sao?
zxcdw

2
@zxcdw: Vâng, Ada là ngôn ngữ đầu tiên (như tôi biết) được xây dựng để hỗ trợ cho các "loại" như vậy. Các loại dữ liệu bị ràng buộc.
m0nhawk

4
Tất cả các ngôn ngữ gõ phụ thuộc sẽ có khả năng này. Đó là nội tại để loại hệ thống en.wikipedia.org/wiki/Dependent_type thực tế mặc dù bạn có thể tạo một kiểu tùy chỉnh như thế này trong bất kỳ ML là tốt, trong các ngôn ngữ một kiểu được định nghĩa là data Bool = True | Falsevà cho những gì bạn muốn, bạn có thể nói data Cents = 0 | 1 | 2 | ...có một nhìn vào "Các kiểu dữ liệu đại số" (nên được đặt tên chính xác hơn là các kiểu hindley-milner nhưng mọi người nhầm lẫn rằng với kiểu suy luận một cách khó chịu) en.wikipedia.org/wiki/Algebraic_data_type
Jimmy Hoffa

2
Dựa vào cách các ngôn ngữ mà bạn đặt tên xử lý số nguyên và tràn, số lượng giới hạn phạm vi như vậy sẽ không có giá trị nếu bạn giữ im lặng quá mức.

9
@StevenBurnap: Các loại không yêu cầu OO. Có một typetừ khóa trong Pascal. Hướng đối tượng là một mẫu thiết kế hơn là thuộc tính "nguyên tử" của các ngôn ngữ lập trình.
wirrbel

Câu trả lời:


26

Pascal có các kiểu con, tức là giảm số lượng số phù hợp với một biến.

  TYPE name = val_min .. val_max;

Ada cũng có một khái niệm về phạm vi: http://en.wikibooks.org/wiki/Ada_Programming/Types/range

Từ Wikipedia ....

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

cũng có thể làm

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

Và đây là nơi nó trở nên mát mẻ

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

C không có loại phân loại nghiêm ngặt, nhưng có nhiều cách để bắt chước một loại (ít nhất là hạn chế) bằng cách sử dụng bitfield để giảm thiểu số lượng bit được sử dụng. struct {int a : 10;} my_subrange_var;}. Điều này có thể hoạt động như một giới hạn trên cho nội dung thay đổi (nói chung tôi sẽ nói: không sử dụng bitfield cho việc này , đây chỉ là để chứng minh một điểm).

Rất nhiều giải pháp cho các kiểu số nguyên có độ dài tùy ý trong các ngôn ngữ khác thay vì xảy ra ở cấp thư viện, Ie C ++ cho phép các giải pháp dựa trên mẫu.

Có những ngôn ngữ cho phép theo dõi các trạng thái biến đổi và kết nối các xác nhận với nó. Ví dụ trong Clojurescript

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

(def A (atom 0 :validator mytest))

Hàm mytestđược gọi khi ađã thay đổi (thông qua reset!hoặc swap!) kiểm tra xem các điều kiện có được đáp ứng hay không. Đây có thể là một ví dụ để thực hiện hành vi phụ trong các ngôn ngữ ràng buộc muộn (xem http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/ ).


2
Nếu bạn thêm một chi tiết về các loại phụ thuộc cũng sẽ tốt, thì vấn đề này là toàn bộ mục đích và lý do để gõ phụ thuộc, có vẻ như ít nhất nó nên được đề cập (ngay cả khi nó là bí truyền)
Jimmy Hoffa

Trong khi tôi có một số hiểu biết về các loại phụ thuộc và suy luận quy nạp / suy luận kiểu milner. Tôi có ít thực hành với những người đó. Nếu bạn muốn thêm thông tin vào câu trả lời của tôi, vui lòng chỉnh sửa nó. Tôi sẽ thêm một cái gì đó về Định nghĩa Peano và các loại số trong toán học theo định nghĩa quy nạp nhưng một ví dụ dữ liệu ML đẹp có thể đáng giá hơn có thể.
wirrbel

bạn có thể loại bỏ một loại phạm vi trong C bằng enum
John Cartwright

1
enum là afaik của kiểu int hoặc unsign int (tôi nghĩ nó là trình biên dịch cụ thể) và không bị kiểm tra ràng buộc.
wirrbel

Nó trở nên mát hơn thế: các kiểu trong phạm vi có thể được sử dụng trong khai báo mảng và cho các vòng lặp for y in Year_Type loop ... loại bỏ các vấn đề như tràn bộ đệm.
Brian Drumond

8

Ada cũng là một ngôn ngữ cho phép giới hạn cho các loại đơn giản, trên thực tế trong Ada, đó là cách thực hành tốt để xác định loại của riêng bạn cho chương trình của bạn để đảm bảo tính chính xác.

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

myVar1 : MyType1;

Nó đã được sử dụng trong một thời gian dài bởi DoD, có thể vẫn vậy nhưng tôi đã mất dấu vết về việc sử dụng hiện tại.


2
Ada vẫn được sử dụng rộng rãi trong các hệ thống quan trọng an toàn. Có một bản cập nhật gần đây cho ngôn ngữ làm cho ngôn ngữ trở thành một trong những ngôn ngữ tốt nhất hiện nay để viết phần mềm đáng tin cậy và có thể bảo trì. Thật không may, công cụ hỗ trợ (trình biên dịch, khung kiểm tra IDE, v.v.) rất tốn kém và bị tụt lại phía sau khiến cho việc này trở nên khó khăn và không hiệu quả.
mattnz

Thật xấu hổ, tôi nhớ lần đầu tiên sử dụng nó và đã rất ngạc nhiên khi thấy nó rõ ràng và không có lỗi. Vui mừng khi biết nó vẫn được cập nhật tích cực, vẫn là một ngôn ngữ tuyệt vời.
greedybuddha

@mattnz: GNAT là một phần của bộ gcc và tồn tại ở cả phiên bản miễn phí và trả phí.
Keith Thompson

@keith: Trình biên dịch GNAT là miễn phí. IDE và khung công tác vẫn còn đắt tiền và thiếu chức năng.
mattnz

7

Xem Giới hạn phạm vi của các loại giá trị trong C ++ để biết ví dụ về cách tạo loại giá trị được kiểm tra phạm vi trong C ++.

Tóm tắt điều hành: Sử dụng một mẫu để tạo một loại giá trị có các giá trị tối thiểu và tối đa tích hợp, mà bạn có thể sử dụng như thế này:

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

Bạn thậm chí không thực sự cần một mẫu ở đây; bạn có thể sử dụng một lớp để có hiệu lực tương tự. Sử dụng một mẫu cho phép bạn chỉ định loại cơ bản. Ngoài ra, điều quan trọng cần lưu ý là loại percentở trên sẽ không phải là một float, mà là một ví dụ của mẫu. Điều này có thể không thỏa mãn khía cạnh 'loại đơn giản' trong câu hỏi của bạn.

Thật kỳ lạ khi tính năng này chưa được thêm vào ngôn ngữ.

Các loại đơn giản chỉ có vậy - đơn giản. Chúng thường được sử dụng tốt nhất làm các khối xây dựng để tạo các công cụ bạn cần thay vì được sử dụng trực tiếp.


2
@JimmyHoffa Trong khi tôi cho rằng có một số trường hợp trình biên dịch có thể phát hiện ra các điều kiện phạm vi, thì việc kiểm tra phạm vi hầu hết cần phải xảy ra trong thời gian chạy. Trình biên dịch không thể biết liệu giá trị mà bạn tải xuống từ máy chủ web sẽ nằm trong phạm vi hay nếu người dùng sẽ thêm quá nhiều bản ghi vào danh sách, hoặc bất cứ điều gì.
Caleb

7

Một số dạng hạn chế của ý định của bạn là kiến ​​thức của tôi có thể có trong Java và C # thông qua sự kết hợp giữa Chú thích và Mẫu proxy động (tồn tại các triển khai tích hợp cho các proxy động trong Java và C #).

Phiên bản Java

Chú thích:

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

Lớp Wrapper tạo cá thể Proxy:

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

InvocationHandler phục vụ như bỏ qua tại mỗi cuộc gọi phương thức:

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

Giao diện ví dụ để sử dụng:

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

Phương pháp chính:

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

Đầu ra:

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

Chú thích (trong thuộc tính được gọi là 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; }
}

Lớp con 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;
    }
}

Ví dụ:

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

Sử dụng:

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

Tóm lại, bạn thấy rằng bạn có thể có một cái gì đó tương tự để hoạt động trong Java , nhưng nó không hoàn toàn thuận tiện, bởi vì

  • Lớp proxy chỉ có thể được khởi tạo cho các giao diện, tức là lớp của bạn phải thực hiện một giao diện
  • Phạm vi cho phép chỉ có thể được khai báo ở cấp độ giao diện
  • Việc sử dụng sau này chỉ cần nỗ lực thêm vào lúc đầu (MyInvocationHandler, gói mỗi lần khởi động) cũng làm giảm một chút sự hiểu biết

Các khả năng của lớp DynamicObject trong C # loại bỏ hạn chế giao diện, như bạn thấy trong triển khai C #. Thật không may, hành vi động này loại bỏ sự an toàn của loại tĩnh trong trường hợp này, vì vậy kiểm tra thời gian chạy là cần thiết để xác định xem một cuộc gọi phương thức trên proxy động có được phép hay không.

Nếu những hạn chế đó được chấp nhận cho bạn, thì điều này có thể làm cơ sở cho việc đào sâu hơn!


1
cảm ơn, đây là một câu trả lời tuyệt vời Là một cái gì đó như thế này có thể có trong C #?
Phản ứng

1
Chỉ cần thêm một triển khai C # mẫu!
McMannus

Chỉ cần FYI: public virtual int Min { get; private set; }là một mẹo hay giúp rút ngắn mã của bạn một cách đáng kể
BlueRaja - Danny Pflughoeft

2
Điều này hoàn toàn khác với những gì Q nói về, lý do là những gì bạn đang làm về cơ bản là sự năng động; đó là phản đề của việc gõ trong đó câu hỏi này yêu cầu một loại , sự khác biệt là khi phạm vi nằm trên một loại, nó được thi hành tại thời gian biên dịch không phải là thời gian chạy. Không ai hỏi về cách xác thực các phạm vi trong thời gian chạy, anh ta muốn nó xác nhận hợp lệ bởi hệ thống loại được kiểm tra tại thời gian biên dịch.
Jimmy Hoffa

1
@JimmyHoffa ah có ý nghĩa. Điểm tốt :)
Phản ứng

2

Phạm vi là một trường hợp đặc biệt của bất biến. Từ Wikipedia:

Một bất biến là một tình trạng có thể được dựa vào để thể đúng trong thực hiện một chương trình.

Một phạm vi [a, b]có thể được khai báo là một biến x loại Integervới các bất biến x> = ax <= b .

Do đó, các loại phụ của Ada hoặc Pascal không cần thiết. Chúng có thể được thực hiện với một kiểu số nguyên với bất biến.


0

Thật kỳ lạ khi tính năng này chưa được thêm vào ngôn ngữ.

Các tính năng đặc biệt cho các loại giới hạn phạm vi không cần thiết trong C ++ và các ngôn ngữ khác có hệ thống loại mạnh.

Trong C ++, mục tiêu của bạn có thể được đáp ứng tương đối đơn giản với các loại do người dùng xác định . Và trong các ứng dụng mà các loại giới hạn phạm vi là mong muốn, chúng hầu như không đủ . Ví dụ, người ta cũng muốn trình biên dịch xác minh rằng các phép tính đơn vị vật lý được viết chính xác, để vận tốc / thời gian tạo ra gia tốc và lấy căn bậc hai của gia tốc / thời gian tạo ra vận tốc. Làm điều này thuận tiện đòi hỏi khả năng xác định một hệ thống các loại, mà không đặt tên rõ ràng cho mọi loại có thể xuất hiện trong một công thức. Điều này có thể được thực hiện trong C ++ .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.