lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Wed, Jun 18, 2014 at 1:05 PM, Coda Highland <chighland@gmail.com> wrote:
On Wed, Jun 18, 2014 at 11:03 AM, steve donovan
<steve.j.donovan@gmail.com> wrote:
> To stick to the original API is missing an opportunity to make that
> API better and more accessible to others. It's a common strategy to do
> a minimal binding (or use FFI) and then build something more elegant
> on top of that.

The context of the discussion IS said minimal binding to build on top of.

I will point out that such syntactic points as multiple return values
and error handling are also the kinds of changes I'd do; I'd
overlooked those in my discussion because usually I'm working with
stuff that's already object-oriented instead of idiomatic C and
therefore those considerations aren't nearly as necessary.

/s/ Adam


I used to prefer creating an idiomatic binding, but I've found much greater power and flexibility in writing a binding directly. A binding is a translation, so the original source should be respected as much as possible. This strictness gives people the freedom to use the original documentation (as was mentioned) and forget that they're using a binding much at all.

Adding defaults, changing naming conventions, or collapsing C methods into a single overloaded Lua method can make things look more like Lua, but it's unlikely others will appreciate the difference. It will also cast doubt on other API methods that have been introduced, as the programmer is forced to ask "Will this method behave like the original?"

My rule is that the binding should require almost no added documentation:

* Only documented APIs should be exported.
* Names and capitialization should not be modified unless required (a method name like "end" would be untenable)
* Namespaces should not be modified. For instance, do *not* change glClear to gl.clear or QQuaternion to Qt.Quaternion.
* Parameter lists should not be modified: do not add defaults unless the API supports them. In those cases, still defer to the C API, rather than inlining the default value. The only exception is when an addition would be transparent to the original API.
* Constructible C++ objects and C structs should map to a table with a constructor method named 'new' (or whatever is idiomatic for your binding).
* Multiple return values should be returned to Lua directly
* Extra and reserved arguments should be preserved, not elided. (e.g. void* pointers that are provided for future growth)
* Enum values and constants should not be modified or converted in any unnecessary way.
* API bindings should be as complete as possible. Do not omit unsafe methods or low-level behavior that "no one would ever need." If this must be done, the method should be implemented to throw an error on use.
* A require statement should map to an #include, where possible. This can create a lot of files, but it keeps things orderly.
* Do not introduce extra objects or helpers in your binding. These can introduce confusion until the programmer realizes it's not a binded class, but a personal addition.

A low-level binding should not try to give the same guarantees as Lua in terms of safety. It's tempting to add a bunch of error conditionals, but these are painful to do in practice and not likely to be complete, so just trust the programmer to use it well.

For example, here's a C++ quaternion class that I recently wrote a direct binding for.

https://github.com/dafrito/lua-cxx/blob/master/src/Qt/QQuaternion.cpp

This would produce an API identical to http://qt-project.org/doc/qt-5/qquaternion.html

require luacxx.QQuaternion
require luacxx.QVector3D

local quat = QQuaternion:new(5, QVector3D:new(2, 3, 4));
quat:normalize();
print(quat);

There are a few stub methods in this class, but since I adhere strictly to the API, it will be trivial for me or someone else to fill it in using the original documentation once it's needed. This would not be nearly as easy if the binding had my own idioms covering it.

Long story short, if you're writing a binding, I recommend to keep it strict. If the API is ugly or untenable in Lua, then there are bigger issues and a wrapper should be created instead.

-- 
Aaron Faanes <dafrito@gmail.com>


On Wed, Jun 18, 2014 at 1:38 PM, Andrew Starks <andrew.starks@trms.com> wrote:


On Wednesday, June 18, 2014, Coda Highland <chighland@gmail.com> wrote:
On Wed, Jun 18, 2014 at 11:03 AM, steve donovan
<steve.j.donovan@gmail.com> wrote:
> To stick to the original API is missing an opportunity to make that
> API better and more accessible to others. It's a common strategy to do
> a minimal binding (or use FFI) and then build something more elegant
> on top of that.

The context of the discussion IS said minimal binding to build on top of.

I will point out that such syntactic points as multiple return values
and error handling are also the kinds of changes I'd do; I'd
overlooked those in my discussion because usually I'm working with
stuff that's already object-oriented instead of idiomatic C and
therefore those considerations aren't nearly as necessary.

/s/ Adam


Thank you. This was the heart of the question. We're writing a very Lua-esque API and exposing the "core" object inside. Our goal was: as little C as possible. 

I wanted to know if others were writing the "core" layer so that all of the function arguments and return values matched, whenever possible, with the idea being that *any* inconsistency would be a burden. For example, accepting "0" from the C function, when the Lua equivalent would return something "false-y" 

It sounds like doing the easy things that don't create a lot of C code is more common. 

-Andrew



--
Aaron Faanes <dafrito@gmail.com>