Named sprintf parameters

Discussion in 'Ruby' started by Trans, Feb 28, 2008.

  1. Trans

    Trans Guest

    I have a question and perhaps a bit of challenge for those with mad
    parse skills: Has anyone ever considered named parameters for sprintf/
    printf? It would be quite useful (to me at least) to be able to do:

    "I am %(name)s." % { :name => "Tom" ]

    Has anyone worked on something like this before? Is there anything
    equivalent in the Perl world or other language? I realize we can use
    numerals to identify the substitutions, but I feel the labels are much
    more readable. Moreover, ultimately it would be interesting to see:

    "I am %(name)s." % binding

    Making use of the binding's local_variables.

    The challenge, of course, is to override printf/sprintf to do this
    (the binding part gets you extra ruby points ;-) Even more
    challenging, augment the C code to handle it and submit it as a patch.

    T.
     
    Trans, Feb 28, 2008
    #1
    1. Advertising

  2. Trans

    ara howard Guest

    On Feb 28, 2008, at 9:57 AM, Trans wrote:

    > I have a question and perhaps a bit of challenge for those with mad
    > parse skills: Has anyone ever considered named parameters for sprintf/
    > printf? It would be quite useful (to me at least) to be able to do:
    >
    > "I am %(name)s." % { :name => "Tom" ]
    >
    > Has anyone worked on something like this before? Is there anything
    > equivalent in the Perl world or other language? I realize we can use
    > numerals to identify the substitutions, but I feel the labels are much
    > more readable. Moreover, ultimately it would be interesting to see:
    >
    > "I am %(name)s." % binding
    >
    > Making use of the binding's local_variables.
    >
    > The challenge, of course, is to override printf/sprintf to do this
    > (the binding part gets you extra ruby points ;-) Even more
    > challenging, augment the C code to handle it and submit it as a patch.
    >
    > T.
    >



    class String
    module M
    def % *args
    newstuff
    super
    end
    end
    include M
    end

    a @ http://codeforpeople.com/
    --
    we can deny everything, except that we have the possibility of being
    better. simply reflect on that.
    h.h. the 14th dalai lama
     
    ara howard, Feb 28, 2008
    #2
    1. Advertising

  3. Trans

    t3ch.dude Guest

    On Feb 28, 11:57 am, Trans <> wrote:
    > I have a question and perhaps a bit of challenge for those with mad
    > parse skills: Has anyone ever considered named parameters for sprintf/
    > printf? It would be quite useful (to me at least) to be able to do:
    >
    >   "I am %(name)s." % { :name => "Tom" ]
    >
    > Has anyone worked on something like this before? Is there anything
    > equivalent in the Perl world or other language? I realize we can use
    > numerals to identify the substitutions, but I feel the labels are much
    > more readable. Moreover, ultimately it would be interesting to see:
    >
    >   "I am %(name)s." % binding
    >
    > Making use of the binding's local_variables.
    >
    > The challenge, of course, is to override printf/sprintf to do this
    > (the binding part gets you extra ruby points ;-)  Even more
    > challenging, augment the C code to handle it and submit it as a patch.
    >
    > T.


    That exact feature exists in that form in Python today...
    http://docs.python.org/lib/typesseq-strings.html

    -T3ch Dude
     
    t3ch.dude, Feb 28, 2008
    #3
  4. Trans

    yermej Guest

    On Feb 28, 11:29 am, ara howard <> wrote:

    > class String
    > module M
    > def % *args
    > newstuff
    > super
    > end
    > end
    > include M
    > end
    >


    Can you please explain this? It looks to me like it would allow you to
    override a method, but still call the original without using alias.
    However, when I try anything like that, it only calls the original
    method. The method defined inside of M isn't called. Is that a 1.9
    thing or am I misunderstanding what you meant?
     
    yermej, Feb 28, 2008
    #4
  5. Trans

    Adam Shelly Guest

    On 2/28/08, yermej <> wrote:
    > On Feb 28, 11:29 am, ara howard <> wrote:
    >
    > > class String
    > > module M
    > > def % *args
    > > newstuff
    > > super
    > > end
    > > end
    > > include M
    > > end
    > >

    >
    > However, when I try anything like that, it only calls the original
    > method. The method defined inside of M isn't called.

    ...
    >


    I also couldn't get Ara's example to work, so I resorted to monkeypatching.
    (Yes, I know, I am Destroying Ruby..)

    ----------

    p "I am %s." % "string"
    p "I am %s." % ["array"]
    p "I am %s." % { :name => "hash" }
    begin
    p "I am %(name)s." % { :name => "hash" }
    rescue ArgumentError => err
    p "caught #{err}"
    end

    puts "\nMonkeyPatching!\n\n"
    class String
    alias :eek:ld_percent :%
    def % arg
    arr = (arg.kind_of? Array) ? arg : [arg]
    target = self.gsub(/%\((.+?)\)/){|m|
    raise ArgumentError, "named parameters need hash" unless arg.kind_of? Hash
    arr << arg[$1.to_sym]
    "%#{arr.size}$"
    }
    target.old_percent arr
    end
    end

    p "I am %s." % "string"
    p "I am %s." % ["array"]
    p "I am %s." % { :name => "hash" }
    p "I am %(name)s." % { :name => "hash" }

    p "I am %(name)s %(with)s %(what)s"%{:what => "multiple arguments",
    :with => "including", :name=> 'hash'}
    p "I am %(name)s %(with)s %1$p"%{:with => "containing", :name=> 'hash'}
    p "I am %(name)s." % [:name, "array"]


    -Adam

    P.S. It actually looks like patching sprintf wouldn't be too hard, as
    long as you disallowed mixing named arguments with any other type.
     
    Adam Shelly, Feb 28, 2008
    #5
  6. On 2/28/08, yermej <> wrote:
    > On Feb 28, 11:29 am, ara howard <> wrote:
    >
    > > class String
    > > module M
    > > def % *args
    > > newstuff
    > > super
    > > end
    > > end
    > > include M
    > > end
    > >

    >
    >
    > Can you please explain this? It looks to me like it would allow you to
    > override a method, but still call the original without using alias.
    > However, when I try anything like that, it only calls the original
    > method. The method defined inside of M isn't called. Is that a 1.9
    > thing or am I misunderstanding what you meant?


    No you are right. Including a module in a class effectively inserts
    the module above the class in the inheritance chain rather than below,
    a class can override methods in a module it includes, not the other
    way around. I missed Ara's point as well.

    Along these lines I was scratching my head the other night when I was
    reading Russ Olsen's "Design Patterns in Ruby" and he was showing an
    implementation of the Decorator pattern using modules as decorators, I
    missed the subtlety that he was using Object#extend to include a
    module above the singleton class of a particular object rather than
    including it in a 'real' class. But I don't think that technique
    applies here.


    --
    Rick DeNatale

    My blog on Ruby
    http://talklikeaduck.denhaven2.com/
     
    Rick DeNatale, Feb 28, 2008
    #6
  7. Trans

    Gary Wright Guest

    On Feb 28, 2008, at 11:57 AM, Trans wrote:
    > I have a question and perhaps a bit of challenge for those with mad
    > parse skills: Has anyone ever considered named parameters for sprintf/
    > printf? It would be quite useful (to me at least) to be able to do:
    >
    > "I am %(name)s." % { :name => "Tom" ]
    >
    > Has anyone worked on something like this before? Is there anything
    > equivalent in the Perl world or other language?


    I released a gem last year (http://jig.rubyforge.com) that defines
    a data structure I nicknamed a Jig that enables this sort of thing
    either by parsing a string to get the format or by explicit
    construction:

    j = Jig.new('I am ', :name)
    j2 = Jig.parse('I am %{:name:}')

    puts j % { :name => 'Tom'} # I am Tom
    puts j2 % { :name => 'Alice'} # I am Alice

    A Jig is a ordered sequence of objects and 'gaps'. Simple gaps are
    identified as symbols during Jig construction but you can also define
    gaps that process anything that is used to fill them:

    capitalize = Jig::Gap.new:)name) { |x| x.capitalize }
    j2 = Jig.new('before: ', :name, ' after: ', capitalize)
    puts j2 % {:name => 'bob' } # 'before: bob after: Bob'

    In that example you can see that all gaps with the same name
    are plugged simultaneously. Unplugged gaps are rendered as
    the empty string when Jig#to_s is called.

    You can also construct a Jig or fill a gap with a proc/lambda/method
    which will be evaluated at the time the Jig is rendered to a string:

    time = Jig.new("The time is ") {Time.now}
    puts time # The time is Thu Feb 28 18:16:07 -0500 2008
    sleep 5
    puts time # The time is Thu Feb 28 18:16:12 -0500 2008

    The gem has classes for handling XML, XHTML, and CSS constructs.

    I've made a few changes since the initialize release but haven't
    gotten around to pushing it a new release to Rubyforge yet....

    Gary Wright

    http://jig.rubyforge.com
     
    Gary Wright, Feb 28, 2008
    #7
  8. Trans

    ara howard Guest

    On Feb 28, 2008, at 1:09 PM, yermej wrote:

    > On Feb 28, 11:29 am, ara howard <> wrote:
    >
    >> class String
    >> module M
    >> def % *args
    >> newstuff
    >> super
    >> end
    >> end
    >> include M
    >> end
    >>

    >
    > Can you please explain this? It looks to me like it would allow you to
    > override a method, but still call the original without using alias.
    > However, when I try anything like that, it only calls the original
    > method. The method defined inside of M isn't called. Is that a 1.9
    > thing or am I misunderstanding what you meant?
    >


    i was just being stupid. you'd need something like this


    cfp2:~ > cat a.rb
    class String

    Percent = instance_method '%' unless defined? Percent

    def % *a, &b
    a.flatten!
    string =
    case a.last
    when Hash
    expand a.pop
    else
    self
    end
    if a.empty?
    string
    else
    Percent.bind(string).call(*a, &b)
    end
    end

    def expand! vars = {}
    loop do
    changed = false
    vars.each do |var, value|
    var = var.to_s
    var.gsub! %r/[^a-zA-Z0-9_]/, ''
    [
    %r/\$#{ var }\b/,
    %r/\@#{ var }\b/,
    %r/\$\{\s*#{ var }\s*\}/,
    %r/\@\{\s*#{ var }\s*\}/,
    ].each do |pat|
    changed = gsub! pat, "#{ value }"
    end
    end
    break unless changed
    end
    self
    end

    def expand opts = {}
    dup.expand! opts
    end

    end

    puts( 'I am @name'.expand:)name => 'Ara') )
    puts( 'I am @name' % {:name => 'Ara'} )



    cfp2:~ > ruby a.rb
    I am Ara
    I am Ara

    a @ http://codeforpeople.com/
    --
    share your knowledge. it's a way to achieve immortality.
    h.h. the 14th dalai lama
     
    ara howard, Feb 29, 2008
    #8
  9. Hi,

    At Fri, 29 Feb 2008 01:57:23 +0900,
    Trans wrote in [ruby-talk:292860]:
    > I have a question and perhaps a bit of challenge for those with mad
    > parse skills: Has anyone ever considered named parameters for sprintf/
    > printf? It would be quite useful (to me at least) to be able to do:


    Yes, once I had posted the patch for it, and was rejected.

    > "I am %(name)s." % { :name => "Tom" ]
    >
    > Has anyone worked on something like this before? Is there anything
    > equivalent in the Perl world or other language? I realize we can use
    > numerals to identify the substitutions, but I feel the labels are much
    > more readable. Moreover, ultimately it would be interesting to see:
    >
    > "I am %(name)s." % binding
    >
    > Making use of the binding's local_variables.


    Though I don't feel it attractive, I believe named parameter is
    an important feature for I18N, and IIRC, ruby-gettext has it.

    --
    Nobu Nakada
     
    Nobuyoshi Nakada, Feb 29, 2008
    #9
  10. On Fri, Feb 29, 2008 at 01:51:02PM +0900, Nobuyoshi Nakada wrote:
    > Hi,
    >
    > At Fri, 29 Feb 2008 01:57:23 +0900,
    > Trans wrote in [ruby-talk:292860]:
    > > I have a question and perhaps a bit of challenge for those with mad
    > > parse skills: Has anyone ever considered named parameters for sprintf/
    > > printf? It would be quite useful (to me at least) to be able to do:

    >
    > Yes, once I had posted the patch for it, and was rejected.
    >
    > > "I am %(name)s." % { :name => "Tom" ]
    > >
    > > Has anyone worked on something like this before? Is there anything
    > > equivalent in the Perl world or other language? I realize we can use
    > > numerals to identify the substitutions, but I feel the labels are much
    > > more readable. Moreover, ultimately it would be interesting to see:
    > >
    > > "I am %(name)s." % binding
    > >
    > > Making use of the binding's local_variables.

    >
    > Though I don't feel it attractive, I believe named parameter is
    > an important feature for I18N, and IIRC, ruby-gettext has it.


    You may find the recently announced Jig library of interest. I was sitting
    next to Gary during something at RubyConf, maybe RejectConf, and he gave me
    a demo. Pretty cool. See http://www.ruby-forum.com/topic/144356

    > Nobu Nakada

    --Greg
     
    Gregory Seidman, Feb 29, 2008
    #10
  11. Trans

    Trans Guest

    On Feb 28, 11:51 pm, Nobuyoshi Nakada <> wrote:
    > Hi,
    >
    > At Fri, 29 Feb 2008 01:57:23 +0900,
    > Trans wrote in [ruby-talk:292860]:
    >
    > > I have a question and perhaps a bit of challenge for those with mad
    > > parse skills: Has anyone ever considered named parameters for sprintf/
    > > printf? It would be quite useful (to me at least) to be able to do:

    >
    > Yes, once I had posted the patch for it, and was rejected.


    :(

    > > "I am %(name)s." % { :name => "Tom" ]

    >
    > > Has anyone worked on something like this before? Is there anything
    > > equivalent in the Perl world or other language? I realize we can use
    > > numerals to identify the substitutions, but I feel the labels are much
    > > more readable. Moreover, ultimately it would be interesting to see:

    >
    > > "I am %(name)s." % binding

    >
    > > Making use of the binding's local_variables.

    >
    > Though I don't feel it attractive, I believe named parameter is
    > an important feature for I18N, and IIRC, ruby-gettext has it.


    Really? It seems like a nice way to apply parameter to templates to
    me. Rather then, say,

    xml = %{
    <customer id="#{params[:id]}">
    <name>#{params[:name]}</name>
    </customer>
    }

    One could do:

    xml = %{
    <customer id="%(id)u">
    <name>%(name)s</name>
    </customer>
    } % params

    A little cleaner --and provides a nice means of reusable
    interpolation.

    I'll have to look at ruby-gettext.

    Thanks,
    T.
     
    Trans, Feb 29, 2008
    #11
  12. Trans

    Trans Guest

    On Feb 28, 3:42 pm, "Adam Shelly" <> wrote:
    > On 2/28/08, yermej <> wrote:
    >
    >
    >
    > > On Feb 28, 11:29 am, ara howard <> wrote:

    >
    > > > class String
    > > > module M
    > > > def % *args
    > > > newstuff
    > > > super
    > > > end
    > > > end
    > > > include M
    > > > end

    >
    > > However, when I try anything like that, it only calls the original
    > > method. The method defined inside of M isn't called.

    > ...
    >
    > I also couldn't get Ara's example to work, so I resorted to monkeypatching.
    > (Yes, I know, I am Destroying Ruby..)
    >
    > ----------
    >
    > p "I am %s." % "string"
    > p "I am %s." % ["array"]
    > p "I am %s." % { :name => "hash" }
    > begin
    > p "I am %(name)s." % { :name => "hash" }
    > rescue ArgumentError => err
    > p "caught #{err}"
    > end
    >
    > puts "\nMonkeyPatching!\n\n"
    > class String
    > alias :eek:ld_percent :%
    > def % arg
    > arr = (arg.kind_of? Array) ? arg : [arg]
    > target = self.gsub(/%\((.+?)\)/){|m|
    > raise ArgumentError, "named parameters need hash" unless arg.kind_of? Hash
    > arr << arg[$1.to_sym]
    > "%#{arr.size}$"
    > }
    > target.old_percent arr
    > end
    > end
    >
    > p "I am %s." % "string"
    > p "I am %s." % ["array"]
    > p "I am %s." % { :name => "hash" }
    > p "I am %(name)s." % { :name => "hash" }
    >
    > p "I am %(name)s %(with)s %(what)s"%{:what => "multiple arguments",
    > :with => "including", :name=> 'hash'}
    > p "I am %(name)s %(with)s %1$p"%{:with => "containing", :name=> 'hash'}
    > p "I am %(name)s." % [:name, "array"]
    >
    > -Adam


    Looks like you get the prize ;)

    I would like to add this to Facets, though under a slightly different
    naming of course. Not sure what though #printfv #sprintfv ?

    Thanks!
    T.
     
    Trans, Mar 2, 2008
    #12
  13. Trans

    Trans Guest

    On Feb 28, 7:13 pm, ara howard <> wrote:
    > On Feb 28, 2008, at 1:09 PM, yermej wrote:
    >
    >
    >
    > > On Feb 28, 11:29 am, ara howard <> wrote:

    >
    > >> class String
    > >> module M
    > >> def % *args
    > >> newstuff
    > >> super
    > >> end
    > >> end
    > >> include M
    > >> end

    >
    > > Can you please explain this? It looks to me like it would allow you to
    > > override a method, but still call the original without using alias.
    > > However, when I try anything like that, it only calls the original
    > > method. The method defined inside of M isn't called. Is that a 1.9
    > > thing or am I misunderstanding what you meant?

    >
    > i was just being stupid. you'd need something like this
    >
    > cfp2:~ > cat a.rb
    > class String
    >
    > Percent = instance_method '%' unless defined? Percent
    >
    > def % *a, &b
    > a.flatten!
    > string =
    > case a.last
    > when Hash
    > expand a.pop
    > else
    > self
    > end
    > if a.empty?
    > string
    > else
    > Percent.bind(string).call(*a, &b)
    > end
    > end
    >
    > def expand! vars = {}
    > loop do
    > changed = false
    > vars.each do |var, value|
    > var = var.to_s
    > var.gsub! %r/[^a-zA-Z0-9_]/, ''
    > [
    > %r/\$#{ var }\b/,
    > %r/\@#{ var }\b/,
    > %r/\$\{\s*#{ var }\s*\}/,
    > %r/\@\{\s*#{ var }\s*\}/,
    > ].each do |pat|
    > changed = gsub! pat, "#{ value }"
    > end
    > end
    > break unless changed
    > end
    > self
    > end
    >
    > def expand opts = {}
    > dup.expand! opts
    > end
    >
    > end
    >
    > puts( 'I am @name'.expand:)name => 'Ara') )
    > puts( 'I am @name' % {:name => 'Ara'} )
    >
    > cfp2:~ > ruby a.rb
    > I am Ara
    > I am Ara


    Hmmm... this looks more like an answer to the interpolation question
    asked in a earlier thread. Is this a full-fledge implementation? Would
    it be a better solution then:

    def String.interpolate(&str)
    eval "%{#{str.call}}", str.binding
    end

    T.
     
    Trans, Mar 2, 2008
    #13
  14. Trans

    Von Fugal Guest

    Adam Shelly wrote:
    <snip/>
    > puts "\nMonkeyPatching!\n\n"
    > class String
    > alias :eek:ld_percent :%
    > def % arg
    > arr = (arg.kind_of? Array) ? arg : [arg]
    > target = self.gsub(/%\((.+?)\)/){|m|
    > raise ArgumentError, "named parameters need hash" unless
    > arg.kind_of? Hash
    > arr << arg[$1.to_sym]
    > "%#{arr.size}$"
    > }
    > target.old_percent arr
    > end
    > end

    <snip/>

    Don't forget the escaped %, and also, I propose the format %foo:s
    instead of %(foo)s. And heres some more code to chew on, based off of
    Adam's:

    class String
    alias old_percent %
    def % arg
    names = []
    target = gsub /(^|[^%])%(\w+):/ do
    name, space = $2, $1 # don't forget to replace whatever wasn't %
    names << name unless names.include? name
    "#{space}%#{names.rindex(name) + 1}$"
    end
    args = case arg
    when Hash: names.map {|n| arg[n] or arg[n.to_sym]} #keys are often
    symbols
    when Array: arg
    else [arg]
    end
    old_percent args
    end
    end
    --
    Posted via http://www.ruby-forum.com/.
     
    Von Fugal, May 16, 2008
    #14
  15. Trans

    Von Fugal Guest

    I forgot one important point... Don't make me use a hash if I don't want
    to! (hence mapping the hash to array or just using an array if no hash)
    --
    Posted via http://www.ruby-forum.com/.
     
    Von Fugal, May 16, 2008
    #15
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Adam Ruth

    Named parameters

    Adam Ruth, Oct 30, 2003, in forum: C Programming
    Replies:
    48
    Views:
    1,108
    James Hu
    Nov 3, 2003
  2. Magnus Lyck?
    Replies:
    5
    Views:
    405
    Magnus Lyck?
    Dec 2, 2003
  3. John Leslie

    pass named parameters to python

    John Leslie, Feb 8, 2005, in forum: Python
    Replies:
    1
    Views:
    551
    Duncan Booth
    Feb 8, 2005
  4. Bill Pursell

    Named parameters

    Bill Pursell, Jun 21, 2006, in forum: C Programming
    Replies:
    37
    Views:
    999
    Mark McIntyre
    Jun 24, 2006
  5. Adam Hartshorne

    "named parameters mechanism"

    Adam Hartshorne, Jan 23, 2006, in forum: C++
    Replies:
    3
    Views:
    377
    John Carson
    Jan 23, 2006
Loading...

Share This Page