Forum: Ruby Gecode/R - Request for syntax feedback

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Andreas L. (Guest)
on 2007-06-05 23:45
(Received via mailing list)
Hello, I'm working on Gecode/R, a Ruby interface to Gecode[0], allowing
constraint programming in Ruby. I'm currently trying to set some
goals/direction for the syntax, and would appreciate feedback from
anyone that has the interest and time. The interface is intended for
people with no previous experience of constraint programming, and should
be fairly easy for an average ruby programmer (i.e. someone familiar
with Ruby) to pick up and use.

I begin with a short introduction and example, I will then highlight
some problems and things to consider. I have formulated some actual
questions in the end of some paragraph, but those are mostly there as
aid for the reader. Feel free to disregard them and comment on any thing
that you think of, any sort of feedback is welcome.


== Introduction and example ==

Using constraint programming basically means that you model the problem
by specifying the constraints that have to hold for something to be a
solution to the problem. The underlying engine then solves the problem
by searching for solutions satisfying all the constraints. Lets take
Ruby Q. #124 (Magic Squares)[1] as an example. For something to be a
solution to that problem we require that the elements are distinct,
between 1 and n^2, and that the sum of the rows, columns and two main
diagonals are equal. So we model that with some code and then let the
underlying engine do a search.

The following is an example of how solving the problem might look.


# A naïve model of the magic square problem with square size n.
class MagicSquare < Gecode::Model
  # n is the size of the magic square.
  def initialize(n)
    # This models that all elements are in 1..(n**2) and are all
    # distinct. IntVar.matrix produces an instance of Matrix filled
    # with instances of IntVar.
    squares = IntVar.matrix(n, n, 1..(n**2))
    all_distinct squares

    # The following models the part about various sums being equal. We
    # know the magic sum (row sum) since we know the sum of all
    # elements.
    magic_sum = n*(1 + n**2) / 2
    n.times do |i|
      squares.row(i).to_a.sum == magic_sum
      squares.column(i).to_a.sum == magic_sum
    end
    squares.diagonal(0, 0).sum == magic_sum
    squares.diagonal(0, squares.column_size - 1).sum == magic_sum

    # We need to select a branching strategy, this is used when we can
    # no longer prune impossible values by deduction and have to make a
    # guess.
    branch_on squares, :variable => :min_size, :value => :min
  end
end

# A couple of utility methods for Array and Matrix to make the above a
# bit more readable.
class Array
  # Folds the elements in the array using the + method.
  def sum
    inject{ |sum, element| sum + element }
  end
end
class Matrix
  # Returns an array containing the element found in the diagonal which
  # contains the element in the specified row and column and contains no
  # element with a strictly smaller row-index. nil is returned if such a
  # diagonal does not exist.
  def diagonal(row, column)
    # Out of bounds.
    return nil unless row < row_size and column < column_size
    # Does not exist.
    return nil if row > 0 and column != 0 and column != column_size - 1

    diag_length = [row_size - row, column_size - column].min
    elements = []
    diag_length.times do |offset|
      elements << self[row + offset, column + offset]
    end
    return elements
  end
end

# Print a solution (the first we find). This assumes that the solution
# has some decent default to_s method.
puts MagicSquare.new(9).solution.to_s


With the above syntax each problem is described as a class inheriting
from some common superclass. The actual model is defined in the
constructor. Each model has a number of variables that define the space
that the engine should search through, in our case we have a n*n matrix
with integers which can take values in the range 1..n**2 . Ruby's Array
and Matrix are used as collections (so IntVar.matrix is just a
convenience method for creating an instance of Matrix and filling it
with instances of IntVar). The advantage with that is that we get all
the utility that we're used to, along with Array's special syntax for
construction.


== Issues ==

=== Describing (linear) constraints ===

In the above example we describe our constraints with lines such as
"squares.diagonal(0, 0).sum == magic_sum". In this case we define a
linear equation that has to hold. These are not lines that are evaluated
to true or false, rather they have the side effect of creating a
constraint that has to hold. The instances of IntVar define arithmetic
methods to create a temporary representation which can then be further
added on to by e.g. more use of arithmetic methods. The representations
are kept track of behind the scenes and translated to real constraints
in the end. Lets take an example:

x,y,z = IntVar.array(3, 0..9) # Three variables with domain 0..9.
x + y == z

"x + y" would evaluate to a temporary representation which is stored and
then has "== z" applied to it.

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

Fitting words other than "constrain" might be "assert", "post" and
"add". One could also go a bit further and make using such a prepended
method compulsory for consistency. I guess the real question is how
disturbing it is for an average Ruby programmer to have arithmetic
methods that have side effects. Do you see it as natural, disturbing or
something in between? Can you think of any way of making the intention
of there being a side effect clearer?

Using arithmetic and comparison methods is of course only one way of
many to describe these (linear) constraints. One drawback with it is
consistency. Since != can't be redefined to be something other than !(x
== y) inequality has to be treated differently. In this case I'm
considering using something similar to "x.not_equal(y)", i.e. just
replace "!=" with "not_equal". There are a lot of constraints, but these
linear constraints are amongst the most common. Can you think of better
ways to represent them?

=== Variable access ===

Something that the above example doesn't really bother with is some way
to allow the variables to be accessed as something other than local
variables. For instance we might want to write a better to_s method
where we need access to the variables in the squares matrix (which hold
that information).

One way that should be familiar is to have the user save squares in an
instance variable (e.g. @squares) that can then be accessed other where.
Other approaches might include trying to save all instance variables
created in the constructor that refer to instances of e.g. IntVar and
then create methods for accessing them. Yet another would be to have the
user explicitly declare variables (possibly on a class level), which
would then be accessible anywhere in the instance. What would you
suggest?

=== Describing distinct constraints ===

The "all_distinct squares" line adds a constraint that says that all
variables contained in squares have to be pairwise inequal. Other names
than "all_distinct" could be used, for example "distinct" and
"all_different" (Gecode uses "distinct"). 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?

=== 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.


== Conclusion ==

Any feedback (pointing out flaws, suggesting modifications, suggesting a
completely different syntax, anything) is appreciated. Questions about
the project, constraint programming, what has to be covered by the
syntax etc are also welcome.

There are more issues that I would like feedback on, but I figured that
it might be best to start with these and then introduce other areas as
the discussion progresses. I'm trying to collect syntax ideas on a
wiki-page[2], that page gives an overview of the majority of the areas
that the syntax will have to cover. It's more of a collection of notes
and thoughts than proposals though, so it might be hard to read but
possibly interesting to someone.


[0] http://www.gecode.org/
[1] http://www.rubyquiz.com/quiz124.html
[2] http://gecoder.lokorin.org/dev/wiki/Syntax_ideas
ara.t.howard (Guest)
on 2007-06-06 00:10
(Received via mailing list)
On Jun 5, 2007, at 1:43 PM, Andreas L. wrote:

> I begin with a short introduction and example, I will then highlight
> some problems and things to consider. I have formulated some actual
> questions in the end of some paragraph, but those are mostly there as
> aid for the reader. Feel free to disregard them and comment on any
> thing
> that you think of, any sort of feedback is welcome.
>

would you mind posting something about this on sciruby?

http://sciruby.codeforpeople.com/

??

-a
James G. (Guest)
on 2007-06-06 00:17
(Received via mailing list)
On Jun 5, 2007, at 3:08 PM, ara.t.howard wrote:

>> be fairly easy for an average ruby programmer (i.e. someone familiar
> would you mind posting something about this on sciruby?
>
> http://sciruby.codeforpeople.com/
>
> ??

Perhaps that would be a better idea after the Google SoC project is a
little farther along with definite syntax to show off?  Just a thought.

James Edward G. II
Andreas L. (Guest)
on 2007-06-06 00:53
(Received via mailing list)
ara.t.howard wrote:
>>
>
> ??
>
> -a

I can, when the project is something beyond mere plans (i.e. when it can
be used for something).
James G. (Guest)
on 2007-06-06 01:23
(Received via mailing list)
On Jun 5, 2007, at 2:43 PM, Andreas L. 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.

> One could also go a bit further and make using such a prepended
> method compulsory for consistency.

I think that's a sound strategy.  That's pretty much the point of
should/should_not in RSpec.  It gives the user a visual queue that
says something like, "We're now switching into defining constraints
mode."

> I guess the real question is how
> disturbing it is for an average Ruby programmer to have arithmetic
> methods that have side effects. Do you see it as natural,
> disturbing or
> something in between?

I don't think it's too shocking.  I think most programmers can accept
that objects can override methods.  You see that the variables are
created as IntVar objects, and it's not a far leap that IntVar
redefines some operators.

If I had to choose though, I think I prefer the RSpec-like context
changing method prefix.

> One drawback with it is consistency. Since != can't be redefined to
> be something other than !(x == y) inequality has to be treated
> differently.

I do like consistency, so, for what it's worth, I prefer equal/
not_equal since we can't have both == and !=.

> === Variable access ===

> One way that should be familiar is to have the user save squares in an
> instance variable (e.g. @squares) that can then be accessed other
> where.

This feels pretty natural to me.  It works on the knowledge we
already have.

> Yet another would be to have the user explicitly declare variables
> (possibly on a class level), which would then be accessible
> anywhere in the instance. What would you suggest?

This could be another interesting option.  See my comment below about
a DSL...

> === 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)

> 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.

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.

James Edward G. II
Andreas L. (Guest)
on 2007-06-06 14:22
(Received via mailing list)
James Edward G. II wrote:
>> following.
>> "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)

>
> As an idea, again using RSpec for inspiration:
>
>   constrain distinct(some_enum)
>

Sounds good to me.

>> something other than throwing it all into a constructor.
> 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.

>
Aye, thanks for the idea.
Andreas L. (Guest)
on 2007-06-06 16:43
(Received via mailing list)
Andreas L. wrote:
> constraint which basically says that x must be in a given range.
> constrain (1..17).must_not(include(x))
>

Not necessarily. It could also be written as

constrain x.must(be_in(1..17))
constrain x.must_not(be_in(1..17))

Which is both consistent and reads well (possibly apart from the
parenthesis).

Another way of making it clear that we're specifying constraints could
be to place them all in a block. E.g. something like the following.

in_a_solution do
  (x + y).must == z
  x.must > z
  y.must_not be_in(1..4)
  distinct x,y,z
end

Possibly also sending the variables through the method with the block
(which would clearly declare which variables that constitute a
solution).

in_a_solution_with(x,y,z) do |x,y,z|
  (x + y).must == z
  x.must > z
  y.must_not be_in(1..4)
  distinct x,y,z
end
James G. (Guest)
on 2007-06-06 17:02
(Received via mailing list)
On Jun 6, 2007, at 5:21 AM, Andreas L. wrote:

>>> intention better. For instance we could allow people to write the
>>
> "constrain" is "constrain_negation", which is probably far from
> perfect.
>
> constrain_negation x + y == z

Yuck.  And I mean that in the nicest possible way.  ;)

> 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

That's not too bad.  I think my biggest complaint there is that it
makes the flow of the line a little hard to follow.  Perhaps must/
must_not could replace constrain altogether though:

must     x + y == z
must_not x + y == z

That doesn't read too well though, does it?

> 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)

This seems like a very viable solution to me.  If it's possible to
just negate all of the tests, that should work:

equal/not_equal
in/not_in
includes/does_not_include

>>> the example)
>> declaration.
>
>     variable :squares, :int_matrix, n, n, 1..(n**2)
>     squares.row(0)
>     ...
>   end
> end

I think this makes it more complicated to support custom data
structures.  What if I want a MyUniqueDataStructure full of IntVars,
for example?

Beyond that, it doesn't seem in anyway superior to:

   class MagicSquare < Gecode::Model
     def initialize(n)
       @squares = ...
     end
     attr_reader :squares
   end

I think it's probably best not to override core Rubyisms if we can't
significantly add to them.

James Edward G. II
James G. (Guest)
on 2007-06-06 17:09
(Received via mailing list)
On Jun 6, 2007, at 7:42 AM, Andreas L. wrote:

> Possibly also sending the variables through the method with the block
> (which would clearly declare which variables that constitute a
> solution).
>
> in_a_solution_with(x,y,z) do |x,y,z|
>   (x + y).must == z
>   x.must > z
>   y.must_not be_in(1..4)
>   distinct x,y,z
> end

This could possibly be an option.  You could choose to write:

constrain ...
constrain ...
constrain ...

or:

constrain do
   ...
   ...
   ...
end

James Edward G. II
Andreas L. (Guest)
on 2007-06-06 18:56
(Received via mailing list)
James Edward G. II wrote:
>
> equal/not_equal
> in/not_in
> includes/does_not_include
>

Yes, that could work. I was probably locking myself in a bit too much
trying to make everything read as must/must_not. I will sketch how other
constraints could be expressed on a similar form to see if there are any
consistency problems.

There doesn't exist support for negating all constraints. One example is
a sorting constraint, which basically says that some variables, when
sorted, are equal to some other variables. Which could be expressed
something like:

constrain xs.sorted.equal(ys)
or
constrain sorted(xs).equal(ys)
or
constrain sort(xs).equal(ys)

The last couple avoid having to modify Enumerable. Using this form I
prefer the second version.

The point however is that there's no simple way to negate that
constraint using Gecode, beyond hacking together several constraints
that express that a single element is out of order and saying that at
least one of them must hold. To me it would seem accepatable to simply
throw an exception in those cases (or translate it to something
inefficient behind the scenes, but I would rather not do that).

>>>>
>>>   variable :some_name, :int, accepted_range_of_values
>>
> example?
>

True, I didn't think about custom data structures.
Andreas L. (Guest)
on 2007-06-06 21:36
(Received via mailing list)
Andreas L. wrote:
>>
>> equal/not_equal
>> in/not_in
>> includes/does_not_include
>>
>
> Yes, that could work. I was probably locking myself in a bit too much
> trying to make everything read as must/must_not. I will sketch how other
> constraints could be expressed on a similar form to see if there are any
> consistency problems.
>

If I understood the suggestion of the syntax correctly then constraints
would basically be given on the form "constrain
variable.predicate(argument)" when variable is a single (finite-domain)
variable and "constrain function(variable).predicate(argument)" when
variable is something enumerable.

I have written down some examples of how constraints could look with
that syntax at http://gecoder.lokorin.org/dev/wiki/Syntax_test . There
are some constraints there which I couldn't get especially readable or
self-evident. I will go through them below.


== Element constraints ==

This is basically the array access of constraints. It takes an array of
constant integers, selects the i:th one where i is decided by a
variable, and constrains that variable to be equal to another variable.
This can typically be used to convert e.g. an identifier to a quantity
of some sort, for instance the price of a fruit.

A short example:

fruit_selection = IntVar.new(0..3) # We can pick one of four fruits.
prices = [45, 60, 764, 45] # The prices of each fruit.
price_to_pay = IntVar.new((prices.min)..(prices.max))
constrain prices[fruit_selection].equal(price_to_pay)
constrain price_to_pay > 500

The above example will force fruit_selection to equal 2 (no branching
required) since it's the only fruit with a price > 500.

I would imagine the above syntax of "constrain array[x].equal(y)" to be
easily understandable, but it would require modification to Enumerable
to define/wrap []. Something more on the form of the rest of the syntax
would probably be

constrain element(array, x).equal(y)

It just doesn't read as well to me. Maybe there's a better way of
representing constraints where the function has to take multiple
arguments, such as returning an helper with only one method, i.e. as
follows.

constrain element(x).in(array).equal(y)


== Channel constraints ==

This is a constraint that I have little idea of how to produce something
readable for. The constraint links two enumerables with variables to
each other so that the xs_i = j <=> ys_j = i forall i where xs and ys
are enumerables.

It's used to represent a model from two different viewpoints. Lets say
that you have a problem involving a sequence of length n with numbers in
0..(n-1) that all have to be distinct (for instance magic sequence).
Naturally you might model this as an array where element i is the value
at position i (so if you print the array you get the actual sequence).
You could also model it as an array where element i is the position of
value i in the sequence. These are two different viewpoints of the same
problem. Below what the two arrays might contain when the problem is
solved.

First viewpoint: xs = [2,0,1,3] (the actual sequence)
Second viewpoint: ys = [2,1,0,3] (the positions of the different
numbers, e.g. the position of 0 in xs is 2, hence ys[0] == 2)

The important part is that some constraints might be easier to model
using the first viewpoint and others might be easier in the second one.
The channel constraint is used to give the person modeling access to
both viewpoints at the same time. The second viewpoint can be
constructed from the first one by using the channel constraint. The best
 syntax I can think of for it is.

constrain channel(xs, ys)

That is probably because I can't think of any common concept that
conveys the intention in a few words.


== Sort constraints ==

There's a special form of sort constraint that operates on three
enumerables: xs, ys and zs. What it does is that it constrains e.g. xs
to be ys sorted using the positions specified in zs. Once again it's the
problem of conveying a function with multiple arguments. Using the
suggestion for element constraints this could become something like

constrain sort(xs).with_indices(zs).equal(ys)

I'm not sure how readable that is.


== Cardinality constraints ==

This is a fairly straightforward constraint, it counts the number of
occurrences in an array. For instance I might specify that an array of
variables must contain 3 instances of 5 (3 and 5 can also be replaced by
variables).

Again it's multiple arguments for a function. Maybe it's more readable
as the following.

constrain number_of(y).in(xs).equal(z)


== Beyond ==

There are more constraints to worry about, but the ones in the syntax
test page represents the commonly used ones for finite domain integers
and booleans. Hence it's probably best to hammer out a good syntax for
them and then adapt it for the other constraints and set variables.
James G. (Guest)
on 2007-06-07 02:02
(Received via mailing list)
On Jun 6, 2007, at 12:35 PM, Andreas L. wrote:

> are some constraints there which I couldn't get especially readable or
> self-evident. I will go through them below.

I'm liking this direction, for what it's worth. I'm worried about the
ones that require changes to core classes though and I think we
generally want to avoid that.  That may be an argument for the
earlier proposed variable(), since it could wrap the data structures
in proxy objects supporting the extra calls.

Random thoughts from that page:

* We probably shouldn't use sort(), because a class my inherit a sort
() from Enumerable.
* Could all_equal() be same()?  I was just wondering if that paired
better with distinct().  I don't hate all_equal() though.  Just a
thought.

> A short example:
>
> fruit_selection = IntVar.new(0..3) # We can pick one of four fruits.
> prices = [45, 60, 764, 45] # The prices of each fruit.
> price_to_pay = IntVar.new((prices.min)..(prices.max))

Could this line also be:

price_to_pay = IntVar.new(*prices)

?  Just curious as it made more sense to me that way.

> constrain prices[fruit_selection].equal(price_to_pay)
> constrain price_to_pay > 500
>
> The above example will force fruit_selection to equal 2 (no branching
> required) since it's the only fruit with a price > 500.
>
> I would imagine the above syntax of "constrain array[x].equal(y)"
> to be
> easily understandable, but it would require modification to Enumerable
> to define/wrap [].

The []() method is defined in Array, actually.

> constrain element(x).in(array).equal(y)
I would say avoiding core class modification is important.  If that
means we need element(), that's probably just a price we need to pay,
in my opinion.

See my earlier comment about proxy objects for another option though.

> == Channel constraints ==


> The best syntax I can think of for it is.
>
> constrain channel(xs, ys)
>
> That is probably because I can't think of any common concept that
> conveys the intention in a few words.

Looks reasonable to me, at least as far as I understood this one.  ;)

>
> I'm not sure how readable that is.

What about taking a page from the Rails book and doing something like:

constrain sorted(xs, :indices => zs).equal(ys)

?

>
> constrain number_of(y).in(xs).equal(z)

Here again parameters seem like they might be OK.  What's nice is
that we can get more variations this way.  For example count(enum)
could count all elements while count(enum, :element => y) would be
just the y elements in enum.

James Edward G. II
Andreas L. (Guest)
on 2007-06-07 11:31
(Received via mailing list)
James Edward G. II wrote:
>> are some constraints there which I couldn't get especially readable or
> * We probably shouldn't use sort(), because a class my inherit a sort()
> from Enumerable.

True, I have switched to sorted.

> * Could all_equal() be same()?  I was just wondering if that paired
> better with distinct().  I don't hate all_equal() though.  Just a thought.
>

Certainly, I have updated the page with this suggestion and all the
others in your mail.

>> fruit_selection = IntVar.new(0..3) # We can pick one of four fruits.
>> prices = [45, 60, 764, 45] # The prices of each fruit.
>> price_to_pay = IntVar.new((prices.min)..(prices.max))
>
> Could this line also be:
>
> price_to_pay = IntVar.new(*prices)
>
> ?  Just curious as it made more sense to me that way.
>

Yes.

>> constrain element(x).in(array).equal(y)
>
> I would say avoiding core class modification is important.  If that
> means we need element(), that's probably just a price we need to pay, in
> my opinion.
>
> See my earlier comment about proxy objects for another option though.
>

I will write up some similar syntax examples with proxy objects to see
how big the difference is.

> Looks reasonable to me, at least as far as I understood this one.  ;)
Actually I made a mistake in the examples of the two different
viewpoints which might have confused a bit. It should be the following.

First viewpoint: xs = [2,0,1,3] (the actual sequence)
Second viewpoint: ys = [1,2,0,3] (the positions of the different
numbers, e.g. the position of 0 in xs is 1, hence ys[0] == 1)

>>
>> I'm not sure how readable that is.
>
> What about taking a page from the Rails book and doing something like:
>
> constrain sorted(xs, :indices => zs).equal(ys)
>
> ?

Yes, especially since indices is optional.

>>
>> constrain number_of(y).in(xs).equal(z)
>
> Here again parameters seem like they might be OK.  What's nice is that
> we can get more variations this way.  For example count(enum) could
> count all elements while count(enum, :element => y) would be just the y
> elements in enum.
>

I'm not sure if count(enum) would be used since it's basically the same
as counting the number of elements in enum and constraining z to be
equal to it (and since you constructed the enum you already know the
size and could thereby replace z with that constant). But the form with
an hash option is more readable than the form with just two arguments.
Andreas L. (Guest)
on 2007-06-07 12:59
(Received via mailing list)
Andreas L. wrote:
>>> follows.
> how big the difference is.
>

Examples: http://gecoder.lokorin.org/dev/wiki/Syntax_test#Pr...

I have assumed that it's in this case okay to shadow methods such as
#sort since if the enum is passed to a wrapping method then it should
only be used with finite-domain variables, which do not have any defined
way of comparison (similarly for min and max).
James G. (Guest)
on 2007-06-07 16:55
(Received via mailing list)
On Jun 7, 2007, at 3:58 AM, Andreas L. wrote:

>>>> arguments, such as returning an helper with only one method,
>>> though.
>>>
>>
>> I will write up some similar syntax examples with proxy objects to
>> see
>> how big the difference is.
>>
>
> Examples: http://gecoder.lokorin.org/dev/wiki/
> Syntax_test#Proxy_objects

That page talks about how we loose some convenience in that we need
to now wrap simple IntVars.  I'm not sure that's 100% necessary
though.  If we built the class, we should be able to count on it
having what we need.  The wrapper could just be for Enumerables and/
or custom data structures, I think.  Double-check my logic there though.

James Edward G. II
Andreas L. (Guest)
on 2007-06-07 17:36
(Received via mailing list)
James Edward G. II wrote:
>>>>> It just doesn't read as well to me. Maybe there's a better way of
>>>> See my earlier comment about proxy objects for another option though.
> we built the class, we should be able to count on it having what we
> need.  The wrapper could just be for Enumerables and/or custom data
> structures, I think.  Double-check my logic there though.
>

Your conclusion is correct. But my intent with the text is that we have
to wrap enumerables before use, I'm not sure how to reach the other
interpretation.
James G. (Guest)
on 2007-06-07 18:07
(Received via mailing list)
On Jun 7, 2007, at 8:35 AM, Andreas L. wrote:

>>>>>>
>>>>> pay, in
>>>
>>
>
> Your conclusion is correct. But my intent with the text is that we
> have
> to wrap enumerables before use, I'm not sure how to reach the other
> interpretation.

Oh yes, I misread.  Sorry.

I'm not giddy with the idea either.  Maybe we're getting too
complicated here.  Let's back up.

Our current pattern is:

   constrain function(...).predicate(...)

First of all, can't function() always return the needed proxy object,
without an need to wrap?  Or is the issue when we don't use the
function()?

If we want to get rid of the need to wrap, we need to eliminate the
calls on random objects.  That's not so bad, because we really want
or constraint builders worrying about the details for us.

Maybe the issue is that I led us down the wrong path with my RSpec
suggestions.  What if we tried something more like Test::Unit's
assertions.  I'm now thinking of a pattern like:

   constrain_...(args, hash_style_options)

Translating the examples I come up with:

   constrain_equal     x, y
   constrain_not_equal x, y
   constrain_in        x, enum
   constrain_not_in    x, enum
   constrain_same      enum
   constrain_distinct  enum

The operators are a little clumsier.  We could do constrain_less_than
(), constrain_greater_than(), etc.  This might be a better option
though:

   constrain_relationship x, :>, y

The symbol could be replaced with the other logical comparisons as
needed.

For things like sorted(), I'm wondering if we could add that as an
option:

   constrain_equal enum, other_enum, :sorted => true

What do we think about this direction, on the whole?

James Edward G. II
Andreas L. (Guest)
on 2007-06-07 18:11
(Received via mailing list)
James Edward G. II wrote:
>>>> instinctively think that it's something that does not have a side
>>>   some_obj.should_not equal(whatever)
>>
> That's not too bad.  I think my biggest complaint there is that it makes
> the flow of the line a little hard to follow.  Perhaps must/must_not
> could replace constrain altogether though:
>
> must     x + y == z
> must_not x + y == z
>
> That doesn't read too well though, does it?
>

I think that must/must_not would make a fairly interesting syntax in the
sense that it reads very well. Therefore I have tried some examples at
http://gecoder.lokorin.org/dev/wiki/Syntax_test#mu... .

They probably require skipping "constrain" though and replacing it with
something block-like, it just doesn't read right otherwise.
James G. (Guest)
on 2007-06-07 18:27
(Received via mailing list)
On Jun 7, 2007, at 9:10 AM, Andreas L. wrote:

>>>>> A problem with the above is that people might look at it and
>>>>
>>>>
>>> RSpec's
>> must     x + y == z
> They probably require skipping "constrain" though and replacing it
> with
> something block-like, it just doesn't read right otherwise.

I like how this reads in the early examples and worry about that
amount of core hacking we have to do to support the later examples:

   whatever.with_offsets   ...
   whatever.sorted         ...
   whatever.sorted_with    ...
   whatever.occurrences_of ...
   whatever.any            ...
   whatever.all            ...
   whatever.implies        ...
   whatever.equivalent_to  ...

James Edward G. II
Andreas L. (Guest)
on 2007-06-07 18:35
(Received via mailing list)
James Edward G. II wrote:
>>>
>> I think that must/must_not would make a fairly interesting syntax in the
>   whatever.sorted         ...
>   whatever.sorted_with    ...
>   whatever.occurrences_of ...
>   whatever.any            ...
>   whatever.all            ...
>   whatever.implies        ...
>   whatever.equivalent_to  ...
>

Unless the enumerables are wrapped before use as in the other example
(or the variables are sent through as parameters alongs with the block
as in some earlier example).
Andreas L. (Guest)
on 2007-06-07 19:08
(Received via mailing list)
James Edward G. II wrote:
> Our current pattern is:
>
>   constrain function(...).predicate(...)
>
> First of all, can't function() always return the needed proxy object,
> without an need to wrap?  Or is the issue when we don't use the function()?
>

Yes, wrapping was to use

  constrain variable.relation(arguments)

consistently for everything (the examples for that are under the header
"Proxy objects").

> Translating the examples I come up with:
>
>   constrain_relationship x, :>, y
>
> The symbol could be replaced with the other logical comparisons as needed.
>
> For things like sorted(), I'm wondering if we could add that as an option:
>
>   constrain_equal enum, other_enum, :sorted => true
>
> What do we think about this direction, on the whole?
>

I have added that syntax to the examples to get a feel for it:
http://gecoder.lokorin.org/dev/wiki/Syntax_test#co...

It's a bit bulkier at times, but is probably more conventional (possibly
easier to get to grips with?). My primary concern with it is probably
things like sort constraints where it isn't necessarily immediately
clear which of the enumerables that will be sorted (i.e. it forces one
to look into the API documentation). Those constraints can probably be
eliminated/mitigated with some other choice of words or option names
though. I will see what it leads to.
James G. (Guest)
on 2007-06-07 19:20
(Received via mailing list)
On Jun 7, 2007, at 10:07 AM, Andreas L. wrote:

> I have added that syntax to the examples to get a feel for it:
> http://gecoder.lokorin.org/dev/wiki/Syntax_test#constrain_.
> 2A_arguments
>
> It's a bit bulkier at times, but is probably more conventional
> (possibly
> easier to get to grips with?).

I agree that it lacks the sex appeal of the RSpec approach.  However,
it does allow us to avoid wrapping all variables and core class
hacking, which I consider big wins.  I would be willing to pay the
price.

James Edward G. II
Andreas L. (Guest)
on 2007-06-08 12:19
(Received via mailing list)
James Edward G. II wrote:
> which I consider big wins.  I would be willing to pay the price.
>

I'm not really sure that the cost compared to the others is that much
lower. The following are the changes necessary to make the others work
as far as I'm aware, please correct me if I'm missing something.

* Custom enums with variables have to be wrapped before use (arrays and
matrices created through the convenience methods are wrapped there).
* It becomes harder to create temporary arrays of variables, e.g. [x,y].
Instead the user has to use variable([x,y]) or something similar (or
possibly IntVar.array(x,y) if that's better).

Beyond that I can not see anything that requires wrapping or altering
core classes (or any other non-gecode classes for that matter).

What I see as a bigger problem of must/must_not is where to fit the
constraint options. It might be necessary to change it around a bit to

variable.<must|must_not> relation(argument)

or something similar for that to work.
James G. (Guest)
on 2007-06-08 16:24
(Received via mailing list)
On Jun 8, 2007, at 3:18 AM, Andreas L. wrote:

>>
> * Custom enums with variables have to be wrapped before use (arrays
> and
> matrices created through the convenience methods are wrapped there).
> * It becomes harder to create temporary arrays of variables, e.g.
> [x,y].
> Instead the user has to use variable([x,y]) or something similar (or
> possibly IntVar.array(x,y) if that's better).
>
> Beyond that I can not see anything that requires wrapping or altering
> core classes (or any other non-gecode classes for that matter).

You are probably right.

If we go with the wrapping approach, I just think the most important
thing is to make sure we are careful to support custom data
structures as painlessly as possible.

James Edward G. II
This topic is locked and can not be replied to.