Search code examples
randomiolanguage

Random value bug in Io


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.


Solution

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