This post is the last in my series on building a statistics library using Delphi 2009’s new generic types. If you’re new to the series, you might want to start with the first post. Having implemented the functions FoldL and Map, and having used FoldL to implement Count and Sum, implementing Average and StandardDeviation are now trivial:
class function TStatistics<T>.Average( const AData: TEnumerable<T>; AAdder: TBinaryOp<T>; ADivider: TBinaryOp<T>; AExplicitCast: TFunc<integer, T>): T; var Total, Num: T; begin Total := Sum(AData, AAdder, AExplicitCast); Num := Count(AData, AAdder, AExplicitCast); Result := ADivider(Total, Num); end; class function TStatistics<T>.StandardDeviation( const AData: TEnumerable<T>; ADeviation, AAdder, ADivider:TBinaryOp<T>; ARoot, ASquare: TUnaryOp<T>; AExplicitCast: TFunc<integer, T>): T; var Avg, Variance: T; SquaredDeviations: TEnumerable<T>; begin Avg := Average(AData, AAdder, ADivider, AExplicitCast); SquaredDeviations := Map( function(AValue: T): T begin Result := ASquare(ADeviation(AValue, Avg)); end, AData); try Variance := Average(SquaredDeviations, AAdder, ADivider, AExplicitCast); Result := ARoot(Variance); finally SquaredDeviations.Free; end; end;
You can download the complete project from CodeCentral.
Even having to use functions like "AAdder" in place of the more conventional "+", I think the algorithm is still fairly easy to read here, at least if you understand the standard deviation. As Raymond points out in comments to the previous post, however, the syntax may not be familiar to see folks who have spent most of their time in older versions of Delphi.
I’m afraid you’re just going to have to get used to it. Delphi is well behind the pack in getting support for anonymous methods, and in many environments it is difficult to get much done without them. JavaScript comes to mind; the asynchronous nature of much of what you have to do in JavaScript makes a continuation-passing style quite practical. Lambda expressions, which are, very roughly, a more concise version of an anonymous function, are required to do any useful work in LINQ. In languages like Lisp and Haskell, all "lines" of code are (again, very roughly) functions, which are anonymous by default. Few mainstream languages don’t support this, and many environments leverage it heavily. If you don’t understand such syntax well, you will find it hard to work outside of Delphi, like a COBOL programmer who doesn’t understand OO. You aren’t required to like it, but you have to understand it.
{ 13 } Comments
Great series of articles covering generics and anonymous methods … Hopefully we will see more coming into the future !
The best example of mainstream language (#1 actually) that have no closures/anonymous methods is … Java (it is alot debated if Java 7 will have it, in the form proposed by Neal Gafter). So in this regard, Delphi is with a step in front !
"El Cy," Java’s "generics" are also somewhere in between poorly designed and fake. Thanks for the compliment; yes, there will be more coming.
Excellent article. Any way you can convert the project to C# ?
Thanks,
Tony
Tony, I don’t think there’s anything in the series which would not work in C# as well. Is there something which isn’t obvious about how to convert it?
Craig Stuntz, good evening.
The best way to overcome operators using prohibition in .NET on typed parameter is to use power of native code.
The best solution is to allow native Delphi compiler to be an independent compiler without caution to Delphi.NET ones.
I repeat again and again
-No constraints is required to achieve safe code in native.
-Constraints may be usefull only as syntax sugar (some form of hack(smart) code to guarantee safe code).
So if we need AExplicitCast(but in yours article it is just a initializer) to T.
We just declare cast operator in T type.
if we need Binary operator on T and U,
(in yours series of articles it is restricted to binaryOp(T,T))
we just declare Binary operator in T or U.
What do you think?
Thanks, Sergey.
Sergey, the constraints are required because there is no guarantee that the generic type and the code which specializes that generic type is compiled at the same time. Consider the case where you create a package containing a generic type, and then an executable which specializes that type by passing some other type as a parameter. Without the constraint, the compiler does not know that the type you will use to specialize the generic has the required features.
"Native code" has its advantages, but enhanced type safety is not one of them.
Craig, in native Delphi there is no IL, and therefore no code would be generated at declare time of generic type in opposition to .NET.
The main difference is that generic IL code generated by compiler in .NET at compile time. Native code is generated at run time.
On native delphi native code is generated at compile time.
There is no way to instantiate generic class at run time in native delphi.
So package can not expose generic class. Package can only expose the closed constructed type. So no problem to interoperate with closed constructed type. And no constrains are required.
So there is no way to instantiate package generic class by external environment without having acess to generic code(i.e. source code) in native delphi.
That is an oversimplification. The Delphi compiler stores the parse tree of a generic type in the DCU, much as with method inlining. So there is more than one "compile time" involved here. I haven’t tested, but I would presume that the compiler reuses compiled, binary specializations for reference types, in other words, more like C# then C++ templates.
I think your references to IL are a red herring. I see no evidence that CodeGear is requiring constraints for reasons having anything to do with .NET; to the contrary they have announced that Delphi for .NET language will diverge from the Delphi for Win32 language. Nor can I see how compiling sooner rather than later affords more type safety. I generally consider C# to be more type safe than Delphi.
I haven’t tried subtyping a generic type defined in a package yet. Have you? I haven’t heard that there’s a rule against it, and I would tend to presume that it is allowed, but like I said, I haven’t tried it yet.
> I haven’t tested, but I would presume that the compiler reuses compiled, >binary specializations for reference types, in other words, more like C# then >C++ templates.
Craig, I have read
"Design and Implementation of Generics for the.NET Common Language Runtime" by Andrew Kennedy Don Syme.
But share "work native code" in .NET for reference typed parameters is only valid on .NET because copy semantic is equal for all reference types
("o" types in terms of .NET), i.e. no copy semantic special operations, just analizing by GC roots in evaluation stacks slots in GC time.
But on native Delphi there are so many reference types with absolutely different copy semantic:
1. class intances
2. interfaces(_addref, _release of unknown)
3. dynamic string and arrays.
4. pointers
What reference type do you have in mind?
So there is no way to share "native code" for all reference types in native Delphi.
C# and Delphi are strongly type languages.
Constrains is solution to guarantee safe code at generics run time instantiation in .NET. It is just a way to guarantee compile time IL code generation to support types parameters supplying at run time.
So no run time instantiation, no contraints needed.
This is where we disagree. I don’t know of any dynamic language which requires them. On the other hand, I can’t think of a "native", statically typed language which doesn’t have them, or something like them, in its implementation of type parameters. Haskell, for example, has type classes, which do much the same thing, its complete lack of .NET support notwithstanding. It seems to me that they are more useful at compile time than at runtime/specialization time. By the time you get to actually specializing a generic, you’d better know that the type you’re passing as a parameter already works. It’s way too late, for a statically-type, compiled language, to be failing then.
Yes, Delphi has lots of reference types, but not that many "types of types" relative to the number of actual types.
Thanks for the excellent series of posts that show some of the power (and some of the restrictions!) of generics and anonymous methods when used together.
As to the syntax, I’m sure I’ll get used to it
I still have misgivings over the whole TUnaryOp issue, though this is from performance rather than semantic or syntactic points of view.
Ultimately it’s another tool in the toolbox and should be used where appropriate. There’s an old adage that applies here, I think: "To a man with a hammer, every problem looks like a nail"
Cheers,
Raymond.
Craig, good afternoon.
So far as I known
.NET 1.0 CIL already support some kind of generic behaviour.
For instance the ldarg & starg IL instructions are generic since .NET 1.0.
Since all value types have its associated reference type equivalents(boxed form) there is a generic way to call root of references type System.Object methods. But to do this safe you must to box typed parameter to quaranteee unified interface invoking for native code(via VMT) since all reference types have System.Object virtual methods and boxed value types
(it is just wrapped value type by reference type) too.
So no problem in quarantee safe code.
Here constraints are not needed.
But if need to quarentee run time safe call methodA on type parameter and compile time generated IL code, we have to do additional actions to quarantee safe and suitable compile time generated IL code for type parameters, i.e. we have to declare constraints.
But if instantion occurs(native code generation) at compile time there is no needs to constraint at all, because compiler just analize the IL code to infer violations. JIT can do this at run time, but it is time consuming operation. But there is way to simplify this operation by generalization unsafe operations in constraints and to check constaraints.
So people who say that .NET is a single rooted dissemble at little. All references type are single rooted. But not a value types in there value form.
Value types have identical methods name as in Sytem.Object, but not are inherited from Sytem.Object, where it’s boxed form are.
Sergey, I’m not sure what your comments on .NET have to do with Win32 generics. After all, I do agree with you insofar as the Win32 generics implementation should not be constrained by .NET needs, given that CodeGear has stated that their Delphi for .NET language will diverge from their Win32 language. This decision was made well before the release of Delphi 2009, so I have no particular reason to want to think about .NET very much when explaining Win32 generics.
I’m trying to figure out where you’re coming from with your arguments against constraints, and it strikes me that you’re asking for something very much like C++ templates, which are really just code generation, rather than higher-order types.
Imagine what it would be like to write a generic type without constraints. Forget, for a moment, the problem of actually instantiating this thing; let’s just look at writing the generic type itself. When I write code that deals with a non-generic type in a statically-typed language, such as Delphi, I am always aware of what I can and cannot do with the type. If I’m working with an instance of TComponent, for example, I know that I can call the Notification method, whereas if I’m working with an instance of TInterfacedObject, I know that I cannot call "Notification," because that type doesn’t have such a method.
Constraints give me a similar knowledge of what I can and cannot do with a generic type parameter, inside and outside of the type implementation, without having to limit my options to a single type.
When you say that constraints are not necessary because they can be derived from the code, you are correct to a first approximation, but you’re engaging in a bit of misdirection, because, in this case, the code itself is just another form of a constraint, albeit specified in a way which is very difficult for someone who is not a compiler to read, and in a form which is useless for developer productivity features such as code completion within the type implementation.
The problem with such an implied constraint (and again, I’m just talking about writing the generic type itself, rather than the issues surrounding specializing such a type), is that, being vaguely and illegibly specified, it is incredibly easy to change by accident. While fixing a bug, a coder could easily but drastically change the nature of the "constraint," invalidating large chunks of code which used the generic type. This is, generally, why statically-typed languages such as Pascal require declaring the interface upfront; it makes a sort of accident much harder to happen.
The constraint, in other words, puts the requirements for the type parameter into the public interface of the type, rather than in the implementation details. In my humble opinion, that is the "Pascal-ish" way to do it.
{ 1 } Trackback
[...] the final post in this series, I’ll use the infrastructure I’ve developed to implement Average and [...]
Post a Comment