tomhrr / dale

Lisp-flavoured C
BSD 3-Clause "New" or "Revised" License
1.02k stars 48 forks source link

initializers and destructors #175

Closed porky11 closed 6 years ago

porky11 commented 7 years ago

Some things on them are unclear. I tried a bit and I'd like to add some documentation, but don't find, where the documentation has to be written.

To init should be added: "If init is not defined, initializing will still work without arguments" It may also be a good Idea to disallow init without arguments. This way it can be enforced to initialize the value oneself at compile time.

To setf-copy-init should be added: "By default it is defined for the type itself. When initializing with an initializer list, this copy constructor is called" This behaviour leads to some Questions: How can the default copy constructor be undefined? When defining static variables with initializer lists, they will be copied at compile time using copy constructor, else at runtime from some read-only-memory using copy-constructor? Otherwise it may be not that efficient

Then something to setf-move-init I would add: "If setf-move-init isn't defined over the type, setf-copy-init is called." So it would be enough, to define setf-move-init and init if some resources are managed automatically.

Questions about destroy and move: The only difference between rvalues and lvalues is the function selection. After using functions specialized on rvalues (rv-ref) they may be in undefined states. So using a value after calling move may cause errors. But delete is called on this value anyway. Isn't this a problem?

porky11 commented 7 years ago

Shouldn't this work?

(import cstdio)

(def x (struct intern ((a int))))

(def setf-copy-init (fn intern bool ((a (p x)) (b (p (const x))))
  (printf "self\n")
  true))

(def setf-copy-init (fn intern bool ((a (p x)) (b (p (const int)))) ;;initializers with different types
  (printf "other\n")
  true))

(def main (fn extern-c void (void)
  (let ((a x ((a 1))) ;;should print "self" in case of self type
        (b x 2)) ;;should print "other" in case of other type (int), compiliation error here
    (do))))
tomhrr commented 7 years ago

To init should be added: "If init is not defined, initializing will still work without arguments"

I don't think I follow this. What do you mean by 'initializing'?

It may also be a good Idea to disallow init without arguments. This way it can be enforced to initialize the value oneself at compile time.

By "arguments", do you mean aside from the initial value reference?

To setf-copy-init should be added: "By default it is defined for the type itself. When initializing with an initializer list, this copy constructor is called" This behaviour leads to some Questions: How can the default copy constructor be undefined?

There isn't a 'default copy constructor' as such. The behaviour in the absence of an applicable 'setf-copy-init' binding is a 'plain' copy.

When defining static variables with initializer lists, they will be copied at compile time using copy constructor, else at runtime from some read-only-memory using copy-constructor? Otherwise it may be not that efficient

The copy happens at compile time.

Then something to setf-move-init I would add: "If setf-move-init isn't defined over the type, setf-copy-init is called." So it would be enough, to define setf-move-init and init if some resources are managed automatically.

Thanks, this will be added.

Questions about destroy and move: The only difference between rvalues and lvalues is the function selection. After using functions specialized on rvalues (rv-ref) they may be in undefined states. So using a value after calling move may cause errors. But delete is called on this value anyway. Isn't this a problem?

No, because movement shouldn't leave the source object in an undefined state if that would cause problems for the associated destructor.

Shouldn't this work?

Yep, I'm not sure what's going on here. I'll update this thread once I've figured it out.

porky11 commented 7 years ago

1: By initializing I mean, that some init function is called 2: I mean a way to disallow something like this:

(def x (var linkage my-type)) ;;no "arguments" 

Then it would be possible to ensure, there is no random data inside a object, and it is with something useful. 3: But it's not documented, that plain copy is the default, if nothing is specified 4: Not for variables with linkage auto

tomhrr commented 7 years ago

By initializing I mean, that some init function is called

Do you mean that an example like the following:

(def x (var linkage my-type))

works, when my-type does not have any init functions defined over it?

I mean a way to disallow something like this:

(def x (var linkage my-type)) ;;no "arguments" 

Then it would be possible to ensure, there is no random data inside a object, and it is with something useful.

In the case above, if init is defined over a reference to my-type only, then it will be called implicitly with x as its argument.
That allows the author to ensure that the object is in a valid state when it is not explicitly initialised by the user. It would be possible to restrict this further by requiring initialisation when there is at least one init for the type, but there is no no-argument init for the type. E.g.:

(def init (fn intern bool ((val (ref my-type)) (n int))
  (setf (: (@ val) value) n)
  true))

(def main (fn extern-c int (void)
  (def x (var auto my-type)) ; Error here.
  0))

What do you think?

But it's not documented, that plain copy is the default, if nothing is specified

Ah OK, no worries, the documentation will be updated accordingly.

Not for variables with linkage auto

Is this in reference to "the copy happens at compile time", or something else?

porky11 commented 7 years ago

works, when my-type does not have any init functions defined over it?

Yes (is it possible to precieve the code tags in comments, without readding them manually?)

What do you think?

Something like this already is defined for may concept types like "Array". There mostly you first declare the variable without arguments, and then call the init-function. Do you mean when writing (def x (var auto my-type some-int)) then will call this init-function, instead of setf-copy-init? Then it may also be possible to add arbitrary number of arguments to the init functions. Then setf-copy-init and setf-move-init will only be called, if both types are the same?

Is this in reference to "the copy happens at compile time", or something else?

Yes

tomhrr commented 7 years ago

Thanks for all the comments. The documentation has now been updated to address them (https://github.com/tomhrr/dale/blob/master/doc/1-8-init-destroy.md). Please let me know if there's anything in there that you think needs to be clarified further.

(is it possible to precieve the code tags in comments, without readding them manually?)

I don't think so, but I haven't looked into this in any detail.

Something like this already is defined for may concept types like "Array". There mostly you first declare the variable without arguments, and then call the init-function. Do you mean when writing (def x (var auto my-type some-int)) then will call this init-function, instead of setf-copy-init? Then it may also be possible to add arbitrary number of arguments to the init functions. Then setf-copy-init and setf-move-init will only be called, if both types are the same?

Sorry, the comment I had previously was wrong. init is only ever defined as taking a single argument. If a variable is defined without
an initialising value, then init is called. If a variable is defined with an initialising value, then either setf-move-init or setf-copy-init is called, depending on which is applicable. The approach I gave above using init, though, can be adapted to setf-copy-init instead: under that model, if setf-copy-init is defined, but init is not, the variable would have to be defined with an initialising value. What do you think?

Is this in reference to "the copy happens at compile time", or something else?

Yes

I'm not sure I understand. The original question was about defining static variables with initialising values: in that case, the initial value is determined at compile time and stored directly against that variable, and no further copy operation happens at runtime. If the variable has automatic linkage, then that step happens at runtime.

porky11 commented 7 years ago

So you will only be able to initialize with same type, not with different types.

What do you think? I think, often you would just want to define a plain copy, which is the default. So you just redefine something, that already is possible, to disable something different seems a bit complicated, but since it is possible to define a macro for defining a copier, this is ok

tomhrr commented 6 years ago

I think, often you would just want to define a plain copy, which is the default. So you just redefine something, that already is possible, to disable something different seems a bit complicated, but since it is possible to define a macro for defining a copier, this is ok

I think you are right about this, so it's now possible instead to define a function called requires-explicit-init over the type, which will have the same effect.