-
-
Save jonsterling/539747 to your computer and use it in GitHub Desktop.
#import <Foundation/Foundation.h> | |
#import <CoreGraphics/CoreGraphics.h> | |
#import <objc/message.h> | |
#import <string> | |
#import <map> | |
#import <typeinfo> | |
#define $(value) box<typeof(value)>(value) | |
using namespace std; | |
template <typename T> id box(T v) { | |
typedef pair<Class,SEL> m; | |
map<const char *,m> ops; | |
ops[@encode(int)] = m([NSNumber class], @selector(numberWithInt:)); | |
ops[@encode(float)] = m([NSNumber class], @selector(numberWithFloat:)); | |
ops[@encode(double)] = m([NSNumber class], @selector(numberWithDouble:)); | |
ops[@encode(BOOL)] = m([NSNumber class], @selector(numberWithBool:)); | |
ops[@encode(const char *)] = m([NSString class], @selector(stringWithUTF8String:)); | |
ops[@encode(SEL)] = m([NSString class], @selector(objectify_stringFromSelector:)); | |
ops[@encode(Class)] = m([NSString class], @selector(objectify_stringFromClass:)); | |
ops[@encode(CGPoint)] = m([NSValue class], @selector(valueWithCGPoint:)); | |
ops[@encode(CGRect)] = m([NSValue class], @selector(valueWithCGRect:)); | |
ops[@encode(CGSize)] = m([NSValue class], @selector(valueWithCGSize:)); | |
ops[@encode(NSRange)] = m([NSString class], @selector(objectify_stringFromRange:)); | |
ops[@encode(void *)] = m([NSValue class], @selector(valueWithPointer:)); | |
m method = ops[@encode(T)]; | |
Class klass = method.first ? method.first : [NSObject class]; | |
SEL s = method.second ? method.second : @selector(objectify_identity:); | |
id result = objc_msgSend(klass, s, v); | |
return result; | |
} | |
template <typename T> T unbox(id v) { | |
map<const char *,SEL> ops; | |
ops[@encode(int)] = @selector(intValue); | |
ops[@encode(float)] = @selector(floatValue); | |
ops[@encode(double)] = @selector(doubleValue); | |
ops[@encode(BOOL)] = @selector(boolValue); | |
ops[@encode(const char *)] = @selector(UTF8String); | |
ops[@encode(SEL)] = @selector(objectify_selectorValue); | |
ops[@encode(Class)] = @selector(objectify_classValue); | |
ops[@encode(CGPoint)] = @selector(CGPointValue); | |
ops[@encode(CGRect)] = @selector(CGRectValue); | |
ops[@encode(CGSize)] = @selector(CGSizeValue); | |
ops[@encode(NSRange)] = @selector(objectify_rangeValue); | |
ops[@encode(void *)] = @selector(pointerValue); | |
SEL method = ops[@encode(T)] ? ops[@encode(T)] : @selector(objectify_identity); | |
typedef T (*SendFunctionType)(id, SEL); | |
return ((SendFunctionType)objc_msgSend)(v, method); | |
} |
#import "Objectify.h" | |
@implementation NSString (Objectify) | |
+ (id)objectify_stringFromSelector:(SEL)aSelector { | |
return NSStringFromSelector(aSelector); | |
} | |
- (SEL)objectify_selectorValue { | |
return NSSelectorFromString(self); | |
} | |
+ (id)objectify_stringFromClass:(Class)aClass { | |
return NSStringFromClass(aClass); | |
} | |
- (Class)objectify_classValue { | |
return NSClassFromString(self); | |
} | |
+ (id)objectify_stringFromRange:(NSRange)aRange{ | |
return NSStringFromRange(aRange); | |
} | |
- (NSRange)objectify_rangeValue { | |
return NSRangeFromString(self); | |
} | |
@end | |
@implementation NSObject (Objectify) | |
+ (id)objectify_identity:(id)object { return object; } | |
- (id)objectify_identity { return self; } | |
@end |
#import <Foundation/Foundation.h> | |
#import "Objectify.h" | |
int main (int argc, const char * argv[]) { | |
NSAutoreleasePool *pool = [NSAutoreleasePool new]; | |
const char *str = "wut"; | |
id o = $<const char *>(str); | |
NSLog(@"char: %s, string: %@, of class: %@", str, o, [o class]); | |
// => char: wut, string: wut, of class: NSCFString | |
[pool drain]; | |
} |
Yeah, that works. I was actually considering doing that too (and maybe reserving the $
shorthand for the macro).
So is this type of thing possible at all without C++?
Possibly, using @encode(typeof(val))
.
By the way, I think something might be a tad wrong with your macro. Nowhere is o
actually used, except in capturing its type…
Whoops, #define VAL(o) ({ typeof(o) _o = (o); $<typeof(_o)>(_o); })
Although, now that I look at the way it works, since it seems typeof
doesn't evaluate the expression, you don't even need all that. Just #define VAL(o) ($<typeof(o)>(o))
.
The issue I was running across when trying to use pure Objective-C is that we need a way to accept an argument of any type. Using (void *)
produces compiler warnings.
Yep. typeof
is an operator that is evaluated compile-time.
What warnings? Plenty of Cocoa things take a void *
context.
Two problems. The first:
id lame(void *l) {
//...
return [NSNumber numberWithInt:(int)l]; // ERROR: cast from 'void *' to 'int' loses precision
}
This first issue can be gotten around by using objc_msgSend
, though. Not a big deal.
The second problem is that @encode
will always give us "^v"
, because it is not smart enough to realize that we did an unsafe cast. It's just going to use whatever it was given.
Yes, you'll have to use something like #define VAL(o) (lame(@encode(typeof(o)), (o)))
with id lame(char *type, void *l);
.
I get the strange feeling of writing Lisp... :)
Yeah. In fact, originally I was doing something almost identical to that. But then I decided the C++ route was more elegant…
C++, elegant? lolz
Actually, yeah, it kind of is.
C++ is a little dirty, but you know what? Any day, I'll take a dictionary that lets me put anything I want into it (under the condition that all my values and keys be unified in type). Any day, I'll take a dictionary that lets me access it using a custom operator instead of ridiculously long and bureaucratic messages.
The purity of Smalltalk-messaging is cool and all, but the Smalltalk paradigm is only really useful, I think, in a language that has autoboxing. Like Smalltalk (or Ruby). Also, the Smalltalk libraries had simpler method names. For instance, dict at:#sym put:"dawg"
instead of dict setValue:@"dawg" forKey:kSym"
. I still prefer dict[:sym] = "dawg"
…
Also, templates are cool. They'd be less cumbersome with actual, behind-the-scenes type inference, but it's still pretty freaking cool.
heheh
Ruby doesn't have autoboxing per se, but boxing by virtue of the massively object-orientedness of it. If everything is an object, no boxing is ever needed. And that's why it's awesome.
Indeed, thanks for the correction.
In short: Type safety is cool, but also sucks.
Humbug. I think the reason people get annoyed with type safety is that they think it means a lot of extra work. And then they come out with solutions that produce the right result most of the time, but are by their very nature incorrect. Type-safety means that we won't be tricked into thinking things work.
But type-safety in a language lacking quality type inference IS A BITCH. Really.
It seems inference really is the key, yes, so that you can pass values around and store them in variables whose types are determined at compile time or even runtime. The programmer is not concerned with the types of the data, merely with the data itself and its manipulation; and it's the compiler's/runtime's job to check that the types are being properly combined.
That said, the need for autoboxing is an indication of a limited type system. For example, C++ doesn't need autoboxing because of templates.
(btw: please inform me if I have no idea what I'm talking about. Apart from it being late, I haven't been stretching myself programming-wise lately; just using Obj-C at work, and other technologies I'm familiar with.)
Yes, well said with respect to inference. As for autoboxing, I think you are right to a point. Haskell, at it's lowest implementation levels, does indeed involve autoboxing of primitives, as does Smalltalk; yet at the programmer's level, these are always values and objects respectively. From my extremely limited understanding, this sort of boxing is a performance measure, but I may be misunderstanding something.
(My Haskell bias demands I should point out the benefits of handling as much type-checking at compile-time as possible)
I can certainly believe it's a performance measure, since a main reason to use primitives over boxed values is for performance. Much as it's not collectable and annoying, int
is faster than NSNumber
;)
Type checking at compile time seems like the best option, but in order to check, for example, a template enough to be sure it will work would involve traversing through the steps and types each place that template is used in the code. I would think that'd be a very resource-intensive operation. And it would also not allow such haxxery as truly dynamic typing — that is, if a type were read as input to the program and used (à la NSClassFromString
or Ruby's const_get
).
True. When working with a good static type system, the way you reason about solutions changes; problems that were solved previously using metaprogramming can be solved just as succinctly elsewise. But metaprogramming is so damned fun…
As for resource-intensive checking, just try compiling any Haskell program. Seems to work quickly enough for me… :)
not really following along completely, but are you calling the objc version that gave warnings like so?
id to_object( void * x )
{
return [NSNumber numberWithInt: (int)x];
}
int a = 42;
id foo = to_object( a );
???
If you do it this way it doesn't give a warning:
id to_object( void * x )
{
return [NSNumber numberWithInt: *(int*)x];
}
int a = 42;
id foo = to_object( &a );
requires sending in the address of the thing you want converted though...
Wow, Tyler, you're totally right. Should have thought of that. :)
maybe it prevents detecting the type though... didn't try to actually do what you were trying to do so not sure. Another other option would be to use C's var-args as the parameter because you can get their types moderately easily. I'm almost motivated enough to have a non-C++ version to sit down and write it, but a little pressed for time at the moment.
Will this work?
#define VAL(o) ({ typeof(o) _o; $<typeof(_o)>(_o); })