Thanks to the way that perl is untyped (to some extent), it's quite possible to have functions that can take any type of parameters, and have the function adapt to several possible input methods.

I'm thinking along the same lines for a data entry function for a module I'm writing. Each point can contain 2 required data elements, an optional name parameter, and an optional hash containing options for that point. Internally to the module, I'll be storing all this as a hash of hashes, but from an external standpoint, I can see many ways of allowing the data to be entered. These include:

In each of these cases, it's easy to see how one might have gotten the original data into one of the formats above, so I'm simply trying to make it as easy to convert from what a user might have to my internal storage. And, save for one case, it's easy enough to determine what format is used based on using ref and other inspection techniques. (The one case would be between the array of arrays, and the up-to-4 arrays; if inspection of the @_ array (after removing other arguments not related to the data) revealed more than 4 elements, it would be assumed that the former case is what is indended by the user.)

Is such complicated distinguishing of a mutable argument a good thing for development? It's certainly not a problem to replace this mutable call with several functions that have more specific ways of accepting data, but that would seem to complicate the API of the module, requiring the user to remember exactly what is to be passed to each function. A function call with such a mutable argument begins to move the module usage to a state of "do what I mean, not what I say" in terms of user interaction, requiring them to remember less about the specifics of the API and focusing them on their own programming more.

Are there any options or ideas in regards to this?


Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain

Replies are listed 'Best First'.
Re (tilly) 1: Complexity of API argument passing
by tilly (Archbishop) on Jul 14, 2001 at 01:40 UTC
    I strongly prefer functions with simple and consistent interfaces. Heavily overloaded interfaces get complex, and maintaining the API is too much work for my taste. Furthermore if the person is using none of your APIs correctly, it becomes much harder to give advice about what they are doing wrong.

    I would KISS, clearly document what the API is, and provide careful error checks. Furthermore if you are developing a larger body of code I would suggest settling on a generic interface pattern that suits you and trying to use that consistently. There are many possible interfaces that could work, but based on experience I have to say that named interfaces are far less error-prone and lead to much more readable code than positional logic.

    If you want to have more possible input formats, add more functions and clearly name them. Make those functions all wrappers around the one you find easiest to work with. This offers flexibility, but does not unduly complicate your module. The wrappers need but be written once and then left. You have done the critical thing - created a single point of maintainance for any future improvements to the module. From the user's point of view the complexity of the interface lies in the number of methods they make use of.

    UPDATE
    I forgot to point out the exception that I have for the rule about not overloading interfaces. If your interface has a hook for a thing with overloadable defined behaviour, that is just fine by me. Internally to your module you are consistent in your use of the parameters. However the user can do what they want.

    The two classic examples of things with overloadable defined behaviour are objects and anonymous functions. An anonymous function has one piece of overloadable behaviour, it can be called. That will do something, anything. There is an expectation about what it should do in the function, but the hook is available. Similarly an object has multiple overloadable behaviours called methods. Provide the right methods the code works. But each defined method is a defined hook that can usefully be used to customize what will happen.

    If this comment about objects (and anonymous functions) does not make sense to you, then you have not grasped the essence of OO...

      Would it be unreasonable to offer both approaches, the individual functions as well as the 'do-all' approach?

      Specifically, in my case, I will be adding data to the internatl structure by a function "add_data_point", which is explicit in what it takes such that it could be prototyped. ($$;$% for example). I could then define various functions "add_data_from_TYPE", TYPE being replaced by what method of data storage I would expect there (hash of hashes, array of hashes, etc). Each function would call add_data_point repeated to add the data to the internal storage in the appropriate manner. This would provide the consistent interface for the API. Again, these functions could be prototyped without problems. I could then provide one last function "add_data", which would do the checks that I suggest in the root node of this thread, and then call out to the specific "add_data_from_TYPE" calls depending on the type of structure. This provides more of the "intelligent" interface for the API, without adding a lot of extra code.


      Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
        I would wonder why.

        It sounds like a lot of work, and a lot of complexity. It makes extending your interface later much harder. It makes it harder to offer a good error check. This "accepts anything" function is going to be much harder to explain. And it introduces a significant point of confusion for people trying to understand how to use your module.

        I simply don't see that what you are adding is valuable. It wouldn't be for me. I really prefer to have several simple ideas that fit together really well than a monolithic idea that tries to solve all of my problems.

Re: Complexity of API argument passing
by TheoPetersen (Priest) on Jul 13, 2001 at 23:39 UTC
    My opinion is that a routine should be generous and intelligent in handling arguments, to a level which matches its code "audience." If you are the only one using the routine, then you can set this to your tastes; obviously you wouldn't spend much consideration on it if it were being used only in one script in one place, but the more places you use it the more you'll invest in making life easier for calling scripts.

    If the routine is being used by other programmers, the requirements for generous argument handling jump immediately. You should process all sensible input arrangements so long as those arrangements don't compromise the routine's efficiency. I personally dislike any mechanism that requires a caller to pass arguments that tell the routine what other arguments are being passed, preferring things that can be deciphered via ref() as you mention.

    I definitely don't go to multiple front-end functions for a routine unless I'm in a maintenance situation where I can't change the input handling of the target routine. I do use such arrangements where I need different output formats, though, if the output format can't be determined just on the basis of wantarray.

Re: Complexity of API argument passing
by voyager (Friar) on Jul 14, 2001 at 01:03 UTC
    I've seen this several places, but don't know the original source (apologies if I butcher it):
    Be liberal in what you accept, and conservative in what you produce.
    You know how frustrating it can be when you just want to use a tool, and you spend a lot of time getting the call just right? Or the pleasure that comes when you call it in a manner intuitive to you, and it works?

    Kudos for the willingness to be so flexible.

Re: Complexity of API argument passing
by jepri (Parson) on Jul 14, 2001 at 16:07 UTC
    From what I've seen, every time there's a huge range of possible data formats, the programmer chooses the easiest one for himself, then writes a convert function for everybody else to use.

    The most recent case I recall of this is Date::Manip where the author picked one date/time format, and then provided the ParseDate function for everyone else.

    HTH,

    ____________________
    Jeremy
    I didn't believe in evil until I dated it.

Re: Complexity of API argument passing
by pmas (Hermit) on Jul 16, 2001 at 19:56 UTC
    If I understand your question correctly, you are asking us what interface we feel be preferable.

    My gut feeling is something like DBI->connect uses now:
    Pass required parameters as scalars, and all optional params as hash, with prototype  sub foo($$;%).
    Optional 3rd parameter will be bundled in hash with some meaningful name.

    This way, you have best of both words: only one function name to remember, and easy way to pass any combination of valid parameters.

    Does it make sense? Just my $0.02.

    pmas

    To make errors is human. But to make million errors per second, you need a computer.