How to do “late” string interpolation in Ruby

前端 未结 6 873
再見小時候
再見小時候 2021-01-01 10:55
>> string = \'#{var}\'
=> \"\\#{var}\"

>> proc = Proc.new { |var| string }
=> #

>> proc.call(123)
=> \"\\         


        
6条回答
  •  说谎
    说谎 (楼主)
    2021-01-01 11:32

    To clarify, those looking for the answer to this question's title (how to do late string interpolation) should understand that there is no such thing as late string interpolation.

    The poster of this question didn't actually care about string interpolation, per se, just formatting. For those that also don't care about the distinction between string interpolation and string formatting in Ruby, many of the answers here are fine.

    If you don't know whether or not you care, there are plenty of responses to stackoverflow questions that explain the nuanced differences -- here's one more explanation:

    In general, string interpolation is compile-time syntactic sugar: the format string is "compiled" exactly once (when generating byte code). With string formatting, the format string is re-parsed on every invocation (see Kernel::sprintf).

    To see this, consider this program:

    x=1
    s="#{x}"
    

    The RubyVM compiles it to bytecode that includes a hard reference to x (see instruction 0005):

    $ ruby --dump=insns <@-:1 (1,0)-(2,8)> (catch: FALSE)
    local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
    [ 2] x@0        [ 1] s@1
    0000 putobject_INT2FIX_1_                                             (   1)[Li]
    0001 setlocal_WC_0                x@0
    0003 putobject                    ""                                  (   2)[Li]
    0005 getlocal_WC_0                x@0
    0007 dup
    0008 checktype                    T_STRING
    0010 branchif                     17
    0012 dup
    0013 opt_send_without_block       , 
    0016 tostring
    0017 concatstrings                2
    0019 dup
    0020 setlocal_WC_0                s@1
    0022 leave
    

    Note that the interpolated string does not appear, as it has been compiled to one call to x.to_s (instruction 0013) and one to the concatstrings VM instruction (instruction 0017).

    On the other hand, a solution like:

    x=1
    s="%{foo}" % {foo: x}
    

    Will, on every invocation, push a format string onto the stack (instruction 0003) and invoke String#% (instruction 0007) to re-parse it, essentially triggering the same format-parsing code described in the Kernel::sprintf documentation.

    $ ruby --dump=insns < x=1
    > s="%{foo}" % {foo: x}
    > EXAMPLE
    == disasm: #@-:1 (1,0)-(2,21)> (catch: FALSE)
    local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
    [ 2] x@0        [ 1] s@1
    0000 putobject_INT2FIX_1_                                             (   1)[Li]
    0001 setlocal_WC_0                x@0
    0003 putstring                    "%{foo}"                            (   2)[Li]
    0005 getlocal_WC_0                x@0
    0007 opt_send_without_block       , 
    0010 dup
    0011 setlocal_WC_0                s@1
    0013 leave
    

    Most use cases won't care about this distinction, but if you came to this question because you do, there you go.

提交回复
热议问题