 |
 |
|
 |
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
OK, so most companies don't seem to be interested in Java any more. Most
of them are asking for something.net, and the most popular one is C#,
unsurprisingly. So I'm sitting hear reading some C# books.
Like anything else, there are some good things, and not so good things.
The one that really tickles me is the author insisting that because
assemblies are loaded dynamically, and because CIL takes up less space
than machine code and is JIT-compiled, you can "drastically reduce the
working set of your application".
Uhuh. Erm... you realise this thing uses a GC for memory management,
right? ;-)
I also love the way it raves about why "metadata" is so great, without
actually explaining precisely what it solves.
But anyway, early days...
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
> OK, so most companies don't seem to be interested in Java any more. Most
> of them are asking for something.net, and the most popular one is C#,
> unsurprisingly. So I'm sitting hear reading some C# books.
I assume you've downloaded and installed MS Visual C# Express already?
If not do it now :-)
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 28/09/2012 03:21 PM, scott wrote:
>> OK, so most companies don't seem to be interested in Java any more. Most
>> of them are asking for something.net, and the most popular one is C#,
>> unsurprisingly. So I'm sitting hear reading some C# books.
>
> I assume you've downloaded and installed MS Visual C# Express already?
Twice, in fact.
First I downloaded the full install. Apparently the only way to do that
is to download an ISO image. Which means you have to burn it to a CD
before you can access it. [Unless you use Unix. :-P ] Unfortunately,
when I tried to install, I got a CRC32 error, and it died half way
through the installation.
So then I had to try again. I've now got it to the point where VS opens.
I don't know if it actually works yet, or whether I need to completely
reinstall Windows to fix it...
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On Fri, 28 Sep 2012 15:51:34 +0100, Orchid Win7 v1 wrote:
> Apparently the only way to do that is to download an ISO image. Which
> means you have to burn it to a CD before you can access it. [Unless you
> use Unix. :-P ]
Daemon Tools.
Jim
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 28/09/2012 03:13 PM, Orchid Win7 v1 wrote:
> So I'm sitting hear reading some C# books.
Heh. It amuses me that run-time polymorphism via inheritance is /the/
central contribution of the OO movement, and here I am reading a book
warning me not to use inheritance under any circumstances unless
absolutely unavoidable...
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Le 2012-09-28 10:51, Orchid Win7 v1 a écrit :
> On 28/09/2012 03:21 PM, scott wrote:
>>> OK, so most companies don't seem to be interested in Java any more. Most
>>> of them are asking for something.net, and the most popular one is C#,
>>> unsurprisingly. So I'm sitting hear reading some C# books.
>>
>> I assume you've downloaded and installed MS Visual C# Express already?
>
> Twice, in fact.
>
> First I downloaded the full install. Apparently the only way to do that
> is to download an ISO image. Which means you have to burn it to a CD
> before you can access it. [Unless you use Unix. :-P ]
You can mount ISOs as a file system under Windows. Microsoft has an
utility for it, but in only works up to Vista. For Windows 7 or 8, you
need Virtual Clonedrive ( http://www.slysoft.com/en/download.html )
--
/*Francois Labreque*/#local a=x+y;#local b=x+a;#local c=a+b;#macro P(F//
/* flabreque */L)polygon{5,F,F+z,L+z,L,F pigment{rgb 9}}#end union
/* @ */{P(0,a)P(a,b)P(b,c)P(2*a,2*b)P(2*b,b+c)P(b+c,<2,3>)
/* gmail.com */}camera{orthographic location<6,1.25,-6>look_at a }
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Le 2012-09-28 12:57, Orchid Win7 v1 a écrit :
> On 28/09/2012 03:13 PM, Orchid Win7 v1 wrote:
>> So I'm sitting hear reading some C# books.
>
> Heh. It amuses me that run-time polymorphism via inheritance is /the/
> central contribution of the OO movement, and here I am reading a book
> warning me not to use inheritance under any circumstances unless
> absolutely unavoidable...
So, what's their solution? You have to reinvent the wheel all the time?
So if you need a linked list of widgets and a linked list of gizmos, you
have to have two complete sets of add(), remove() and iterate() methods?
--
/*Francois Labreque*/#local a=x+y;#local b=x+a;#local c=a+b;#macro P(F//
/* flabreque */L)polygon{5,F,F+z,L+z,L,F pigment{rgb 9}}#end union
/* @ */{P(0,a)P(a,b)P(b,c)P(2*a,2*b)P(2*b,b+c)P(b+c,<2,3>)
/* gmail.com */}camera{orthographic location<6,1.25,-6>look_at a }
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Orchid Win7 v1 <voi### [at] dev null> wrote:
> On 28/09/2012 03:13 PM, Orchid Win7 v1 wrote:
> > So I'm sitting hear reading some C# books.
>
> Heh. It amuses me that run-time polymorphism via inheritance is /the/
> central contribution of the OO movement, and here I am reading a book
> warning me not to use inheritance under any circumstances unless
> absolutely unavoidable...
forget all those silly concepts from olde ages. Apps today are just a
multi-tiered assemblage of multiple layers of API calls to multiple frameworks.
The language you use is irrelevant, which is why .net allows you to use your
favorite language syntax to call the APIs.
it's all dynamic and marshaled in real-time wrapped in zipped xml sent
asynchronously via http. bwahahahaha
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Orchid Win7 v1 <voi### [at] dev null> wrote:
> Heh. It amuses me that run-time polymorphism via inheritance is /the/
> central contribution of the OO movement, and here I am reading a book
> warning me not to use inheritance under any circumstances unless
> absolutely unavoidable...
Does it explain why?
--
- Warp
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Orchid Win7 v1 <voi### [at] dev null> wrote:
> Like anything else, there are some good things, and not so good things.
> The one that really tickles me is the author insisting that because
> assemblies are loaded dynamically, and because CIL takes up less space
> than machine code and is JIT-compiled, you can "drastically reduce the
> working set of your application".
I don't even understand what that means.
I have seen several arguments in the past (but I think they have mostly
died alongside the prejudice against C++ and templates) that C++ templates
are bad because they increase code size, thus increasing memory usage.
That's one of the stupidest arguments ever. The reason is that the
alternative proposed by these critics is to use runtime polymorphism.
Yes, rather than have the same function repeated for each used type you
will have one single function that handles all the types. However, if all
your objects are dynamically allocated and dynamically bound (which is
necessary for runtime polymorphism) they will end up taking enormously
more memory than duplicating that function a few times (optimized to handle
each type statically).
In other words, their solution to the "huge" "problem" of increased code
size is to increase memory usage ten-fold in order to make the executable
binary slightly smaller. Great job.
--
- Warp
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 28/09/2012 09:32 PM, Warp wrote:
> Orchid Win7 v1<voi### [at] dev null> wrote:
>> Like anything else, there are some good things, and not so good things.
>> The one that really tickles me is the author insisting that because
>> assemblies are loaded dynamically, and because CIL takes up less space
>> than machine code and is JIT-compiled, you can "drastically reduce the
>> working set of your application".
>
> I don't even understand what that means.
C# is compiled to CIL, which is then JIT-compiled to machine code. The
author claims (and I am highly sceptical) that CIL is much smaller than
machine code. The author also says that since only the parts of your
code which actually /run/ get compiled to machine code, the running
machine code is smaller, giving your application a smaller "working set".
This, it is claimed, can reduce page faults, improve cache performance,
and so forth.
Yes, that's right. Your application is using 250MB of heap space, but
the executable binary is 4KB smaller, and that's the important thing, right?
> I have seen several arguments in the past that C++ templates
> are bad because they increase code size, thus increasing memory usage.
> In other words, their solution to the "huge" "problem" of increased code
> size is to increase memory usage ten-fold in order to make the executable
> binary slightly smaller. Great job.
Yeah, that's about the size of it.
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
>> Heh. It amuses me that run-time polymorphism via inheritance is /the/
>> central contribution of the OO movement, and here I am reading a book
>> warning me not to use inheritance under any circumstances unless
>> absolutely unavoidable...
>
> So, what's their solution? You have to reinvent the wheel all the time?
Use interfaces instead.
> So if you need a linked list of widgets and a linked list of gizmos, you
> have to have two complete sets of add(), remove() and iterate() methods?
That is better handled with generics than any kind of inheritance. A
better example might be a single-linked list and a double-linked list.
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 28/09/2012 09:27 PM, Warp wrote:
> Orchid Win7 v1<voi### [at] dev null> wrote:
>> Heh. It amuses me that run-time polymorphism via inheritance is /the/
>> central contribution of the OO movement, and here I am reading a book
>> warning me not to use inheritance under any circumstances unless
>> absolutely unavoidable...
>
> Does it explain why?
In most OO languages (e.g., Java, Eiffel, Smalltalk...) you can override
any method. Java adds a "final" keyword which you can use to disable
overriding on a given method (presumably resulting in a small
performance benefit). Eiffel of course achieves the same thing
automatically using whole-program analysis - then again, Eiffel doesn't
support dynamic code loading like Java does.
Regardless, C# turns this backwards. In C#, by default you /cannot/
override anything, unless you explicitly turn it on by writing
"virtual". Meaning that if the author of some class didn't foresee the
potential need to subclass it, you may not be able to override the
necessary methods.
But that's nothing. According to the author of this book, you should
mark all classes as "sealed", preventing them from ever being
subclassed. Every class should be sealed, and no methods should be
marked virtual, unless absolutely necessary.
Apparently it is "very hard" to design a class in such a way that it can
be subclassed correctly. So you should only allow this possibility where
a class has been explicitly designed with the intention of being
subclassed later. In all other cases, you should disable subclassing,
because if you didn't design for it, it won't work anyway.
All of which strikes me as a little baffling. I mean, I know that
inheritance is overused and collaboration is underused. But this just
seems excessive!
Then again, this is from the same book that explains how to write
destructors (i.e., "don't") before explaining how to write methods with
named arguments. The former is an obscure and very dangerous feature
which you should never use, under any circumstances, ever. The latter is
a pretty simple and common feature. Really, the order of presentation
here seems rather strange. Rather than a coherent introduction, it seems
like a random grab-bag of "features", in no particular order...
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 9/29/2012 3:19, Orchid Win7 v1 wrote:
>>> Heh. It amuses me that run-time polymorphism via inheritance is /the/
>>> central contribution of the OO movement, and here I am reading a book
>>> warning me not to use inheritance under any circumstances unless
>>> absolutely unavoidable...
>>
>> So, what's their solution? You have to reinvent the wheel all the time?
>
> Use interfaces instead.
Sounds like a stupid book. I wouldn't trust its advice.
Don't use inheritance unless you're obviously making a subclass of the thing
you're inheriting from. Don't use polymorphic dispatch unless you intended
to use polymorphic dispatch.
I think of all the code in my entire project, I have exactly one set of
routines that inherit from some other routine I wrote, and they're basically
type-specific widgets for displaying the results of searching over
collections of various kinds of documents.
I have a bunch of .. well, database connectors, one for each table, that all
inherit from the general "database connector." I have a bunch of protobufs
that all inherit from "generic protobuf".
Otherwise, there's virtually no inheritance in my work code.
I have a game, where I do indeed have some inheritance, but where multiple
inheritance might very well have worked out better. For each stackable
screen (say, you have a help screen on top of a file selection screen on top
of a start game screen on top of a top-level-menu screen), it inherits from
the appropriate superclass (input-blocking screen, input-blocking screen
with timeout, etc). Other than that, again, very general.
IME, libraries often have classes you'd almost never use as is, which you
can inherit from, and applications generally have very few classes you
actually inherit from. And for the latter, you almost always know when
you're writing the base class that you're going to be inheriting from it.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 9/29/2012 3:27, Orchid Win7 v1 wrote:
> Regardless, C# turns this backwards. In C#, by default you /cannot/ override
> anything, unless you explicitly turn it on by writing "virtual". Meaning
> that if the author of some class didn't foresee the potential need to
> subclass it, you may not be able to override the necessary methods.
In practice, this is generally not a problem. If you're not thinking of code
at the level of overriding virtual methods, it's usually impossible to
override a method correctly, unless you're just doing something like adding
logging that the method got called. (At which point, how do you get your
custom class into the places where it's supposed to be, if others are
instantiating the wrong class?)
> Apparently it is "very hard" to design a class in such a way that it can be
> subclassed correctly. So you should only allow this possibility where a
> class has been explicitly designed with the intention of being subclassed
> later. In all other cases, you should disable subclassing, because if you
> didn't design for it, it won't work anyway.
While this is excessive, it's probably good advice for people who haven't
done a fair amount of work on such projects. Sealing the class is probably a
bit much, but not declaring things virtual that you don't expect a subclass
will want to override is not a bad thing.
> All of which strikes me as a little baffling. I mean, I know that
> inheritance is overused and collaboration is underused. But this just seems
> excessive!
It is. But then again, absolute advice given in a beginner's book is usually
excessive. You develop the good habit, *then* you learn from experience
where the right place to break the habit is.
> Then again, this is from the same book that explains how to write
> destructors (i.e., "don't") before explaining how to write methods with
> named arguments. The former is an obscure and very dangerous feature which
> you should never use, under any circumstances, ever. The latter is a pretty
> simple and common feature. Really, the order of presentation here seems
> rather strange.
Sounds like he wanted to mention destructors along with constructors, and
since he had nothing to say about them besides "don't", he had no other good
place to put them.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 9/29/2012 3:18, Orchid Win7 v1 wrote:
> This, it is claimed, can reduce page faults, improve cache performance, and
> so forth.
Reducing the working set is independent of how much code you have. It
depends on how much code you're working with right at this very moment. The
author doesn't understand the concept of "working set".
Working set is, roughly, the number of pages you're accessing faster than
paging in swapped-out pages is fast enough to keep up with. If you have a
300-meg executable in a tight loop 23K in size, you have a 23K working set.
> Yes, that's right. Your application is using 250MB of heap space, but the
> executable binary is 4KB smaller, and that's the important thing, right?
The part of the heap you're using is also part of the working set.
>> I have seen several arguments in the past that C++ templates
>> are bad because they increase code size, thus increasing memory usage.
Well, C++'s target platforms include things that C# isn't targeted at. If
you're trying to fit your code into a credit card terminal, you're going to
be worried about C++ template code bloat and the features of C#'s execution
model won't even enter the conversation.
>> In other words, their solution to the "huge" "problem" of increased code
>> size is to increase memory usage ten-fold in order to make the executable
>> binary slightly smaller. Great job.
>
> Yeah, that's about the size of it.
Nah. I'm pretty sure C# generics use the same code for each reference type.
I.e., you might have a version for int, a version for float, and one version
for anything descended from Object.
The reason C++ makes more code is that it actually does stuff like inline
the right calls, doing type-specific generic expansions. C# doesn't do that,
so I think you get far fewer versions of the code. Especially for generics
that are anchored, i.e., that are generic over some specific superclass.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Orchid Win7 v1 <voi### [at] dev null> wrote:
> Then again, this is from the same book that explains how to write
> destructors (i.e., "don't")
If C# is anything like Java in this regard, then destructors are pretty
much useless (because when you need destructors for something other than
freeing memory, which isn't necessary in C#, you usually need the
destructor to be called in a deterministic manner at a specific point in
the program, not at some undetermined time in the future maybe.)
In C++ destructors are useful for more than just freeing memory. For
example they are commonly used in things like locks: If a function needs
to use a lock, it can make a static lock instance which frees the lock
in its destructor, and it's guaranteed to be freed immediately when the
function is exited, regardless of how it's exited (ie. no matter where
the 'return' sentence is, or even if it's exited by throwing an
exception). It's handy because you don't need to any special code to
make sure that the destructor is called when the function is exited.
--
- Warp
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Darren New <dne### [at] san rr com> wrote:
> Well, C++'s target platforms include things that C# isn't targeted at. If
> you're trying to fit your code into a credit card terminal, you're going to
> be worried about C++ template code bloat and the features of C#'s execution
> model won't even enter the conversation.
"Template code bloat" is a myth with little to no actual evidence.
If you need the same code to handle more than one type, you have to do that
somehow. Either you duplicate the code for each type, or you use runtime
polymorphism, or alternatively you use some really contrived code full of
conditionals that checks the actual type of the data when it tries to use it.
In most cases option #1 is most probably going to take *less* memory and
be a lot more efficient than either of the other options. That's because
with option #1 the compiler can optimize the code for those types while
with the other options it can't. In fact, your code might become *smaller*
when it's optimized for each type rather than larger.
Even if it doesn't become smaller, the increase in memory usage will most
probably be less than with the other solutions. If even that's too much,
then you shouldn't be using several types in the first place, but than it's
not the templates' fault, it's your own fault for wanting to use several
types on a machine with 1 kilobyte of RAM.
> The reason C++ makes more code is that it actually does stuff like inline
> the right calls, doing type-specific generic expansions.
Inlining can actually *reduce* the size of the binary in some cases (because
of the subsequent compile-time optimizations).
--
- Warp
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 29/09/2012 08:35 PM, Warp wrote:
> Orchid Win7 v1<voi### [at] dev null> wrote:
>> Then again, this is from the same book that explains how to write
>> destructors (i.e., "don't")
>
> If C# is anything like Java in this regard, then destructors are pretty
> much useless
That was pretty much it, yes.
In particular, any objects that "this" points to might already have been
destroyed, so you can't touch those. You also have to make sure you do
/not/ cause any dead objects to become alive again by adding pointers to
them. Destructors are also run in a separate thread, so you have to be
careful about thread-safety... In short, writing a correct destructor is
a nightmare.
> In C++ destructors are useful for more than just freeing memory. For
> example they are commonly used in things like locks: If a function needs
> to use a lock, it can make a static lock instance which frees the lock
> in its destructor, and it's guaranteed to be freed immediately when the
> function is exited, regardless of how it's exited (ie. no matter where
> the 'return' sentence is, or even if it's exited by throwing an
> exception). It's handy because you don't need to any special code to
> make sure that the destructor is called when the function is exited.
Yeah, if destructors behave in a deterministic way, I'd imagine there's
a few useful things you can do with them.
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 29/09/2012 06:40 PM, Darren New wrote:
> On 9/29/2012 3:18, Orchid Win7 v1 wrote:
>> This, it is claimed, can reduce page faults, improve cache
>> performance, and so forth.
>
> The author doesn't understand the concept of "working set".
The author claims that if you never try to print anything, the code for
doing printing will never be JIT-compiled, thereby resulting in a
smaller working set.
From what you're saying, if that code is already compiled to machine
code but just never /runs/, then it won't increase the working set
anyway. (Unless it causes the running code to span more VM pages.)
>> Yeah, that's about the size of it.
>
> Nah. I'm pretty sure C# generics use the same code for each reference
> type. I.e., you might have a version for int, a version for float, and
> one version for anything descended from Object.
Sure. I meant in the C++ case.
> The reason C++ makes more code is that it actually does stuff like
> inline the right calls, doing type-specific generic expansions. C#
> doesn't do that, so I think you get far fewer versions of the code.
> Especially for generics that are anchored, i.e., that are generic over
> some specific superclass.
And Haskell generates one version of the code, and lets you request
specific versions if you want them.
Oh, wait...
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 9/29/2012 14:17, Orchid Win7 v1 wrote:
> In short, writing a correct destructor is a nightmare.
Not really. Writing a complicated class that also has a destructor is a
nightmare. If you have a destructor on a class that has anything more than a
reference to the object you want to destruct, you're probably doing it
wrong. If you're doing more than wrapping the non-GCed resource, you're
doing too much work in that class.
That said, no, destructors aren't especially useful except for logging that
you you forgot to explicitly free your resource. :-)
>> In C++ destructors are useful for more than just freeing memory. For
>> example they are commonly used in things like locks:
Personally, I find that sort of thing terribly confusing. I suppose you get
used to having variable declarations actually being control flow statements,
but I find it rather bizarre.
>> It's handy because you don't need to any special code to
>> make sure that the destructor is called when the function is exited.
I prefer the Sing# method, wherein if you have a variable that needs to be
tracked, and you let it go out of scope without disposing it, the code
doesn't compile. You can still get resource leaks with the C++ method, if
your structures are complicated enough.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 9/29/2012 12:44, Warp wrote:
> Darren New<dne### [at] san rr com> wrote:
>> Well, C++'s target platforms include things that C# isn't targeted at. If
>> you're trying to fit your code into a credit card terminal, you're going to
>> be worried about C++ template code bloat and the features of C#'s execution
>> model won't even enter the conversation.
>
> "Template code bloat" is a myth with little to no actual evidence.
I said you'd be worried about it. I didn't necessarily say the worries were
justified. :-)
> If you need the same code to handle more than one type, you have to do that
> somehow. Either you duplicate the code for each type, or you use runtime
> polymorphism, or alternatively you use some really contrived code full of
> conditionals that checks the actual type of the data when it tries to use it.
I think if you have the code for several different pointer types, and you
deal with them all the same way, you might have multiple copies of the same
code getting compiled, right?
I mean, if you instantiate it over int* and float* and MyClass* and
YourClass* and ThisWidget* and ThatWidget*, you're going to wind up with six
copies of that code, right? And if your linker is stupid enough, you're
going to wind up with copies in every place you use that code.
>> The reason C++ makes more code is that it actually does stuff like inline
>> the right calls, doing type-specific generic expansions.
>
> Inlining can actually *reduce* the size of the binary in some cases (because
> of the subsequent compile-time optimizations).
I'm saying that if you have a collection class instantiated for multiple
types of pointers, C++ has no choice but to expand each, because you don't
declare the type in the template. The compiler and linker would need to be
fairly smart, I'd think, to recognize that myblah.implement(&myBlah) and
yourblah.implement(yourBlah) both generate identical code because the
"implement" function doesn't do anything type-specific with the blahs.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 9/29/2012 14:28, Orchid Win7 v1 wrote:
> From what you're saying, if that code is already compiled to machine code
> but just never /runs/, then it won't increase the working set anyway.
Right. That's what distinguishes "working set" from "code size". Why would
it have a special name?
> And Haskell generates one version of the code, and lets you request specific
> versions if you want them.
Well, all three do that, for some definition of "version" and "request" and
"specific". :-) I think you'd have to be a little more precise if you were
trying to make a point there.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Darren New <dne### [at] san rr com> wrote:
> I prefer the Sing# method, wherein if you have a variable that needs to be
> tracked, and you let it go out of scope without disposing it, the code
> doesn't compile.
Why? Since the compiler can track when the object goes out of scope, it
can just as easily automatically add the destructor call. Why burden the
programmer with that task? It doesn't make any sense.
--
- Warp
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
>> And Haskell generates one version of the code, and lets you request
>> specific versions if you want them.
>
> Well, all three do that, for some definition of "version" and "request"
> and "specific". :-) I think you'd have to be a little more precise if
> you were trying to make a point there.
Java does all polymorphism at run-time.
C++ does polymorphism at compile-time if you implement it with
templates, and at run-time if you implement it with inheritance. (I.e.,
to switch from one to the other, you have to radically restructure your
application.)
I have no idea what C# does.
Haskell does polymorphism at run-time. Unless you add a single compiler
pragma, in which case you can selectively turn on compile-time
polymorphism instead, on a per-function, per-type basis. (Usually only
for types you use heavily, or for primitive types where it's going to
allow lots of other optimisations to fire.)
And really, whether it's done at compile-time or run-time is an
implementation detail. It /ought/ to be trivial to switch from one to
the other...
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 9/29/2012 23:13, Warp wrote:
> Why?
Separate compilation combined with actual enforcement of the rules. Sing# is
designed to use the language, in part, to enforce security. You don't get
stack-smash and buffer-overrun viruses because you can't smash the stack or
overrun buffers, as an example.
> Since the compiler can track when the object goes out of scope,
But C++ can't do this, which is why you wind up with all kinds of smart
pointer types. You can make it work, but you can also get it wrong.
If you take a mutex, and pass it to a subroutine by value, you've just
screwed your lock, because you'll free the lock when you return from the
subroutine, and you'll free the already-disposed lock a second time when you
return from the call that passed it.
> it can just as easily automatically add the destructor call.
It also prevents you from calling the destructor twice.
For example, in one module, you have
void xyz(tracked one, tracked two) {
destroy one;
}
In another, separately-compiled module, you have
void pdq() {
tracked alpha = ...;
tracked beta = ...;
xyz(alpha, beta);
alpha.use();
beta.use(); // As written, this will crash.
...
}
How would you recommend the compiler deal with such a thing, if you don't
have to annotate what's destructed and what isn't?
Singularity does it by making you declare the function like this:
void xyz([claimed] tracked one, tracked two) { destroy one; }
(or some such syntax)
Then, the compiler enforces at compile time that inside the body of xyz, you
destroy item one exactly once, and that you don't destroy item two.
As another example:
void xyz(tracked one, tracked two) { destroy one; }
void abc(tracked three, tracked four) { xyz(three, four); }
void def() {
tracked five = ...;
tracked six = ...;
abc(five, six);
...
}
Where does five go out of scope? Where does six go out of scope? Five and
six are still in scope in def, but five is the same as three is the same as
one, which is destroyed in xyz. So after the call to abc, five is no longer
usable. Either that, or you have to say that xyz is incapable of actually
discarding five, which means every tracked value has to be discarded in the
same stack frame where it was created, which seems rather restrictive.
How does C++ do it? You create the file object at one stack frame, and then
you only pass it by reference down thru the stack, right? So the thing
really only gets destructed in the stack frame where you allocated it. Or
you allocate it on the heap and hope that someone eventually cleans it up
again, with smart pointers trying to automate that to some extent?
If "two" in "xyz" is declared as an "out" variable, you have to assign a new
tracked item to it before returning, and the variable can't already have a
value when you invoke xyz. If it's declared an "in out" variable (i.e., a
pass-by-reference), then you can destroy it in the body, but only if you
create a new one and assign it before you return. If it's an "in" variable
and you destroy it, you have to put that in the type signature of the
routine, so the caller knows it's gone.
The general technique is called "typestate", invented in the 80's as a
general method by Robert Strom. :-)
(Sing# does some other cool things like that, too, such as letting you take
pointers to the insides of structures on the stack, but tracking where you
assign such pointers so they never outlast the stack frame they're pointing
to. This lets you do things like handle network packet parsing with zero
copying, while avoiding the possibility of leaking pointers or smashing the
stack. It also tracks the state your communication channel state machine is
in, making sure you don't try to send a message other than the one you
declared you'd send at any given time, which eliminates a whole host of bugs
you have to kludge around in languages like Hermes and Erlang where an
unexpected packet type either gets ignored and accumulates forever in the
buffer or causes the code to do something otherwise unexpected.)
Now, granted, in Singularity, the OS is garbage-collected also, so there are
like 3 types that you actually have to destroy (shared buffers, pointers to
shared buffers, and communication channels to pass those shared buffers
over), so there's generally very little occasion to need to destroy
something. You don't destroy threads, or locks, or memory allocations, or
stuff like that. Just connections to other programs.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Darren New <dne### [at] san rr com> wrote:
> > Since the compiler can track when the object goes out of scope,
> But C++ can't do this
Of course it can. That's the very essence of RAII.
>, which is why you wind up with all kinds of smart
> pointer types. You can make it work, but you can also get it wrong.
You are confusing tracking the scope of an object with tracking references
that point to a dynamically allocated object. Those are completely different
things.
> If you take a mutex, and pass it to a subroutine by value, you've just
> screwed your lock
Which is why mutex objects cannot be passed by value (ie. they have their
copy constructors and copy assignment operators disabled).
(If you really need to pass such an object as a function parameter, then
you can do it by using *move* semantics rather than *copy* semantics.)
>, because you'll free the lock when you return from the
> subroutine, and you'll free the already-disposed lock a second time when you
> return from the call that passed it.
std::unique_ptr does not reference-count, it contains nothing more than
the pointer to the object (which itself doesn't need to contain anything
at all related to memory management), and you can give std::unique_ptrs
as function parameters (and use them eg. in data containers). You can also
eg. return one as the return value of a function. It it does not delete the
object twice.
(The reason is that it uses move semantics rather than copy semantics.)
> Singularity does it by making you declare the function like this:
> void xyz([claimed] tracked one, tracked two) { destroy one; }
> (or some such syntax)
What exactly stops the compiler from generating the destructor call
automatically? Why exactly do you have to write it explicitly?
--
- Warp
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 10/1/2012 6:29, Warp wrote:
> Darren New<dne### [at] san rr com> wrote:
>>> Since the compiler can track when the object goes out of scope,
>
>> But C++ can't do this
>
> Of course it can. That's the very essence of RAII.
Only for stuff I allocate on the stack and which I don't pass by value.
>> , which is why you wind up with all kinds of smart
>> pointer types. You can make it work, but you can also get it wrong.
>
> You are confusing tracking the scope of an object with tracking references
> that point to a dynamically allocated object. Those are completely different
> things.
I'm not confusing them at all. I'm simply pointing out that scope-tracking
doesn't work for heap-allocated objects.
>> If you take a mutex, and pass it to a subroutine by value, you've just
>> screwed your lock
>
> Which is why mutex objects cannot be passed by value (ie. they have their
> copy constructors and copy assignment operators disabled).
And you deal with mutex objects in the heap how?
> (If you really need to pass such an object as a function parameter, then
> you can do it by using *move* semantics rather than *copy* semantics.)
And when you move it back...?
> (The reason is that it uses move semantics rather than copy semantics.)
But nothing enforces that.
>> Singularity does it by making you declare the function like this:
>> void xyz([claimed] tracked one, tracked two) { destroy one; }
>> (or some such syntax)
>
> What exactly stops the compiler from generating the destructor call
> automatically? Why exactly do you have to write it explicitly?
Because you might want to destruct something somewhere other than when
returning from a block, I suppose. You don't want to destruct some object
like that when you dispose the penultimate reference, but you do want to
destruct it when you dispose the ultimate reference. You need some way of
storing it in the heap, as well as the ability to reliably pass references
to stack objects downward in the call tree but not return them upwards.
Remember that singularity supports multiple languages, too.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
Darren New <dne### [at] san rr com> wrote:
> On 10/1/2012 6:29, Warp wrote:
> > Darren New<dne### [at] san rr com> wrote:
> >>> Since the compiler can track when the object goes out of scope,
> >
> >> But C++ can't do this
> >
> > Of course it can. That's the very essence of RAII.
> Only for stuff I allocate on the stack and which I don't pass by value.
"Scope" is defined by the stack. An object created inside a scope goes out
of scope when that scope ends, of course. If you create an object
dynamically, it can outlive the scope where it was created.
If you pass the object by value, you are creating a *new* object (with its
own scope). It's not the same object. That's what "copying" means.
> I'm not confusing them at all. I'm simply pointing out that scope-tracking
> doesn't work for heap-allocated objects.
That's rather obvious, given that heap-allocated objects are not
scope-based.
> > (If you really need to pass such an object as a function parameter, then
> > you can do it by using *move* semantics rather than *copy* semantics.)
> And when you move it back...?
You don't "move it back". If you move a resource you relinquish it to
whatever you are moving it to.
> > (The reason is that it uses move semantics rather than copy semantics.)
> But nothing enforces that.
The class itself can enforce it.
> >> Singularity does it by making you declare the function like this:
> >> void xyz([claimed] tracked one, tracked two) { destroy one; }
> >> (or some such syntax)
> >
> > What exactly stops the compiler from generating the destructor call
> > automatically? Why exactly do you have to write it explicitly?
> Because you might want to destruct something somewhere other than when
> returning from a block, I suppose.
And have the possibility of dereferencing a destroyed object?
--
- Warp
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 10/2/2012 4:32, Warp wrote:
> "Scope" is defined by the stack.
Only for auto variables, yes.
> An object created inside a scope goes out
> of scope when that scope ends, of course.
Objects (i.e., values) don't have scopes. Variables have scopes. A scope is
the portion of the program code over which the name of a variable is valid.
An object doesn't go out of scope when the scope ends. The variable
referencing the object goes out of scope when the scope ends.
> If you create an object
> dynamically, it can outlive the scope where it was created.
And that's the problem.
> If you pass the object by value, you are creating a *new* object (with its
> own scope). It's not the same object. That's what "copying" means.
And that too is the problem.
>> I'm not confusing them at all. I'm simply pointing out that scope-tracking
>> doesn't work for heap-allocated objects.
>
> That's rather obvious, given that heap-allocated objects are not
> scope-based.
Well, you asked me why the compiler couldn't just add the destructor for
you. This is your answer: it's rather obvious.
>>> (If you really need to pass such an object as a function parameter, then
>>> you can do it by using *move* semantics rather than *copy* semantics.)
>
>> And when you move it back...?
>
> You don't "move it back". If you move a resource you relinquish it to
> whatever you are moving it to.
So if I want to pass an object into a function as an argument, and then
still use it when that function returns, I can't?
Also, how does the compiler know you've moved it, so you *don't* reference
it upon return?
>>> (The reason is that it uses move semantics rather than copy semantics.)
>
>> But nothing enforces that.
>
> The class itself can enforce it.
"Can enforce" is not the same as "enforce". it's not part of the semantics
of the language.
>>>> Singularity does it by making you declare the function like this:
>>>> void xyz([claimed] tracked one, tracked two) { destroy one; }
>>>> (or some such syntax)
>>>
>>> What exactly stops the compiler from generating the destructor call
>>> automatically? Why exactly do you have to write it explicitly?
>
>> Because you might want to destruct something somewhere other than when
>> returning from a block, I suppose.
>
> And have the possibility of dereferencing a destroyed object?
No. That's exactly the point of the declarations like [claim] that I showed
you. You can't dereference it after it's destroyed because the declarations
prevent you from dereferencing it after someone else has claimed it, and
destroying it claims it.
--
Darren New, San Diego CA, USA (PST)
"They're the 1-800-#-GORILA of the telecom business."
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On 28/09/2012 04:40 PM, Jim Henderson wrote:
> On Fri, 28 Sep 2012 15:51:34 +0100, Orchid Win7 v1 wrote:
>
>> Apparently the only way to do that is to download an ISO image. Which
>> means you have to burn it to a CD before you can access it. [Unless you
>> use Unix. :-P ]
>
> Daemon Tools.
Yes, apparently.
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|  |
|
 |
On Sat, 01 Dec 2012 14:16:02 +0000, Orchid Win7 v1 wrote:
> On 28/09/2012 04:40 PM, Jim Henderson wrote:
>> On Fri, 28 Sep 2012 15:51:34 +0100, Orchid Win7 v1 wrote:
>>
>>> Apparently the only way to do that is to download an ISO image. Which
>>> means you have to burn it to a CD before you can access it. [Unless
>>> you use Unix. :-P ]
>>
>> Daemon Tools.
>
> Yes, apparently.
Not just apparently - just "Yes". :)
Jim
Post a reply to this message
|
 |
|  |
|  |
|
 |
|
 |
|  |
|
 |