単純型の新しい制限を定義できるプログラミング言語


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 | ...Aを持っています「代数データ型」を見て(より正確にはヒンドリー-ミルナーの種類の名前が、人々が混同すべきことであるが、うるさく型推論と)en.wikipedia.org/wiki/Algebraic_data_type
ジミー・ホッファ

2
名前を付けた言語が整数のオーバーフローおよびアンダーフローを処理する方法を考えると、サイレントオーバーフロー/アンダーフローを維持する場合、このような範囲の制限はそれだけの価値はありません。

9
@StevenBurnap:型はオブジェクト指向を必要としません。type結局Pascalにはキーワードがあります。オブジェクト指向は、プログラミング言語の「アトマー」プロパティというよりもデザインパターンです。
ウィルベル

回答:


26

Pascalにはサブレンジタイプがありました。つまり、変数に収まる数の数を減らしていました。

  TYPE name = val_min .. val_max;

Adaには範囲の概念もあります: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には厳密なサブレンジタイプはありませんが、ビットフィールドを使用して使用ビット数を最小限に抑えることにより、1つを模倣する方法があります(少なくとも制限されています)。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は、条件が満たされているかどうかをareset!またはを介してswap!)変更したときに呼び出されます。これは、遅延バインディング言語でサブレンジ動作を実装するための例です(http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/を参照)。


2
あなたが依存型についての詳細を追加するだけでなくいいだろうと、この問題は、依存タイピングのための全体の目的及び理由である(それは難解であっても)、それは、少なくとも言及されるべきであると思われる
ジミー・ホッファ

私は依存型と帰納的推論/ミルナー型推論についてある程度理解していますが。私はそれらを少し練習しています。私の回答に情報を追加したい場合は、自由に編集してください。私は帰納的定義によって数学でペアノ公理と数型について何かを追加しようとしていましたが、素敵なMLデータの例は多分もっと価値があるかもしれません。
ウィルベル

あなたは列挙型使用してCで範囲タイプをその場しのぎ可能性
ジョン・カートライト

1
enumはint型またはunsigned int型(コンパイラ固有のものだと思います)のafaikであり、境界チェックされていません。
ウィルベル

それよりもクールになります。範囲宣言された型は、配列宣言やforループで使用できfor y in Year_Type loop ... 、バッファオーバーフローなどの問題を排除できます。
ブライアンドラモンド14年

8

Adaは単純な型の制限を許可する言語でもあります。実際、Adaでは、正確性を保証するためにプログラムに独自の型定義することをお勧めします。

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

myVar1 : MyType1;

DoDによって長い間使用されていましたが、おそらくまだ使用されていますが、現在の使用法を把握できていません。


2
Adaは、依然として安全性が重要なシステムで広く使用されています。信頼性と保守性の高いソフトウェアを作成するために、今日利用可能な言語の1つとなる言語への最近の更新があります。残念ながら、ツールのサポート(コンパイラ、IDEテストフレームワークなど)は高価であり、作業が難しく、生産的ではありません。
マッテンツ

残念なことに、私は初めてそれを使用したことを覚えており、コードがどのように明確でバグがなかったかに驚かされました。まだ積極的に更新されており、素晴らしい言語であることを聞いてうれしいです。
greedybuddha

@mattnz:GNATはgccスイートの一部であり、無料版と有料版の両方に存在します。
キーストンプソン

@keith:GNAT Compilerは無料です。IDEとフレームワークは依然として高価であり、機能がありません。
マッテンツ

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コンパイラが範囲外の状態を検出できる場合があると思いますが、範囲のチェックはほとんど実行時に行う必要があります。コンパイラは、Webサーバーからダウンロードした値が範囲内にあるかどうか、またはユーザーが1つのリストに追加するレコードが多すぎるかどうかなどを知ることはできません。
カレブ

7

あなたの意図のいくつかの制限された形式は、注釈と動的プロキシパターンの組み合わせを通じてJavaとC#で可能な私の知識です(JavaとC#の動的プロキシの組み込み実装が存在します)。

Javaバージョン

注釈:

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

プロキシインスタンスを作成するWrapperクラス:

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サブクラス:

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

使用法:

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

結論として、Javaで動作するようにそのようなものを取得できることがわかりますが、完全に便利ではありません。

  • プロキシクラスはインターフェイス用にインスタンス化できます。つまり、クラスはインターフェイスを実装する必要があります
  • 許可された範囲は、インターフェイスレベルでのみ宣言できます
  • 後で使用するには、最初に余分な労力(インスタンス化ごとにラップするMyInvocationHandler)が必要になります。

C#のDynamicObjectクラスの機能は、C#の実装で見られるように、インターフェイスの制限を取り除きます。残念ながら、この場合、この動的な動作により静的な型の安全性が失われるため、動的プロキシでのメソッド呼び出しが許可されているかどうかを判断するにはランタイムチェックが必要です。

これらの制限が受け入れられる場合、これはさらに掘り下げるための基礎として役立ちます!


1
おかげで、これは素晴らしい答えです。このようなことはC#で可能ですか?
Reactgular

1
サンプルC#実装を追加しました!
マクマンナス

public virtual int Min { get; private set; }
参考までに

2
これはQの目的とはまったく異なります。理由は、基本的にダイナミクスです。これは、この質問がtypeを求めているタイピングのアンチテーゼです。違いは、範囲がtype上にあるときであり、実行時ではなくコンパイル時に強制されます。実行時に範囲を検証する方法については誰も尋ねませんでした。コンパイル時にチェックされる型システムによって検証されることを望んでいました。
ジミー・ホッファ

1
@JimmyHoffaああ、それは理にかなっています。良い点:)
Reactgular

2

範囲は、不変条件の特殊なケースです。ウィキペディアから:

不変は、プログラムの実行中に本当のことを頼ることができる状態です。

範囲[a, b]は、不変式x> = aおよびx <= bを持つ型の変数xとして宣言できます。Integer

したがって、AdaまたはPascalのサブレンジタイプは厳密には必要ありません。それらは、不変式を持つ整数型で実装できます。


0

この機能が言語に追加されていないのは奇妙です。

範囲が限定された型の特別な機能は、C ++や強力な型システムを備えた他の言語では必要ありません。

C ++では、ユーザー定義型で目標を比較的簡単に達成できますそして、範囲が制限されたタイプが望ましいアプリケーションでは、それらはほとんど十分ではありません。たとえば、速度/時間で加速度が生成され、加速度/時間の平方根を取ることで速度が生成されるように、物理ユニットの計算が正しく記述されていることをコンパイラに確認させます。これを便利に行うには、式に現れる可能性のあるすべてのタイプを明示的に命名することなく、タイプのシステムを定義する機能が必要です。 これはC ++で実行できます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.