highly parametric interfaces Alex Shinn (14 Apr 2006 03:42 UTC)
|
Re: highly parametric interfaces
John Cowan
(14 Apr 2006 04:52 UTC)
|
Re: highly parametric interfaces
Per Bothner
(14 Apr 2006 05:50 UTC)
|
Re: highly parametric interfaces
Alex Shinn
(16 Apr 2006 06:14 UTC)
|
Re: highly parametric interfaces
Marc Feeley
(14 Apr 2006 11:47 UTC)
|
Felix raised a good point, which is that many Schemers, including those that regularly use keywords, feel at some level that they're a bit of a hack. Keywords are purely syntactic sugar, and add complexity to the language core. Yet we use them. Even if not in the literal sense of a self-evaluating literal with a colon stuck on one end, sooner or later most people come across a procedure with too many parameters to simply be tacked on as optional arguments. Currently there are many ways to handle this: * Functional purist - "Your design is wrong, there's no excuse ever to write a function with such varied behavior." This person may in fact be correct. Using combinators and monads, any parametrized function can be broken into smaller, more manageable components. However, finding the appropriate breakdown can be time-consuming and difficult. Moreover, some interfaces genuinely are more naturally expressed with parameters. Anyone who would argue this is probably already using Haskell, or at any rate should just ignore this SRFI and never use it :) * OOP purist - "Just wrap any parametric behaviour into one or more configuration objects." This is very flexible, and convenient for chaining and passing on the same (or slightly modified) configuration to other functions. Early discussions on the RRRS authors list included an interesting variation of the NUMBER->STRING procedure: (number->string <number> <format>) Formats included (FIX n), (SCI n m), and more complicated compositions such as (number->string n (polar (flo 2 (radix 8)) (flo (radix 10))) The performance minded individual would make the configuration object constructors integrable. [The original discussion wasn't clear but it seemed to imply they were syntax, which destroys compositionality and loses much of the flexibility you've gained.] The disadvantages of this are that either 1) for every parametric procedure you need to define a new class and keep it in sync as the API changes, or 2) you use a single extensible configuration class, perhaps a hash-table or closure, which suffers from poor performance. Perl effectively uses the latter, passing hash-tables as arguments to complex functions, but Perl's hash-table syntax makes this feel more like a keyword interface, discussed below. * Efficiency-oriented OOP - "Just set the parameters in the calling object." This person of course assumes there are no functions, only methods, and only for a single calling object. This is where you get: (define fmt (make-number-formatter)) (number-formatter-set-complex-behavior! fmt O_POLAR) (number-formatter-set-magnitude-style! fmt O_FLOAT) (number-formatter-set-magnitude-places! fmt 2) (number-formatter-set-magnitude-radix! fmt 8) (number-formatter-set-angle-style! fmt O_FLOAT) (number-formatter-set-angle-radix! fmt 10) (number-formatter-format fmt n) for the equivalent to the above NUMBER->STRING example. These people are all coding C++ or Java, so we can ignore this and not even discuss what's wrong with it. * Lisp minimalist - "Why does everyone keep introducing new types when a simple alist will suffice?" Again, this is true, you can easily represent everything with an alist: (number->string n '((radix . 10))) This is fully general, and can express nested formats like the polar example naturally. Unfortunately, for more typical examples this is usually going to involve backquotes and commas (button `((text . ,(gettext "OK")) (action . ,quit))) * Syntactic sweet tooth - "Lists are fine, but let's clean up the appearance a little." Rather than an alist, a simple plist is just as efficient and can look much nicer: (button 'text (gettext "OK") 'action quit) Moreover, if we use a common convention for the keyword symbols we can visually distinguish (and have our editors automatically highlight) keyword usage: (button 'text: (gettext "OK") 'action: quit) ------------------------------------------------------------ The last example is what the Lisp community overwhelmingly turns to in these situations, but there is sufficient variation that is seems worthwhile standardizing. Some of the current practices are: :keyword - prefix, self-evaluating symbol as in CL :keyword - prefix, distinct type as in many Schemes keyword: - suffix, distinct type as in many other Schemes #:keyword - non-conflicting prefix, distinct type keyword - syntactic keyword as in SSAX The last is really disturbing. Here the author was faced with an SSAX:MAKE-PARSER procedure accepting up to 7 optional callback arguments, trying to write a portable library, and wanting to keep the interface simple. Because there is no portable keyword interface, SSAX:MAKE-PARSER became a macro, automatically quoting the keyword symbols. This means SSAX:MAKE-PARSER isn't composable, which has been a source of frustration to me. For this reason alone there should be a keyword SRFI! [I'm personally leaning towards 'keyword: i.e. just using symbols, with a : suffix as a convention. No need to add new types, and we can have a portable OPT-LAMBDA or LET-KEYWORDS* form. On the other hand, a separate type can help catch errors. Either way I'd probably just go with the masses here.] At the same time, Felix's concern is very valid. We shouldn't use keywords everywhere just because we can. Many of CL's functions take only one or two keyword arguments which could just as well be passed as an optional argument. And I think most Schemers would prefer (assoc elt ls my-equal?) to (assoc elt ls test: my-equal?) At the other extreme, SSAX and GUI interfaces clearly demand keyword API's. In the middle are interfaces like hash-tables and ports, which could be argued either way. We'll just have to fight those out :) -- Alex