Bahasa pemrograman yang memungkinkan Anda menentukan batas baru untuk tipe sederhana

19

Banyak bahasa menyukai C++,, C#dan Javamemungkinkan Anda membuat objek yang mewakili tipe sederhana seperti integeratau float. Menggunakan antarmuka kelas, Anda dapat menimpa operator dan melakukan logika seperti memeriksa jika nilai melebihi aturan bisnis 100.

Saya bertanya-tanya apakah mungkin dalam beberapa bahasa untuk mendefinisikan aturan ini sebagai penjelasan atau atribut dari variabel / properti.

Misalnya, C#Anda dapat menulis:

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

Atau mungkin di dalam C++kamu bisa menulis:

int(0,100) x = 0;

Saya belum pernah melihat hal seperti ini dilakukan, tetapi mengingat seberapa tergantung kita pada validasi data sebelum penyimpanan. Aneh bahwa fitur ini belum ditambahkan ke bahasa.

Bisakah Anda memberi contoh bahasa di mana ini mungkin?

Reactgular
sumber
14
Bukankah Ada sesuatu seperti ini?
zxcdw
2
@zxcdw: Ya, Ada adalah bahasa pertama (seperti yang saya tahu) yang telah mendukung "jenis" tersebut. Disebut tipe data terbatas.
m0nhawk
4
Semua bahasa yang diketik secara dependen akan memiliki kemampuan ini. Ini intrinsik untuk sistem tipe en.wikipedia.org/wiki/Dependent_type secara realistis meskipun Anda dapat membuat jenis kustom dari sifat ini dalam ML apa pun juga, dalam bahasa-bahasa ini suatu tipe didefinisikan sebagai data Bool = True | Falsedan untuk apa yang Anda inginkan, Anda dapat mengatakan data Cents = 0 | 1 | 2 | ...memiliki lihat "Tipe Data Aljabar" (yang seharusnya lebih tepat disebut tipe hindley-milner tetapi orang-orang bingung dengan inferensi tipe yang mengganggu) en.wikipedia.org/wiki/Algebraic_data_type
Jimmy Hoffa
2
Mengingat bagaimana bahasa yang Anda beri nama menangani bilangan bulat over- dan underflow, batasan rentang seperti itu sendiri tidak akan bernilai banyak jika Anda tetap menggunakan overflow / underflow.
9
@StevenBurnap: Jenis tidak memerlukan OO. Lagipula ada typekata kunci dalam Pascal. Orientasi objek lebih merupakan pola desain daripada properti "atomar" dari bahasa pemrograman.
wirrbel

Jawaban:

26

Pascal memiliki tipe subrange, yaitu mengurangi jumlah angka yang masuk ke dalam variabel.

  TYPE name = val_min .. val_max;

Ada juga memiliki gagasan rentang: http://en.wikibooks.org/wiki/Ada_Programming/Types/range

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

bisa juga dilakukan

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

Dan di sinilah ia menjadi dingin

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

C tidak memiliki tipe subrange yang ketat, tetapi ada cara untuk meniru satu (setidaknya terbatas) dengan menggunakan bitfield untuk meminimalkan jumlah bit yang digunakan. struct {int a : 10;} my_subrange_var;}. Ini dapat berfungsi sebagai batas atas untuk konten variabel (secara umum saya akan mengatakan: jangan gunakan bitfield untuk ini , ini hanya untuk membuktikan suatu hal).

Banyak solusi untuk tipe integer panjang sewenang-wenang dalam bahasa lain lebih tepatnya terjadi pada tingkat pustaka, yaitu C ++ memungkinkan untuk solusi berbasis template.

Ada bahasa yang memungkinkan untuk memantau keadaan variabel dan menghubungkan pernyataan untuk itu. Misalnya dalam Clojurescript

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

(def A (atom 0 :validator mytest))

Fungsi mytestdipanggil ketika atelah berubah (via reset!atau swap!) memeriksa apakah kondisi terpenuhi. Ini bisa menjadi contoh untuk menerapkan perilaku subrange dalam bahasa yang mengikat akhir (lihat http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/ ).

wirrbel
sumber
2
Jika Anda ingin menambahkan detail tentang tipe dependen juga akan menyenangkan, masalah ini adalah seluruh tujuan dan alasan untuk mengetik dependen, tampaknya setidaknya harus disebutkan (bahkan jika itu esoterik)
Jimmy Hoffa
Sementara saya memiliki beberapa pemahaman tentang tipe dependen dan inferensi penalaran / tipe milner. Saya memiliki sedikit latihan dengan itu. Jika Anda ingin menambahkan informasi ke jawaban saya, silakan mengeditnya. Saya akan menambahkan sesuatu tentang Aksioma Peano dan tipe angka dalam matematika dengan definisi induktif tetapi contoh data ML yang bagus mungkin lebih bermanfaat mungkin.
wirrbel
Anda dapat menggunakan jenis rentang dalam C menggunakan enum
John Cartwright
1
enum adalah afaik dari tipe int atau unsigned int (saya pikir itu adalah compiler khusus) dan tidak terikat-diperiksa.
wirrbel
Itu menjadi lebih dingin dari itu: tipe berkisar dapat digunakan dalam deklarasi array dan untuk loop for y in Year_Type loop ... menghilangkan masalah seperti buffer overflows.
Brian Drummond
8

Ada juga adalah bahasa yang memungkinkan batasan untuk tipe sederhana, sebenarnya di Ada itu praktik yang baik untuk menentukan tipe Anda sendiri untuk program Anda untuk menjamin kebenaran.

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

myVar1 : MyType1;

Itu digunakan untuk waktu yang lama oleh DoD, mungkin masih tapi saya sudah lupa penggunaannya saat ini.

greedybuddha
sumber
2
Ada masih banyak digunakan dalam sistem kritis keselamatan. Ada pembaruan terbaru untuk bahasa yang menjadikan bahasa itu salah satu yang terbaik saat ini untuk menulis perangkat lunak yang andal dan terpelihara. Sayangnya dukungan alat (kompiler, kerangka uji IDE, dll.) Mahal dan tertinggal sehingga sulit dan tidak produktif untuk bekerja dengannya.
mattnz
Sayang sekali, saya ingat menggunakannya untuk pertama kalinya dan kagum pada betapa bersih dan bebas bug itu membuat kode. Senang mendengarnya masih aktif diperbarui, masih bahasa yang bagus.
greedybuddha
@mattnz: GNAT adalah bagian dari paket gcc, dan ada dalam versi gratis dan berbayar.
Keith Thompson
@keith: Kompilator GNAT gratis. Kerangka dan IDE masih mahal dan kurang fungsional.
mattnz
7

Lihat Membatasi rentang tipe nilai dalam C ++ untuk contoh cara membuat tipe nilai rentang-diperiksa dalam C ++.

Ringkasan eksekutif: Gunakan templat untuk membuat tipe nilai yang memiliki nilai minimum dan maksimum bawaan, yang dapat Anda gunakan seperti ini:

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

Anda bahkan tidak benar-benar membutuhkan templat di sini; Anda bisa menggunakan kelas dengan efek yang sama. Menggunakan templat memungkinkan Anda menentukan jenis yang mendasarinya. Juga, penting untuk dicatat bahwa tipe di percentatas tidak akan menjadi float, tetapi merupakan contoh dari template. Ini mungkin tidak memenuhi aspek 'tipe sederhana' dari pertanyaan Anda.

Aneh bahwa fitur ini belum ditambahkan ke bahasa.

Tipe sederhana hanya itu - sederhana. Mereka sering digunakan sebagai blok bangunan untuk membuat alat yang Anda butuhkan daripada digunakan secara langsung.

Caleb
sumber
2
@JimmyHoffa Sementara saya kira ada beberapa kasus di mana kompiler dapat mendeteksi kondisi di luar jangkauan, pemeriksaan kisaran sebagian besar perlu terjadi pada saat run time. Kompiler tidak dapat mengetahui apakah nilai yang Anda unduh dari server web akan berada dalam kisaran, atau apakah pengguna akan menambahkan satu terlalu banyak rekaman ke daftar, atau apa pun.
Caleb
7

Beberapa bentuk terbatas dari niat Anda adalah setahu saya mungkin di Jawa dan C # melalui kombinasi Anotasi dan Pola Proksi Dinamis (terdapat implementasi bawaan untuk proxy dinamis di Jawa dan C #).

Versi Java

Anotasi:

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

Kelas Wrapper membuat instance Proxy:

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

InvocationHandler berfungsi sebagai bypass pada setiap panggilan metode:

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

Contoh-Antarmuka untuk Penggunaan:

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

Metode Utama:

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

Keluaran:

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

Anotasi (dalam C # disebut atribut):

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

Sub-Kelas 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());
    }
}

Pemakaian:

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

Kesimpulannya, Anda melihat bahwa Anda bisa mendapatkan sesuatu seperti itu untuk bekerja di Jawa , tetapi itu tidak sepenuhnya nyaman, karena

  • Kelas proxy hanya dapat digunakan untuk antarmuka, yaitu kelas Anda harus mengimplementasikan antarmuka
  • Range yang Diizinkan hanya dapat dideklarasikan pada level antarmuka
  • Penggunaan kemudian datang hanya dengan upaya ekstra di awal (MyInvocationHandler, membungkus di setiap instansi) yang juga sedikit mengurangi pemahaman

Kemampuan kelas DynamicObject di C # menghapus batasan antarmuka, seperti yang Anda lihat dalam implementasi C #. Sayangnya, perilaku dinamis ini menghapus keamanan tipe statis dalam kasus ini, jadi pemeriksaan runtime diperlukan untuk menentukan apakah metode panggilan pada proxy dinamis diizinkan.

Jika batasan tersebut dapat diterima untuk Anda, maka ini dapat berfungsi sebagai dasar untuk penggalian lebih lanjut!

McMannus
sumber
1
terima kasih, ini adalah jawaban yang luar biasa. Apakah hal seperti ini dimungkinkan dalam C #?
Reactgular
1
Baru saja menambahkan implementasi sampel C #!
McMannus
Just FYI: public virtual int Min { get; private set; }adalah trik bagus yang akan mempersingkat kode Anda secara signifikan
BlueRaja - Danny Pflughoeft
2
Ini sama sekali berbeda dari apa yang dimaksud dengan Q, alasan mengapa apa yang Anda lakukan pada dasarnya adalah dinamika; yang merupakan antitesis dari pengetikan di mana pertanyaan ini menanyakan tipe , perbedaannya adalah ketika rentang berada pada tipe, itu diberlakukan pada waktu kompilasi bukan run-time. Tidak ada yang bertanya tentang cara memvalidasi rentang pada waktu berjalan, ia ingin itu divalidasi oleh sistem tipe yang diperiksa pada waktu kompilasi.
Jimmy Hoffa
1
@ JimmyHoffa ah itu masuk akal. Poin bagus :)
Reactgular
2

Kisaran adalah kasus khusus dari invarian. Dari Wikipedia:

Sebuah invarian adalah suatu kondisi yang bisa diandalkan untuk menjadi kenyataan selama pelaksanaan program.

Rentang [a, b]dapat dideklarasikan sebagai variabel x bertipe Integerdengan invarian x> = a dan x <= b .

Oleh karena itu, jenis subrange Ada atau Pascal tidak sepenuhnya diperlukan. Mereka dapat diimplementasikan dengan tipe integer dengan invarian.

nekat
sumber
0

Aneh bahwa fitur ini belum ditambahkan ke bahasa.

Fitur khusus untuk tipe rentang terbatas tidak diperlukan dalam C ++ dan bahasa lain dengan sistem tipe kuat.

Di C ++, sasaran Anda dapat dipenuhi relatif hanya dengan tipe yang ditentukan pengguna . Dan dalam aplikasi di mana berbagai jenis terbatas diinginkan, mereka hampir tidak cukup . Sebagai contoh, seseorang juga ingin kompiler memverifikasi bahwa perhitungan unit fisik ditulis dengan benar, sehingga kecepatan / waktu menghasilkan akselerasi, dan mengambil akar kuadrat dari akselerasi / waktu menghasilkan kecepatan. Melakukan hal ini dengan mudah membutuhkan kemampuan untuk mendefinisikan sistem tipe, tanpa secara eksplisit menyebutkan setiap tipe yang bisa muncul dalam formula. Ini bisa dilakukan di C ++ .

kevin cline
sumber