2

I'm writing a unit test framework that will supply random integers, booleans, characters, and strings to test functions.

Github repo: IoCheck. Code in question:

genChar := method(
    Random value(128) floor asCharacter
)

genSeq := method(gen,
    len := Random value(100) floor

    0 to(len) map(i,
        gen()
    )
)

genString := method(
    genSeq(genChar) join
)

# below always has same genChar    
"Random string: #{genString}" interpolate println


genSeq should generate a random sequence of 0 to 99 elements, using a generator function to populate the sequence. For some reason, when genChar is passed (see the genString call in example.io), genSeq returns the exact same element in all positions.

draegtun
  • 22,441
  • 5
  • 48
  • 71
mcandre
  • 22,868
  • 20
  • 88
  • 147

1 Answers1

2

The argument you pass genSeq is evaluated before calling it.

NB. Unlike languages like Python or Javascript, parenthesis aren't used to call the method and instead in Io its used to send messages to the method. Thus gen and gen() are the same because in Io methods are always called when used. You can access the method without calling it by using getSlot

NB. This link to a comment on Hacker News may help: http://news.ycombinator.com/item?id=1539431


One solution is to pass a block() (anonymous function) instead and then call it from within genSeq:

genSeq := method (gen,
    len := Random value(100) floor

    0 to(len) map(i, gen call)       // <= gen call  ie. call the block
)

genString := method (
    genSeq( block(genChar) ) join    // <= wrapped in a block()
) 

Another alternative is to pass a sequence (string) and run perform on it:

genSeq := method (gen,
    len := Random value(100) floor

    0 to(len) map(i, perform(gen))   // run string as method
)

genString := method (
    genSeq( "genChar" ) join         // method name is a sequence
)

And another alternative is to lazy evaluate the argument:

genSeq := method (                        // no defined parameters.  Lazy time!
    len := Random value(100) floor

    0 to(len) map(i, call evalArgAt(0))   // <= arg is evaluated here!
)

genString := method (
    genSeq( genChar ) join
)


BTW... to avoid control characters I also did this change (couldn't find doc for Random object but below was a random guess and it worked!).

genChar := method(
    Random value(33, 128) floor asCharacter
)
AndyG
  • 39,700
  • 8
  • 109
  • 143
draegtun
  • 22,441
  • 5
  • 48
  • 71
  • Wonderfully detailed explanation. Yep, changing everything to blocks fixed the bug. And the blocks are necessary for the forAll method, which will accept a list of generators and call them several times to create test values for a function. – mcandre Oct 12 '11 at 16:27