C++
、C#
、Java
などの多くの言語では、integer
やfloat
などの単純な型を表すオブジェクトを作成できます。クラスインターフェイスを使用すると、演算子をオーバーライドし、値が100のビジネスルールを超えているかどうかをチェックするなどのロジックを実行できます。
一部の言語でこれらのルールを変数またはプロパティの注釈または属性として定義できるかどうか疑問に思っています。
たとえば、C#
には次のように記述します。
[Range(0,100)]
public int Price { get; set; }
または、C++
に次のように書くこともできます。
int(0,100) x = 0;
私はこのようなことが行われるのを見たことがありませんが、ストレージの前にデータ検証に依存するようになったことを考えると。この機能が言語に追加されていないのは奇妙です。
これが可能な言語の例を教えてください。
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
は、a
が(reset!
またはswap!
を介して)変更されたときに、条件が満たされているかどうかをチェックするときに呼び出されます。これは、遅延バインディング言語で部分範囲動作を実装するための例となる可能性があります( http://blog.fogus.me/2011/09/23/clojurescript-watchers-and-validators/ を参照)。
Adaは単純型の制限を許可する言語でもあります。実際、Adaでは、プログラムが正確であることを保証するために 独自の型を定義する を実行することをお勧めします。
type MyType1 is range 1 .. 100;
type MyType2 is range 5 .. 15;
myVar1 : MyType1;
それはDoDによって長い間使用されていましたが、まだ使用されている可能性がありますが、現在の使用状況はわかりません。
JavaとC#で注釈と動的プロキシパターンの組み合わせを介して可能な限り、あなたの意図のいくつかの制限された形式は、動的プロキシの組み込み実装がJavaおよびC#)。
Javaバージョン
注釈:
@Target(ElementType.PARAMETER)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {
int min ();
int max ();
}
Proxyインスタンスを作成する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で動作させることができることがわかりますが、完全に便利ではありません。
C#のDynamicObjectクラスの機能により、C#実装に見られるように、インターフェイスの制限がなくなります。残念ながら、この場合、この動的動作により静的型の安全性が失われるため、動的プロキシでのメソッド呼び出しが許可されているかどうかを判断するには、ランタイムチェックが必要です。
これらの制限が受け入れられる場合、これはさらに掘り下げるための基礎として役立ちます!
C++で範囲チェックされた値型を作成する方法の例については、 C++での値型の範囲の制限 を参照してください。
エグゼクティブサマリー:テンプレートを使用して、組み込みの最小値と最大値を持つ値タイプを作成します。これは次のように使用できます。
// create a float named 'percent' that's limited to the range 0..100
RangeCheckedValue<float, 0, 100> percent(50.0);
ここではテンプレートさえ実際には必要ありません。クラスを使用して同様の効果を得ることができます。テンプレートを使用すると、基になるタイプを指定できます。また、上のpercent
の型はfloat
ではなく、テンプレートのインスタンスになることに注意することが重要です。これは、質問の「単純型」の側面を満たさない場合があります。
この機能が言語に追加されていないのは奇妙です。
単純型はそれだけです-単純です。多くの場合、これらは直接使用するのではなく、必要なツールを作成するためのビルディングブロックとして最適に使用されます。
範囲は、不変条件の特殊なケースです。ウィキペディアから:
不変は、プログラムの実行中にtrueであると信頼できる条件です。
範囲[a, b]
は変数として宣言できますxInteger
タイプで、不変式x> = aおよびx <= bを使用します。
したがって、AdaまたはPascalの部分範囲型は厳密には必要ありません。それらは不変の整数型で実装できます。
この機能が言語に追加されていないのは奇妙です。
C++および強力な型システムを備えたその他の言語では、範囲が限定された型の特別な機能は必要ありません。
C++では、 ユーザー定義型 を使用すると、比較的簡単に目標を達成できます。 そして範囲制限されたタイプが望ましいアプリケーションでは、それらはほとんど十分ではありません。たとえば、速度/時間で加速度が生成され、加速度/時間の平方根で速度が生成されるように、物理単位の計算が正しく記述されていることをコンパイラに確認させることもできます。これを行うには、式に現れる可能性のあるすべての型に明示的に名前を付けることなく、型のシステムを定義する機能が必要です。 これはC++で実行できます 。