How do you cope with 2 or more optional parameters?

classic Classic list List threaded Threaded
19 messages Options
Reply | Threaded
Open this post in threaded view
|

How do you cope with 2 or more optional parameters?

spir
PS: There are certainly several techniques (eg thought at requiring a table for opt args). I'd like to review various possibilities.

Hello,

Well, I guess all is in title.
How you cope with a cas like the following:

   func = function(param, optparam1, optparam2) dostuff end
   ...
   func(arg, optarg2)

Names arguments are very cool in some cases ;-)

Denis
________________________________

la vita e estrany

http://spir.wikidot.com/
Reply | Threaded
Open this post in threaded view
|

Re: How do you cope with 2 or more optional parameters?

Joseph Stewart
This isn't pretty, but I've done things like this before:

function a(x,y,z)
local x = x or "dX"
local y = y or "dY"
local z = z or "dZ"
print(x,y,z)
end

then you can do things like

a(nil,1) -- prints "dX 1 dZ"

along with many other variants.

-joe

On Fri, Dec 18, 2009 at 12:07 PM, spir <[hidden email]> wrote:
PS: There are certainly several techniques (eg thought at requiring a table for opt args). I'd like to review various possibilities.

Hello,

Well, I guess all is in title.
How you cope with a cas like the following:

  func = function(param, optparam1, optparam2) dostuff end
  ...
  func(arg, optarg2)

Names arguments are very cool in some cases ;-)

Denis
________________________________

la vita e estrany

http://spir.wikidot.com/

Reply | Threaded
Open this post in threaded view
|

Re: How do you cope with 2 or more optional parameters?

Javier Guerra Giraldez
In reply to this post by spir
On Fri, Dec 18, 2009 at 12:07 PM, spir <[hidden email]> wrote:
> PS: There are certainly several techniques (eg thought at requiring a table for opt args). I'd like to review various possibilities.

roughly in order of personal preference:

- try to design the API so that it doesn't make sense to have more
than 1 optional parameter

- try to order optional parameters so that specifying one only makes
sense if the previous one is also specified.

- insert nil values: "func(val1,nil,nil,val4)"

- if there's few parameters, and the type makes it obvious which is
which, start the function by shifting them to make them match (this is
heavily used in some JavaScript libraries, jQuery in particular)

- if there are too many arguments, replace most of them with object state

- use a table as the only parameter, so the user writes "func{arg=val,
arg4=val4}" instead of "func(val1,val2,{arg5=val5})"

--
Javier
Reply | Threaded
Open this post in threaded view
|

Re: How do you cope with 2 or more optional parameters?

Michal Kolodziejczyk-3
On 18.12.2009 18:19, Javier Guerra wrote:
> On Fri, Dec 18, 2009 at 12:07 PM, spir <[hidden email]> wrote:
>> PS: There are certainly several techniques (eg thought at requiring a table for opt args). I'd like to review various possibilities.
>
> roughly in order of personal preference:
>
> - try to design the API so that it doesn't make sense to have more
> than 1 optional parameter

This is interesting... That would be kind of dataflow programming:

http://en.wikipedia.org/wiki/Dataflow_programming

Here is an example (but this assumes some form of object oriented
programming):

object={
  setX=function(self, value)
    self.X=value
    return self
  end,
  setY=function(self, value)
    self.Y=value
    return self
  end,
  setZ=function(self, value)
    self.Z=value
    return self
  end
}
setmetatable(object, {__tostring=function(self)
  return(string.format("X=%s Y=%s Z=%s", tostring(self.X),
tostring(self.Y), tostring(self.Z)))
  end
})

object:setX(1):setZ(3) -- instead of object:setXYZ(1,nil,3)
print(object)

Regards,
        miko
Reply | Threaded
Open this post in threaded view
|

Re: How do you cope with 2 or more optional parameters?

Petite Abeille

On Dec 18, 2009, at 7:05 PM, Michal Kolodziejczyk wrote:

> This is interesting... That would be kind of dataflow programming:

Method chaining?

http://martinfowler.com/dslwip/MethodChaining.html

> Here is an example (but this assumes some form of object oriented programming):


local function f( t )
    print( t[ 1 ] )
   
    return f
end

f{1}{2}{3}

> 1
> 2
> 3




Reply | Threaded
Open this post in threaded view
|

Re: How do you cope with 2 or more optional parameters?

Javier Guerra Giraldez
In reply to this post by Michal Kolodziejczyk-3
2009/12/18 Michal Kolodziejczyk <[hidden email]>:
> On 18.12.2009 18:19, Javier Guerra wrote:
>> - try to design the API so that it doesn't make sense to have more
>> than 1 optional parameter
>
> This is interesting... That would be kind of dataflow programming:

not at all, it's about keeping focus.

what you describe is a nice API style ( try to make it work by
returning useful values), also called method chaining, or 'fluent
style' (i'm not sure if i find this name amusing or nauseating); but
it's not what i was thinking about.

about keeping focus: if a function has too many parameters, and
especially if many of them are optional, it's a hint that either:

a) all these parameters are in fact configuration. if so they should
be in a table anyway, or in object state.

b) the function is trying to do many different things according to the
right combination of present and absent parameters.  in that case,
better split it in two or more functions, each one with fewer
parameters.


--
Javier
Reply | Threaded
Open this post in threaded view
|

Re: parameter table [was: How... optional parameters?]

spir
In reply to this post by Javier Guerra Giraldez
Javier Guerra <[hidden email]> dixit:

> - use a table as the only parameter, so the user writes "func{arg=val,
> arg4=val4}" instead of "func(val1,val2,{arg5=val5})"

I recently realised the following:
* Most funcs have a main parameter defining "what" is processed (the 'subject', the prime mattery).
* Many have no other parameter.
* If any, they usually define "how" to process, ie they are parameters in the proper sense.
* These additional parameters often are optional.

   sort(seq, compfunc, reverse)

This is the reason why I intended to put additional parameters inside a table -- as opposed to the main one.

In OO programming, the main param is indeed most of the time the object itself, so it is implicit in func call.

   seq:sort(compfunc, reverse)

In this case, it would really make sense I guess to define methods with a table parameter only. Naming also allows:
* forgetting order
* auto-comment
* optionality
* (possibly) default values

The issue is indeed that the func def header doesn't describe the interface anymore. For this reason, and for the sake of consistency, I would like the possibility to define a func with a table of (possibly named) parameters, in the same way we can call them. (Note that this is not a feature request, as maybe it does not fit with Lua as an overall language, rather only with OO use it of it.)

Denis
________________________________

la vita e estrany

http://spir.wikidot.com/
Reply | Threaded
Open this post in threaded view
|

Re: How do you cope with 2 or more optional parameters?

spir
In reply to this post by Michal Kolodziejczyk-3
Michal Kolodziejczyk <[hidden email]> dixit:

> On 18.12.2009 18:19, Javier Guerra wrote:
> > On Fri, Dec 18, 2009 at 12:07 PM, spir <[hidden email]> wrote:
> >> PS: There are certainly several techniques (eg thought at requiring a table for opt args). I'd like to review various possibilities.
> >
> > roughly in order of personal preference:
> >
> > - try to design the API so that it doesn't make sense to have more
> > than 1 optional parameter
>
> This is interesting... That would be kind of dataflow programming:
>
> http://en.wikipedia.org/wiki/Dataflow_programming
>
> Here is an example (but this assumes some form of object oriented
> programming):
>
> object={
>   setX=function(self, value)
>     self.X=value
>     return self
>   end,
>   setY=function(self, value)
>     self.Y=value
>     return self
>   end,
>   setZ=function(self, value)
>     self.Z=value
>     return self
>   end
> }
> setmetatable(object, {__tostring=function(self)
>   return(string.format("X=%s Y=%s Z=%s", tostring(self.X),
> tostring(self.Y), tostring(self.Z)))
>   end
> })
>
> object:setX(1):setZ(3) -- instead of object:setXYZ(1,nil,3)
> print(object)
>
> Regards,
> miko
>

This is a common pattern of concatenative/postfix languages -- for the reason the last result is always on top of the stack. One could write eg:
   object 1 setX 3 setZ
and even chain with print or whatever:
   object 1 setX 3 setZ print

Some (OO) languages like Io also return the object itself by default, thus allowing such as pipe-like programming pattern. I find this default really attractive. For once, code density does not conflict with clarity.

Denis
________________________________

la vita e estrany

http://spir.wikidot.com/
Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Miles Bader-2
In reply to this post by spir
spir <[hidden email]> writes:
> In this case, it would really make sense I guess to define methods with a table parameter only. Naming also allows:
> * forgetting order
> * auto-comment
> * optionality
> * (possibly) default values

* much, much, less efficient

-Miles

--
Brain, n. An apparatus with which we think we think.

Reply | Threaded
Open this post in threaded view
|

Re: parameter table

spir
Miles Bader <[hidden email]> dixit:

> spir <[hidden email]> writes:
> > In this case, it would really make sense I guess to define methods with a table parameter only. Naming also allows:
> > * forgetting order
> > * auto-comment
> > * optionality
> > * (possibly) default values
>
> * much, much, less efficient
>
> -Miles
>

Would you like to expand on this? Do you mean machine time? If yes, what should be such costly?

Denis
________________________________

la vita e estrany

http://spir.wikidot.com/
Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Linker
It will make GC crazy by generate a lot of garbage(tables).

On Sat, Dec 19, 2009 at 17:38, spir <[hidden email]> wrote:
Miles Bader <[hidden email]> dixit:

> spir <[hidden email]> writes:
> > In this case, it would really make sense I guess to define methods with a table parameter only. Naming also allows:
> > * forgetting order
> > * auto-comment
> > * optionality
> > * (possibly) default values
>
> * much, much, less efficient
>
> -Miles
>

Would you like to expand on this? Do you mean machine time? If yes, what should be such costly?

Denis
________________________________

la vita e estrany

http://spir.wikidot.com/



--
Regards,
Linker Lin
[hidden email]
Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Miles Bader-2
In reply to this post by spir
spir <[hidden email]> writes:
>> * much, much, less efficient
>
> Would you like to expand on this? Do you mean machine time? If yes,
> what should be such costly?

Various reasons:  as Linker mentioned, it generates lots of garbage and
so keeps the GC busy; also constructing a table is slower than not doing
so, and accessing elements of a table is slower than accessing arguments.

That's not to say this technique should _never_ be used -- I use it
sometimes, as it can be helpful when the argument list is highly
variable etc.  But I don't think it's suitable for "primitive" functions
that are expected to have low overhead, and which might be called a lot;
that also suggests that for consistency reasons, you shouldn't use it
for primitive functions even if they _aren't_ called a lot...

I think it's common to use this technique for "constructors" of somewhat
complex objects, and heavyweight operations that have lots of options.

One compromise I sometimes use is to make a function accept _either_ a
table containing named arguments, or unnamed arguments passed normal;
roughly like:

   function foo (x, y z)
     if (type (x) == 'table')
       y = x.y
       z = x.z
       x = x.x    -- do x last, as it overwrites the table variable
     end

     ... use x, y, and z ...
   end

That allows the caller to use whichever style is appropriate for his
use, and doesn't have too much overhead when a table isn't used, and
there's only a slight bit of code bloat for the unpacking code.

-Miles

--
Education, n. That which discloses to the wise and disguises from the foolish
their lack of understanding.

Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Miles Bader-2
Incidentally, a simplistic benchmark:

   $ time lua -e 'function f(a,b,c) return a+b+c end; sum = 0; for x = 1, 5000000 do sum = sum + f(x, x+1, x+2) end; print(sum)'
   37500022500000

   real    0m1.745s
   user    0m1.352s
   sys     0m0.000s

   $ time lua -e 'function f(t) return t.a+t.b+t.c end; sum = 0; for x = 1, 5000000 do sum = sum + f{a=x, b=x+1, c=x+2} end; print(sum)'
   37500022500000

   real    0m3.844s
   user    0m3.768s
   sys     0m0.004s

The simple argument version is about 3 times faster (there would probably be less
somewhat advantage for more complex functions).

Here's the "optional table" version, which is sort of in between:

   $ time lua -e 'function f(a,b,c) if type (a) == 'table' then b = a.b c = a.c a = a.a end return a+b+c end; sum = 0; for x = 1, 5000000 do sum = sum + f(x, x+1, x+2) end; print(sum)'
   37500022500000

   real    0m2.004s
   user    0m2.000s
   sys     0m0.004s

But with luajit, it's even more crazy (I guess since it's much easier to
optimize simple things like argument passing than table construction
etc...):

[note the loop count here is different than above]

   $ time luajit-2.0.0-beta2 -e 'function f(a,b,c) return a+b+c end; sum = 0; for x = 1, 50000000 do sum = sum + f(x, x+1, x+2) end; print(sum)'
   3.750000225e+15

   real    0m0.244s
   user    0m0.224s
   sys     0m0.004s

   $ time luajit-2.0.0-beta2 -e 'function f(t) return t.a+t.b+t.c end; sum = 0; for x = 1, 50000000 do sum = sum + f{a=x, b=x+1, c=x+2} end; print(sum)'3.750000225e+15

   real    0m13.135s
   user    0m12.989s
   sys     0m0.000s

The simple argument version is almost 60 times faster!

With luajit, there's apparently little overhead from the "optional
table" code:

   $ time luajit-2.0.0-beta2 -e 'function f(a,b,c) if type (a) == 'table' then b = a.b c = a.c a = a.a end return a+b+c end; sum = 0; for x = 1, 50000000 do sum = sum + f(x, x+1, x+2) end; print(sum)'
   3.750000225e+15

   real    0m0.238s
   user    0m0.228s
   sys     0m0.000s

-Miles

--
Innards, n. pl. The stomach, heart, soul, and other bowels.

Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Artur Galyamov
Hi, Miles!

19.12.09, 23:22, "Miles Bader" <[hidden email]>:
> Here's the "optional table" version, which is sort of in between:
>    $ time lua -e 'function f(a,b,c) if type (a) == 'table' then b = a.b c = a.c a = a.a end return a+b+c end; sum = 0; for x = 1, 5000000 do sum = sum + f(x, x+1, x+2) end; print(sum)'
>    37500022500000
>    real    0m2.004s
>    user    0m2.000s
>    sys     0m0.004s

AFAIK, type() uses lua_pushstring(luaL_typename(...)), so it adds amount
of time here. I tested type(v) vs xtype(v) vs f(v), where xtype is
self-written C closure w/upvalues, and f(v) does nothing:

(unrelated to your benchmarks) (all globals)
type: 3.255u
xtype: 2.240u
f: 2.225u


If list will find it useful, here is the source:


static int
xtype(lua_State *L)
{
    static const int is[] = {
        1,  // -1 = LUA_TNONE          => [1] = "nil"
        1,  //  0 = LUA_TNIL           => [1] = "nil"
        4,  //  1 = LUA_TBOOLEAN       => [4] = "boolean"
        8,  //  2 = LUA_TLIGHTUSERDATA => [8] = "userdata"
        2,  //  3 = LUA_TNUMBER        => [2] = "number"
        3,  //  4 = LUA_TSTRING        => [3] = "string"
        5,  //  5 = LUA_TTABLE         => [5] = "table"
        6,  //  6 = LUA_TFUNCTION      => [6] = "function"
        8,  //  7 = LUA_TUSERDATA      => [8] = "userdata"
        7   //  8 = LUA_TTHREAD        => [7] = "thread"
    };
    int      type;

    type = lua_type(L, 1);

    if (-1 <= type && type <= 8)
        lua_pushvalue(L, lua_upvalueindex(is[type+1]));
    else
        lua_pushstring(L, luaL_typename(L, 1));
    return 1;
}
....
lua_pushliteral(L, "nil");
lua_pushliteral(L, "number");
lua_pushliteral(L, "string");
lua_pushliteral(L, "boolean");
lua_pushliteral(L, "table");
lua_pushliteral(L, "function");
lua_pushliteral(L, "thread");
lua_pushliteral(L, "userdata");
lua_pushcclosure(L, xtype, 8);
lua_setglobal(L, "xtype");



--
Artur
Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Mark Hamburg
I believe xtype is a direct replacement for type and it would probably be good to use it in Lua 5.2. I did something similar a while back.

Mark

Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Leo Razoumov
In reply to this post by Miles Bader-2
On 2009-12-19, Miles Bader <[hidden email]> wrote:

> Incidentally, a simplistic benchmark:
> [..snip..]
>
>    $ time lua -e 'function f(t) return t.a+t.b+t.c end; sum = 0; for x = 1, 5000000 do sum = sum + f{a=x, b=x+1, c=x+2} end; print(sum)'
>    37500022500000
>
>    real    0m3.844s
>    user    0m3.768s
>    sys     0m0.004s
>
>
>  But with luajit, it's even more crazy (I guess since it's much easier to
>  optimize simple things like argument passing than table construction
>  etc...):
>
>    $ time luajit-2.0.0-beta2 -e 'function f(t) return t.a+t.b+t.c end; sum = 0; for x = 1, 50000000 do sum = sum + f{a=x, b=x+1, c=x+2} end; print(sum)'3.750000225e+15
>
>    real    0m13.135s
>    user    0m12.989s
>    sys     0m0.000s
>

It is surprising that LuaJIT-2.0.0 is 3 times slower than the Lua
interpreter in the case of argument table. Actually, in my tests
(Ubuntu-9.04 Linux, 2GHz DualCore x86)  LuaJIT-1.1.5 is 2 times faster
than the Lua interpreter for argument tables

* Lua-5.1.4
zsh$ time lua -e 'function f(t) return t.a+t.b+t.c end; sum=0; for x =
1,5000000 do sum=sum + f{a=x,b=x+1,c=x+2} end; print(sum)'
37500022500000
lua -e   4.06s user 0.01s system 99% cpu 4.078 total

* LuaJIT-1.1.5
zsh$ time luajit -O -e 'function f(t) return t.a+t.b+t.c end; sum=0;
for x = 1,5000000 do sum=sum + f{a=x,b=x+1,c=x+2} end; print(sum)'
37500022500000
luajit -O -e   2.22s user 0.00s system 100% cpu 2.216 total

What gives?

--Leo--
Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Erik Lindroos
On Sun, Dec 20, 2009 at 4:42 PM, Leo Razoumov <slonik.az@gmail.com> wrote:
It is surprising that LuaJIT-2.0.0 is 3 times slower than the Lua
interpreter in the case of argument table. Actually, in my tests
(Ubuntu-9.04 Linux, 2GHz DualCore x86)  LuaJIT-1.1.5 is 2 times faster
than the Lua interpreter for argument tables

Notice that the iteration count of the luajit timings are 10 times larger.
Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Miles Bader-2
In reply to this post by Leo Razoumov
Leo Razoumov <[hidden email]> writes:
> It is surprising that LuaJIT-2.0.0 is 3 times slower than the Lua
> interpreter in the case of argument table. Actually, in my tests
> (Ubuntu-9.04 Linux, 2GHz DualCore x86)  LuaJIT-1.1.5 is 2 times faster
> than the Lua interpreter for argument tables
...
> What gives?

I used more repetitions for luajit -- 10 times the number to be exact,
so luajit is actually about 3 (10/3) times _faster_ than lua.

-Miles

--
Yo mama's so fat when she gets on an elevator it HAS to go down.

Reply | Threaded
Open this post in threaded view
|

Re: parameter table

Leo Razoumov
On 2009-12-21, Miles Bader <[hidden email]> wrote:

> Leo Razoumov <[hidden email]> writes:
>  > It is surprising that LuaJIT-2.0.0 is 3 times slower than the Lua
>  > interpreter in the case of argument table. Actually, in my tests
>  > (Ubuntu-9.04 Linux, 2GHz DualCore x86)  LuaJIT-1.1.5 is 2 times faster
>  > than the Lua interpreter for argument tables
>
> ...
>  > What gives?
>
>  I used more repetitions for luajit -- 10 times the number to be exact,
>  so luajit is actually about 3 (10/3) times _faster_ than lua.
>
>  -Miles
>

Auch! My bad. Did not count all the zeros. This is why I prefer to
write 5E6 or 5E7 to spare myself eye-strain.

--Leo--