ภาษาโปรแกรมที่อนุญาตให้คุณกำหนดขีด จำกัด ใหม่สำหรับประเภทอย่างง่าย


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/D Independent_typeแนบเนียนแม้ว่าคุณจะสามารถสร้างประเภทนี้เองใน ML ใด ๆ เช่นกันในภาษาเหล่านี้มีการกำหนดประเภทdata Bool = True | Falseและสิ่งที่คุณต้องการคุณสามารถบอกว่าdata Cents = 0 | 1 | 2 | ...มี ดูที่ "Algebraic Data Types" (ซึ่งควรจะมีชื่ออย่างถูกต้องมากกว่า hindley-milner แต่คนสับสนว่าการอนุมานแบบนั้นน่ารำคาญ) en.wikipedia.org/wiki/Algebraic_data_type
Jimmy Hoffa

2
เมื่อพิจารณาว่าภาษาที่คุณใช้จัดการกับจำนวนเต็มมากเกินไปและอันเดอร์โฟลว์การ จำกัด ช่วงดังกล่าวด้วยตัวของมันเองนั้นจะไม่คุ้มค่ามากนักหากคุณคงเงียบเกิน / ต่ำลง

9
@StevenBurnap: ประเภทไม่จำเป็นต้องมี OO มีtypeคำหลักใน Pascal หลังจากทั้งหมด การวางแนววัตถุเป็นรูปแบบการออกแบบมากกว่าคุณสมบัติ "atomar" ของภาษาโปรแกรม
wirrbel

คำตอบ:


26

ปาสคาลมีประเภทของช่วงย่อยเช่นการลดจำนวนของตัวเลขที่พอดีกับตัวแปร

  TYPE name = val_min .. val_max;

Ada ยังมีความคิดเกี่ยวกับช่วง: http://en.wikibooks.org/wiki/Ada_Programming/Types/range

จาก 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); 

ยังสามารถทำ

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

และนี่คือที่ที่มันเย็น

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

C ไม่มีประเภทย่อยที่เข้มงวด แต่มีวิธีการเลียนแบบหนึ่ง (อย่างน้อยก็ จำกัด ) โดยใช้ bitfields เพื่อลดจำนวนบิตที่ใช้ struct {int a : 10;} my_subrange_var;}. สิ่งนี้สามารถทำงานเป็นขอบเขตบนสำหรับเนื้อหาตัวแปร (โดยทั่วไปฉันจะบอกว่า: อย่าใช้ bitfields สำหรับสิ่งนี้เป็นเพียงเพื่อพิสูจน์จุด)

โซลูชันจำนวนมากสำหรับประเภทจำนวนเต็มความยาวโดยพลการในภาษาอื่นค่อนข้างเกิดขึ้นในระดับไลบรารี Ie 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
หากคุณต้องการเพิ่มรายละเอียดเกี่ยวกับประเภทที่ขึ้นต่อกันเช่นกันจะดีปัญหานี้มีวัตถุประสงค์ทั้งหมดและเหตุผลของการพิมพ์ที่ขึ้นอยู่กับว่ามันควรจะกล่าวถึงอย่างน้อย (แม้ว่าจะลึกลับ)
จิมมี่ฮอฟฟา

ในขณะที่ฉันมีความเข้าใจเกี่ยวกับประเภทพึ่งพาและการอนุมานเหตุผลอุปนัย / การอนุมานประเภท milner ฉันมีการฝึกฝนเล็กน้อยกับสิ่งเหล่านี้ หากคุณต้องการเพิ่มข้อมูลในคำตอบของฉันอย่าลังเลที่จะแก้ไข ฉันจะเพิ่มบางอย่างเกี่ยวกับ Axano ของ Peano และประเภทตัวเลขในคณิตศาสตร์โดยการนิยามแบบอุปนัย แต่ตัวอย่างข้อมูล ML ที่ดีอาจคุ้มค่ากว่า
wirrbel

คุณสามารถแยกประเภทของช่วงใน C โดยใช้ enum
John Cartwright

1
enum เป็น afaik ประเภท int หรือ unsigned int (ฉันคิดว่ามันเป็นคอมไพเลอร์โดยเฉพาะ) และไม่ได้ตรวจสอบที่ถูกผูกไว้
wirrbel

มันเย็นกว่านั้น: ประเภทที่เรียงลำดับสามารถใช้ในการประกาศอาร์เรย์และสำหรับลูปfor y in Year_Type loop ... ขจัดปัญหาเช่นบัฟเฟอร์ล้น
Brian Drummond

8

Ada ยังเป็นภาษาที่อนุญาตให้ จำกัด ประเภทแบบง่าย ๆ อันที่จริง Ada เป็นวิธีปฏิบัติที่ดีในการกำหนดประเภทของคุณเองสำหรับโปรแกรมของคุณเพื่อรับประกันความถูกต้อง

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

myVar1 : MyType1;

มันถูกใช้เป็นเวลานานโดยกระทรวงอาจจะยังคงเป็น แต่ฉันได้สูญเสียการติดตามของการใช้งานในปัจจุบัน


2
Ada ยังคงใช้กันอย่างแพร่หลายในระบบที่สำคัญด้านความปลอดภัย มีการอัปเดตล่าสุดของภาษาทำให้ภาษาเป็นหนึ่งในภาษาที่ดีที่สุดในปัจจุบันสำหรับการเขียนซอฟต์แวร์ที่เชื่อถือได้และบำรุงรักษาได้ น่าเสียดายที่การสนับสนุนเครื่องมือ (คอมไพเลอร์กรอบการทดสอบ IDE และอื่น ๆ ) มีราคาแพงและล้าหลังทำให้ยากและไม่เกิดผลกับการทำงาน
mattnz

อัปยศฉันจำได้ว่าใช้มันเป็นครั้งแรกและรู้สึกประหลาดใจกับความชัดเจนและข้อผิดพลาดในการสร้างโค้ด ดีใจที่ได้ยินว่ามันยังมีการปรับปรุงอย่างแข็งขันยังคงเป็นภาษาที่ยอดเยี่ยม
greedybuddha

@mattnz: GNAT เป็นส่วนหนึ่งของชุด gcc และมีอยู่ในทั้งรุ่นฟรีและจ่าย
Keith Thompson

@keith: GNAT Compiler ฟรี IDEs และเฟรมเวิร์กยังมีราคาแพงและขาดฟังก์ชั่น
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 ในขณะที่ฉันคิดว่ามีบางกรณีที่คอมไพเลอร์สามารถตรวจจับออกจากช่วงของเงื่อนไขการตรวจสอบช่วงที่ส่วนใหญ่ต้องเกิดขึ้นในเวลาทำงาน คอมไพเลอร์อาจไม่ทราบว่าค่าที่คุณดาวน์โหลดจากเว็บเซิร์ฟเวอร์จะอยู่ในช่วงหรือถ้าผู้ใช้จะเพิ่มหนึ่งระเบียนมากเกินไปในรายการหรืออะไรก็ตาม
Caleb

7

ความตั้งใจของคุณคือความรู้ของฉันใน Java และ C # ผ่านการผสมผสานของ Annotations และ Dynamic Proxy Pattern (มีการปรับใช้ในตัวสำหรับพร็อกซีแบบไดนามิกใน 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 Sub-Class:

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

TheClassClass:

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 #!
McMannus

เพียงแค่ FYI: public virtual int Min { get; private set; }เป็นเคล็ดลับที่ดีที่จะทำให้รหัสของคุณสั้นลงอย่างมาก
BlueRaja - Danny Pflughoeft

2
นี่คือสิ่งที่แตกต่างไปอย่างสิ้นเชิงจากสิ่งที่ถามเกี่ยวกับเหตุผลในการทำสิ่งที่คุณทำคือการเปลี่ยนแปลง; ซึ่งเป็นสิ่งที่ตรงกันข้ามในการพิมพ์โดยที่คำถามนี้ถามหาประเภทความแตกต่างที่เกิดขึ้นเมื่อช่วงอยู่ในประเภทมันถูกบังคับใช้ในเวลาคอมไพล์ไม่ใช่เวลาทำงาน ไม่มีใครถามถึงวิธีการตรวจสอบความถูกต้องของช่วงเวลารันไทม์เขาต้องการให้มันตรวจสอบความถูกต้องโดยระบบประเภทที่ตรวจสอบในเวลารวบรวม
Jimmy Hoffa

1
@ JimmyHoffa อาที่เหมาะสม จุดดี :)
Reactgular

2

ช่วงเป็นกรณีพิเศษของค่าคงที่ จาก Wikipedia:

คงเป็นเงื่อนไขที่สามารถอาศัยที่จะเป็นจริงในช่วงการทำงานของโปรแกรมได้

ช่วงที่[a, b]สามารถประกาศเป็นตัวแปรxประเภทIntegerที่มีค่าคงที่x> = aและx <= b

ดังนั้นประเภทย่อย Ada หรือ Pascal จึงไม่จำเป็นอย่างยิ่ง พวกเขาสามารถนำมาใช้กับประเภทจำนวนเต็มกับค่าคงที่


0

แปลกที่ฟีเจอร์นี้ไม่ได้ถูกเพิ่มลงในภาษา

คุณสมบัติพิเศษสำหรับประเภท จำกัด ช่วงไม่จำเป็นต้องใช้ใน C ++ และภาษาอื่น ๆ ที่มีระบบประเภทที่มีประสิทธิภาพ

ใน C ++, เป้าหมายของคุณสามารถพบได้ค่อนข้างเพียงกับประเภทที่ผู้ใช้กำหนด และในการใช้งานที่หลากหลายประเภท จำกัด เป็นที่พึงประสงค์ที่พวกเขาแทบจะไม่เพียงพอ ตัวอย่างเช่นเราต้องการให้คอมไพเลอร์ตรวจสอบว่าการคำนวณหน่วยทางกายภาพถูกเขียนอย่างถูกต้องดังนั้นความเร็ว / เวลาก่อให้เกิดความเร่งและการหารากที่สองของความเร่ง / เวลาทำให้เกิดความเร็ว การทำสิ่งนี้สะดวกต้องใช้ความสามารถในการกำหนดระบบประเภทโดยไม่ต้องตั้งชื่อทุกประเภทที่อาจปรากฏในสูตรได้อย่างชัดเจน ซึ่งสามารถทำได้ใน C ++

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.