Andreas Launila
6/6/2007 10:22:00 AM
James Edward Gray II wrote:
> On Jun 5, 2007, at 2:43 PM, Andreas Launila wrote:
>
>> == Issues ==
>>
>> === Describing (linear) constraints ===
>
>> A problem with the above is that people might look at it and
>> instinctively think that it's something that does not have a side
>> effect. Therefore prepending some sort of method might convey that
>> intention better. For instance we could allow people to write the
>> following.
>>
>> constrain x + y == z
>
> RSpec works a lot like this. It has you define expectations with syntax
> like:
>
> some_obj.should equal(whatever)
> some_obj.should_not equal(whatever)
>
>> Fitting words other than "constrain" might be "assert", "post" and
>> "add".
>
> I like the word "constrain." If you go the RSpec route though, you will
> probably also want a negative form and I don't know what that would be.
>
That would be a nice solution. The best word I can think of for
"constrain" is "constrain_negation", which is probably far from perfect.
constrain_negation x + y == z
or maybe
constrain (x + y == z).negate
Another variation would be to use a combination of constrain and RSpec's
should/should_not.
constrain (x + y).must == z
constrain (x + y).must_not == z
It might chop up the constraint a bit, but it reads well. One could
possibly also drop the constraint part. It would be nicer with a
negative form of constrain though (or some other fitting word) since it
would make it easier to be consistent. As an example there's a domain
constraint which basically says that x must be in a given range.
Negating the constraint is possible and might be written as follows.
constrain x.in(1..17)
constrain x.not_in(1..17)
The above takes a different form than the linear constraints. To make it
consistent with the mixed variation of linear constraint syntax one
could rewrite it as.
constrain (1..17).must(include(x))
constrain (1..17).must_not(include(x))
But that feels a bit clunky and requires changes to Range.
With a negative form of constraint the constraints would look rather
good while being consistent.
constrain x + y == z
constrain_negation x + y == z
constrain x.in(1..17)
constrain_negation x.in(1..17)
>> === Describing distinct constraints ===
>
>> Other ways of specifying the constraint could also be imagined, such
>> as "squares.all_distinct". The latter could make things a bit more
>> consistent by allowing a method to be prepended, such as "constrain
>> squares.all_distinct" (assuming such a prepended method is used for
>> the linear constraints). What feels more natural/readable?
>
> I worry that the syntax purposed above requires you to add some methods
> to Array and/or Matrix. This seems like a slope you want to avoid.
>
> As an idea, again using RSpec for inspiration:
>
> constrain distinct(some_enum)
>
Sounds good to me.
>> === Overall syntax ===
>>
>> Every model has at least three parts:
>> * Declaration of variables (in the example that would be "squares = ...")
>> * Definition of constraints (all the lines from "all_distinct ..." down
>> to "squares.diagonal..." in the example).
>> * Selection of branching strategy (the line starting with "branch_on" in
>> the example)
>>
>> Maybe that could be used to produce some other overall syntax? E.g.
>> something other than throwing it all into a constructor.
>
> Reading this definitely made me wonder over a DSL. That's pretty much
> what you have with the class system though. If constrain() becomes the
> constraint definer and branch_on() represents the branching strategy,
> the only thing you haven't really wrapped is the variable declaration.
>
> I don't know what that would look like, but maybe something like:
>
> variable :some_name, :int, accepted_range_of_values
>
> Then I guess you could later refer to them by name. Of course, that's
> just not as convenient when it comes to accessing them. Iterating over
> rows and columns shows this well.
I don't understand the part about not being as convenient to access. I'm
assuming that one could define matrices just as well as single variables
with such a syntax, which would basically give the same level of
convenience. An example to see if I understood you correctly (possibly
with some keys for the parameters beyond the name and type):
class MagicSquare < Gecode::Model
def initialize(n)
variable :squares, :int_matrix, n, n, 1..(n**2)
squares.row(0)
...
end
end
I mentioned declaring these kinds of variables on a class level before,
but I see now that doing so would make it hard to initialize the matrix
size in the example. One could however change that part so that the
problem parameters are used to create the class rather than instances of
the class.
>
> Whatever way you go with this though, you may want to consider providing
> a DSL wrapper for your classes, something like:
>
> magic_square = problem do |model|
> # ...
> end
>
> That just might be nice when you don't need to define a full class.
>
>
Aye, thanks for the idea.
--
Andreas Launila