Yin-yang puzzle in Ruby

Viewed 106

As an attempt to learn more about call/cc in general I saw How does the yin-yang puzzle work? question and this explaination about the yin-yang puzzle:

(let*
    (
        (yin (
            (lambda (cc) (display #\@) cc)
            (call/cc (lambda (c) c))
        ))
        (yang (
            (lambda (cc) (display #\*) cc)
            (call/cc (lambda (c) c))
        ))
    )
    (yin yang)
)

I probably (or probably not) understood the yin-yang puzzle concept as of by now. But I found that scheme syntax is not particularly easy to understand, and upon searching I found that ruby have Continuation module. Since ruby syntax follows procedural style, I found that it's much easier to read ruby code than scheme code. Therefore I decided to translate the puzzle to ruby version (I'm both novice at scheme and ruby):

require "continuation"

yin = (lambda do |cc|
    print "@"
    return cc
end).call(callcc {|c| c})

yang = (lambda do |cc|
    print "*"
    return cc
end).call(callcc {|c| c})

yin.call(yang)

However this version prints out @*@***********... (demo here) instead of @*@**@***@****@*****@**..., which is not what I expected.

Is my ruby version correct? If it's not correct then I don't really know what's to do from here...

1 Answers

i think your problem that yin and yang will become Continuation and they don't call together but only yang nested inside yin.

yin = (lambda do |cc|
  print "@"
  return cc # (3) return Continuation (1)
end).call(callcc {|c| c}) # (1) this params call first

# (2) lambda will call immediately -> print the first @
# so yin = Continuation (1)

yang = (lambda do |cc|
  print "*"
  return cc # (6) return Continuation (4)
end).call(callcc {|c| c}) # (4) this params call first *

# (5) lambda will call immediately -> print the first *
# so yang = Continuation (4)

yin.call(yang)

now we could interpret your code as below

yin = callcc {|c| c} # yin context
  print '@'
  yang = callcc {|cc| cc} # yang context
    print '*'
    yin.call(yang)
  # end yang context
# end yin context

as you can see, after the first time call (the second @ and *) the last line yin.call(yang) will keep call yang yang ( * yang ( * yang ( ..., if you replace by yin.call(yin), the output will be @*@*@*@*...

finally, here is my solution, the idea is that yin and yang will be nested together, the next yang will contains the prev yin + '*' and the next yin will contains the previous yang + '@'

require "continuation"

yin = lambda { |yang|
  cc = callcc { |cc| cc }
  print "@"
  yang.call(cc)
}
  
yang = lambda { |yin|
  cc = callcc { |cc| cc }
  print "*"
  yin.call(cc)
}

yin.call(yang)
Related