tomhrr / dale

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

Extensibility of type concepts (i.e the already defined concepts) #107

Open porky11 opened 7 years ago

porky11 commented 7 years ago

For example in your Array concept you check, if T implements some concept, and instantiates some other concept-macros automatically and such (https://github.com/tomhrr/dale/blob/master/modules/array.dt#L103)

Now I define additional concepts (e.g. Add, which means, + is defined over the type), create a concept-macro + for arrays of these concepts, and want them to be implemented automatically, when the Array is instantiated.

For a single concept I could just define a new concept-macro which calls (instantiate Array (force (uq T) Type)), and then does the automatic instatiations, etc.. With multiple such concepts (eg. a Sub too, similar to Add), one would have to instantiate both with forcing them to the specific concept, which is not neccessary in your non-extensible way of implementing this. Another problem, when doing so, is, that Type (refined by both concept macros) will be defined twice, which will report an error.

This can be avoided with some extra work (checking if the type exists, before defining it). So when using this way of extending, some macro would be nice, which defines a type and doesn't report an error, when it already exists (should not be difficult). And all the struct definitions will have to be updated. The same for the functions. Another possibility would be enabeling to instantiate all concepts, that are implemented of the concept exactly once, or just recursively instantiating all the concepts, which are refined by a single concept (this should be more secure). Or doing even both (just instantiating once, and only the selected concept, via force, and it's refinements, if there are multiple implemented concepts). This should also be implementable via macros, I think, but is more difficult. It seems appropriate for concepts, which define a type and functions for it, but not for concepts which just define functions for a specific type.

If you have to instantiate a forced version to every specialized concept of the array, the first approach doesn't have much benefits anymore. The simplest solution with this seems a macro, which takes a list of concepts and instantiates the type for all of them. With the second approach, this can also be done with a similar macro, by defining an anonymous (gensym) concept, which refines all the concepts in the list. and instantiating this without force. (but force may still be needed in the underlying macros)

Now, that I have written this, I think, I may be able to implement something like this myself. Some suggestions which approach seems better, would be nice. If it works and is useful, I'll send a pull-request.

tomhrr commented 7 years ago

On Fri, Sep 16, 2016 at 04:45:52PM -0700, Fabio Krapohl wrote:

...

I think I'm missing something, here. Wanting to be able to instantiate the additional macros automatically makes sense, but putting that to one side, there shouldn't be a need in the above cases to use 'force'. Nor should there be any problem with multiply-defined types. The example program at https://github.com/tomhrr/dale/blob/master/eg/github-107/github-107.dt works through the 'Add'/'Sub' use case, assuming I understand what you're proposing.

porky11 commented 7 years ago

See here https://github.com/porky11/planets/blob/master/src/sequence-math.dt#L18 I already implemented something similar here, (also for * and /), and in this comment you see another definition of the Array concept macro, which only instantiates + (or another function) if a specific concept is implemented over this type. instantiatng this concept macro would require that the Array-type already exists, which could be interpreted that it could call all concept macros that are refined by this type. So it isn't needed to instantiate specific functions if you know, they can be implemented for this type. As you know from the default implementation of array, it also instantiates some functions only if some concepts are defined over the element type

tomhrr commented 7 years ago

On Tue, Sep 27, 2016 at 09:26:01AM -0700, Fabio Krapohl wrote:

See here https://github.com/porky11/planets/blob/master/src/sequence-math.dt#L18

Thanks for this, it makes much more sense now.

The approach where the concept macro is instantiated for all of the concepts that are implemented by the argument (or arguments) makes sense to me. BasicMath in the planets project is using refinements to group a set of concepts, which is fine, but not the only way in which refinements are used: keeping that consistent will help to make things clearer. An 'instantiate-all' form would need to iterate across the concepts for each of the types, and the relevant concept macros would need to handle (as you mentioned) various structures/functions already existing, but neither of these is much of a problem. It would be nicer if there were some way to have each of the 'sub' concept macros not have to instantiate the original one too, to make it a bit cleaner, but I can't see a decent way to do that.

porky11 commented 7 years ago

It would be nicer if there were some way to have each of the 'sub' concept macros not have to instantiate the original one too, to make it a bit cleaner, but I can't see a decent way to do that.

But this is what instantiate-all would do. Or do you mean, you prefer another option for doing this? Not by using a different macro for instantiate but extending the 'sub' concepts macros. In that case, you could call something like instantiate-all inside all 'sub' concept macros, but will have to ensure, that this will only be called once. But maybe if some other idea for automatically instantiating functions, described in #86, is used, instantiate-all may not be neccessary anymore.

tomhrr commented 7 years ago

I was thinking that instantiate-all would expand to a series of instantiate calls, with each of those 'sub' calls handling the existence/non-existence of required types. That would allow instantiate-all to remain simple, at the expense of the 'sub' calls becoming more complicated.