I have been working on a small mathematical scripting engine (or DSL, if you prefer). Making it for fun, its nothing serious. In any case, one of the features I want is the ability to get results from it in a type safe manner. The problem is that there are 5 different types that it can return.
Number, bool, Fun, FunN and NamedValue. There is also AnyFun which is a abstract base class for Fun and FunN. The difference between Fun and FunN is that Fun only takes one argument, while FunN takes more then one argument. Figured it was common enough with one argument to warrant a separate type (could be wrong).
At the moment, I am using a wrapper type called Result and a class called Matcher to accomplish this (inspired by pattern matching in languages like F# and Haskell). It basically looks like this when you use it.
engine.Eval(src).Match()
.Case((Number result) => Console.WriteLine("I am a number"))
.Case((bool result) => Console.WriteLine("I am a bool"))
.Case((Fun result) => Console.WriteLine("I am a function with one argument"))
.Case((AnyFun result) => Console.WriteLine("I am any function thats not Fun"))
.Do();
This is my current implementation. It is rigid, though. Adding new types is rather tedious.
public class Result
{
public object Val { get; private set; }
private Callback<Matcher> _finishMatch { get; private set; }
public Result(Number val)
{
Val = val;
_finishMatch = (m) => m.OnNum(val);
}
public Result(bool val)
{
Val = val;
_finishMatch = (m) => m.OnBool(val);
}
... more constructors for the other result types ...
public Matcher Match()
{
return new Matcher(this);
}
// Used to match a result
public class Matcher
{
internal Callback<Number> OnNum { get; private set; }
internal Callback<bool> OnBool { get; private set; }
internal Callback<NamedValue> OnNamed { get; private set; }
internal Callback<AnyFun> OnAnyFun { get; private set; }
internal Callback<Fun> OnFun { get; private set; }
internal Callback<FunN> OnFunN { get; private set; }
internal Callback<object> OnElse { get; private set; }
private Result _result;
public Matcher(Result r)
{
OnElse = (ignored) =>
{
throw new Exception("Must add a new exception for this... but there was no case for this :P");
};
OnNum = (val) => OnElse(val);
OnBool = (val) => OnElse(val);
OnNamed = (val) => OnElse(val);
OnAnyFun = (val) => OnElse(val);
OnFun = (val) => OnAnyFun(val);
OnFunN = (val) => OnAnyFun(val);
_result = r;
}
public Matcher Case(Callback<Number> fn)
{
OnNum = fn;
return this;
}
public Matcher Case(Callback<bool> fn)
{
OnBool = fn;
return this;
}
... Case methods for the rest of the return types ...
public void Do()
{
_result._finishMatch(this);
}
}
}
The thing is that I want to add more types. I want to make so functions can return both numbers and bools, and change Fun to Fun< T >, where T is the return type. This is actually where the main problem lies. I have AnyFun, Fun, FunN, and after introducing this change I would have to deal with AnyFun, Fun< Number >, Fun< bool >, FunN< Number >, FunN< bool >. And even then I would want it to match AnyFun against any function that isnt matched themselves. Like this:
engine.Eval(src).Match()
.Case((Fun<Number> result) => Console.WriteLine("I am special!!!"))
.Case((AnyFun result) => Console.WriteLine("I am a generic function"))
.Do();
Does anyone have any suggestions for a better implementation, that handles adding new types better? Or are there any other suggestions for how to get the result in a type safe manner? Also, should I have a common base class for all the return types (and add a new type for bool)?
Performance is not an issue, btw.
Take care, Kerr
EDIT:
After reading the feedback, I have created this matcher class instead.
public class Matcher
{
private Action _onCase;
private Result _result;
public Matcher(Result r)
{
_onCase = null;
_result = r;
}
public Matcher Case<T>(Callback<T> fn)
{
if (_result.Val is T && _onCase == null)
{
_onCase = () => fn((T)_result.Val);
}
return this;
}
public void Else(Callback<object> fn)
{
if (_onCase != null)
_onCase();
else
fn(_result.Val);
}
public void Do()
{
if (_onCase == null)
throw new Exception("Must add a new exception for this... but there was no case for this :P");
_onCase();
}
}
Its shorter, but the order of the cases matter. For example, in this case the Fun option will never run.
.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))
.Case((Fun result) => Console.WriteLine("I am alone"))
But it will if you switch places.
.Case((Fun result) => Console.WriteLine("I am alone"))
.Case((AnyFun result) => Console.WriteLine("AAANNNNNNNYYYYYYYYYYYYY!!!!"))
Is it possible to improve that? Are there any other issues with my code?
EDIT 2:
Solved it :D.
The C programming language is type-safe in limited contexts; for example, a compile-time error is generated when an attempt is made to convert a pointer to one type of structure to a pointer to another type of structure, unless an explicit cast is used.
Type safety in the source code is a programming language control that ensures that any variable access only its authorized memory locations in a well-defined and permissible way. In other words, the type safety feature ensures that the code doesn't perform any invalid operation on the underlying object.
Return Type − A function may return a value. The return_type is the data type of the value the function returns. Some functions perform the desired operations without returning a value. In this case, the return_type is the keyword void. Function Name − This is the actual name of the function.
void report_square( void ) { int value = INT_MAX; long long squared = 0LL; squared = square( value ); printf( "value = %d, squared = %lld\n", value, squared ); return; // Use an empty expression to return void. }
Your matcher could handle unlimited types by doing something like this:
public class Matcher
{
private readonly Result result; // pass this in
private readonly List<Func<Result, bool>> cases = new ...();
public Matcher Case<T>(Action<T> action)
{
cases.add(result =>
{
if(typeof(T).IsAssignableFrom(result.Value.GetType()))
{
action((T)(result.Value));
return true;
}
return false;
}
return this;
}
public void Do()
{
for each(var @case in cases)
{
if(@case(result)) return;
}
}
}
I think you don't actually need a list, unless your Result
doesn't have a Value
until later on. I don't quite understand your object model, but if the type of the result is known, then don't use a list and just do the type test immediately.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With