ChaiScript: First impressions, questions, comments, etc


#1

Hi,
So I recently found out about ChaiScript, and subsequently spent both days this past weekend tinkering with it. I was using a checkout of the ‘develop’ branch, which I believe is the 6.0.0 development version. Anyways, I have a couple of first impressions, comments, and questions.

First off, I’ll say this this is hands-down the easiest integration into C++ I’ve ever seen for a scripting language. So kudos for that. I wish more languages were this easy to plug into C++.

C1: For a first task, I spent some time adding (reflecting?) the standard math library into ChaiScript, only to realize that someone has already done that ( https://github.com/ChaiScript/ChaiScript_Extras/tree/master/include/chaiscript/extras ). A link to additional modules on the documentation page ( http://chaiscript.com/docs.html ) would have been helpful to save me from re-inventing the wheel on that one. If it is linked somewhere, I must have missed it. No problem. I learned something about binding functions to it.

Q1: Regarding that, what’s the best way to implement/add a C/C++ function to chaiscript that operates on any numeric type? For example, if I have sin(x), I’d like to be able to have that work in chaiscript regardless of if the user passes in an unsigned int, int16_t, float, etc. For starters, I wound up just adding in the provided overloads (sin, sinl, sinf, etc) and naming them all “sin”. Though I don’t think that will allow implicit conversion from integer types. I could wrap it in a template, and then add overloads for each of the integer types, but I’m not sure if that’s the best way to do that. And there are a lot of possible permutations, when you consider signed and unsigned types and length modifiers. Since most of those functions already have corresponding C implementations, it doesn’t make sense to implement them in chaiscript itself. If there’s a recommended practice here, perhaps a FAQ on ChaiScript.com would be helpful.

C2: I saw a question on either a ticket or the discourse forum somewhere, where someone was asking for random number support. And there was subsequent discussion about adding in the C++ random number facilities, distributions, etc. One thing I really like about ChaiScript, is that out of the box, it’s fairly minimal, and doesn’t provide an excessively huge standard library. So my 2 cents on that are, if such things are added in the future, it’d be nice to put them in a separate module that could be optionally added to the interpreter by the user. I did wind up adding in random number support, but I didn’t need everything that’s in the C++11/14 random number library, and so I provided a much smaller/simpler interface than what’s in the standard. That seems fine for my needs. I definitely liked having the flexibility to be able to make my own decisions about that.

Q2: I noticed that the built-in map type can only operate with strings as the key type. I assume there’s probably a reason that’s it’s not a map of object to object. Can you enlighten me on that decision? I think it would be useful to allow other types as the key type. And, assuming that’s possible (e.g. there’s not some technical limitation), it’d be nice to have a Set type as well. How hard do you think it would be to implement these (scale of 1-10)? I’d be willing to submit patch(es) for them if they are feasible.

Q3: I also noticed that there’s an old ticket in the tracker about enumeration support. Again, I’ll put in my vote that it would be a useful thing. And I’d be willing to try my hand at implementing it, though you might have to point me in the right direction, as I’m not familiar with the code base yet. Similar question applies about difficulty level of adding such a capability.

C3: I ran a really simple script under valgrind, and hit a couple of “use of uninitialized value” messages. Those may have been fixed already with the most recent commit, but I thought I’d mention it. Basically, I created a chai script with one empty function, and then did an eval in C++ to get a std::function to it. That’s it. I poked around in the internals a bit to see if I could figure it out in short order, but the couple of ideas that I tried didn’t fix it. I can file a bug for that one if that would be useful. I’ll also check if I can reproduce it after updating to the newest version.

C4: I ran into an issue where the constructor for the ChaiScript object was private. That caught me off guard. Adding “public:” fixed it. Looks like this was recently fixed in the mainline, so my copy must have been from just before that.

Q4: I also came to the realization that ChaiScript doesn’t currently support inheritance of ChaiScript-defined classes. I did find the pseudo-inheritance sample in the samples/ directory. Is that currently the recommended way to achieve polymorphic-like behavior? Or are there other implementation patterns that people use in practice to make their objects extensible? To be honest, of everything I’ve brought up, this is the one that has me wondering if ChaiScript will be appropriate for my application or not. I’ll continue to tinker with it and see if I think I can do what I need to do. What would be the likelihood of seeing support for inheritance, interfaces, or mixins in a future version? I do realize that multiple inheritance is a really hard problem. But even single inheritance would be useful. Back to the difficulty of implementation scale, what are we talking? 10/10?

C5: I’d also be really interested to see a “development roadmap” page added to ChaiScript.com, so I have some idea of where the project is headed.

Anyways, I realize that’s a lot of stuff. I think ChaiScript is a really interesting project, and I’m glad to see that there’s active development going on. I’ve definitely had fun tinkering with it, and I think it sets a high bar for ease of scripting language integration.

Thanks!


#2

Ok, huge message. I’ll see what I can do.

  1. © Added top level menu item for ChaiScript_Extras
    (Q) You could make a C++ function that takes a Boxed_Number type then Do The Right Thing based on if it’s float or not. This will accept any numeric type. Otherwise you do like you did and provide the overloads you want just like in C++
  2. © My current plan is to keep refining the language and not adding a bunch of library stuff. I agree it would make sense to leave the random numbers to something like Extras. The only thing I plan to add still is regex support.
    (Q) std::map requires a strict < ordering. There’s simply no way to do an ordering of [unknowntype1] < [unknowntype2]. At least nothing I’ve ever come up with. It would be theoretically possible to support this with hashes and std::unordered_map but I’m still not even sure of that. So if you have any ideas…
  3. (Q) It depends on what exactly you want. If you want C++ enums available in ChaiScript, that’s been added: https://github.com/ChaiScript/ChaiScript/blob/develop/unittests/compiled_tests.cpp#L522-L578 If you want ChaiScript native enums?
    © Fixed in most recent release.
  4. © Develop branch is still in flux, but yes it’s fixed now. In my tests I was only looking at the ChaiScript_Basic interface
    (Q) Your question has at least 3 possible answers depending on what exactly you mean.
    1. C++ Base Class with ChaiScript Derived Class
    2. ChaiScript Base Class with C++ Derived Class
    3. ChaiScript Base Class with ChaiScript Derived Class
      It’s not clear to me which of these you want
  5. It’s true that there’s no roadmap published. I’ve spent the last several months just doing reorg and cleanup for C++14

-Jason


#3

Thanks for the prompt response!

2 . (Q) I’m pretty sure that Python uses a similar hashing technique under the hood. They impose the additional constraint that the object must also be immutable (so it’s hash value doesn’t change once it’s been inserted into the map/set). In Python, you can implement a hash function for a particular class type by overriding the object’s __ hash __(self) method.

I suppose such an implementation that guarantees the immutability property might be difficult (or impossible) to do with C++/ChaiScript, because you’d need to ensure that there are no directly or indirectly mutable objects inside the object to be inserted into the map/set. Just making the UnknownType const might not be enough, due to the possibility of holding a mutable member. That kind of throws a wrench in things.

So holding an arbitrary object in a map or set might be a challenging problem. What about allowing other built-in types to be key types besides just string, e.g. integer types, booleans, etc? Floating point is an odd one to consider… Python technically lets you use a floating point value as a key type in a map, or as a value in a set. Interestingly, Google Protocol Buffers don’t allow floating point as a key type in their map.

That’s an interesting topic. I’ll read up on how other languages handle it. If I find anything interesting, I’ll post links on the forum here. It seems to me like this “should” be a solvable problem, as lots of scripting languages seem to have found a way to do it.

3 . (Q) Yes, specifically I’m talking about ChaiScript native enums.

4 . (Q) [3] and [1] are primarily of interest to me, in that order. If I had to pick only one of them, I’d pick [3]. My thought on this is that re-use of code between ChaiScript-defined objects (those defined completely in ChaiScript) is somewhat limited without some mechanism like inheritance or mixins. Though it would also be really nice to allow the user to define subclasses of a C++ class, as in [1]. I suspect that [3] and [1] are probably two very different tasks.

I did spend some time since the last post poking through the ChaiScript code base, and in general, I thought it was pretty easy to follow. If I actually try to modify it, well, that will be the real test of how much of it I understand.

Thanks,

  • Brian

#4

Actually, thinking about the hashing thing a bit more… it probably isn’t strictly necessary to enforce the immutability constraint that Python does. E.g. in C++, nothing forces you to put an immutable object type as the key of a map (though I have no idea if it behaves properly if you change it after insertion). The map only cares that you have operator<( ) overridden. There are then a couple of choices for the implementation of operator<() when using hashing:

  1. Compute and cache the hash when the object is inserted; however, any changes to the object after insertion aren’t reflected in the hash. That has an unfortunate and potentially “surprising” behavior. However, it would have pretty good performance for the comparison operations.

  2. Dynamically compute the hash each time a comparison operation is needed, at the cost of performance for objects that are expensive to hash.

  3. Select one of the above as a reasonable default operation, but let the user specify that the other one should be used. The challenge here is coming up with an interface for selecting the comparator that makes sense.

In my experience, I’d say it’s probably bad practice to be changing the underlying data of an object after it’s been inserted into a map (as a key) or set. Python obviously enforces a constraint that you can’t do that.

Thoughts?

– Brian