Custom curry

Richard Suchenwirth 2002-01-21 - Currying (named after the founder of combinatory logic, Haskell Curry [1 ] - Playing Haskell) is in my understanding the act of deriving a function from another by adding one or more constant arguments. For instance, we can specialize the following general two-operand multiplier

 proc mult {a b} {expr $a*$b}

into a "doubler" that multiplies its single argument by 2:

 proc double {a} {mult $a 2}

If all added arguments come before the "real" variables (in the last example, the order of the arguments can be swapped), we can easily use Tcl's interp alias command that does the same effect more efficiently, and saves one level in the call stack:

 interp alias {} double {} mult 2

(the {} indicating twice that the current interpreter is meant). Conversely, even without being aware, we have used currying whenever

  • a proc body consists of a single command with other args than of the proc
  • an interp alias has more than four arguments

We can use currying to factor out frequent calls or parts thereof, which might include assignment to a variable:

 interp alias {} -> {} set res
 -> 1 ;# equivalent to: set res 1

and of course we may curry the process of interp currying:}

 proc curry {new args} {eval [list interp alias {} $new {}] $args}

Such custom currying may seem like obfuscation, but it makes for tighter code, as the following example, German plurals, shows. Repeated action like the adding of umlaut dots (a -> ä) is factored out into a custom curry, as is the concatenation with a suffix (which otherwise would require frequent bracing of the variable name, as seen in English plurals:

 proc sjoin args {join $args ""} ;# spaceless concat
 
 proc de:pl word {
    curry +   sjoin $word
    curry a:+ sjoin [string map {a ä} $word]
    curry o:+ sjoin [string map {o ö} $word]
    curry u:+ sjoin [string map {u ü} $word]
    curry -1+ sjoin [string range $word 0 end-1]
    curry -2+ sjoin [string range $word 0 end-2]
    # The language inside the switch body looks strange, but clear:
    switch -- $word {
        Drucker - Fenster - Lehrer - Löffel - Messer - Schüler
        - Hamster - Gitter - Finger - Schweinchen
           {+ ;# leave unchanged}
        Hund - Berg - Pferd - Krokodil - Tier - Problem - Bildschirm
        - Meer - Tisch - Begriff - Schaf - Haar - Kamel - Dromedar
        - Schwein
           {+ e}
        Bahn - Elefant - Frau - Tür - Zahl - Tastatur - Staat - Ohr
        - Burg
           {+ en}
        Kind - Rind - Bild - Schild - Geist - Brett
           {+ er}
        Katze - Frage - Pflanze - Straße - Gabel - Gasse - Diskette
        - Platte - Seite - Ratte - Kammer - Henne - See - Auge - Hase
           {+ n}
        Garten {a:+}
        Bach - Baum - Traum - Maus - Laus - Platz - Stadt - Kraft
        - Hand - Zaun
           {a:+ e}
        Haus - Mann - Tal {a:+ er}
        Kloster - Tochter - Vogel {o:+}
        Sohn - Hof - Wolf  {o:+ e}
        Dorf - Horn  {o:+ er}
        Mutter  {u:+}
        Fluss - Fuß - Kuh - Stuhl - Fuchs - Zug {u:+ e}
        Buch - Tuch - Huhn - Mund {u:+ er}
        Bus - Zirkus    {+ se}
        Thema   {-1+ en}
        Globus - Zyklus - Faktum - Medium - Museum {-2+ en}
        Kaktus  {-2+ een}
        Genus   {-2+ era}
        Modus   {-2+ i}
        Nomen - Pronomen {-2+ ina}
        Atlas   {-1+ nten}
        default {+ s}
    }
 }

Discussion: The "curry" names are defined directly before use, which increases readability. They are however never undefined and thus may clutter the current interpreter. A cleaner solution would be to arrange for their destruction on leaving of this proc (see Local procedures for an analog example).

After marveling at the power of currying, and thinking further, it occurred to me that this is of course also possible in C by using macros:

 #define RES       res =
 #define DOUBLE(x) mult(x,2)

So, no big deal... or is it? You cannot pass these C macros as arguments (would raise syntax errors), while the Tcl curry examples above are callable by name, and thus can be further processed (e.g. with functional composition...)


escargo(?): Some references on currying:

  • [2 ]
  • In Scheme [4 ], [3 ].
  • In Haskell [5 ].
  • In Perl6 [6 ].

One difference to "real curry" as in Feather CurryObj is that you have to pass in a name. It seems more functional to make up a name and return that, which would be identical to the call like used in Lambda in Tcl:

 proc curry2 args {
    set name [info level 0]
    eval [list interp alias {} $name {}] $args
    set name
 } ;# RS
 % set double2 [curry2 mult 2] ==> curry2 mult 2
 % $double2 5 ==> 10

RS: It appears as if currying is a special application of lambda (see Lambda in Tcl) and can also be implemented like this:

 set double3 [lambda x {mult 2 $x}] ==> lambda x {mult 2 $x}
 $double3 5 ==> 10

at the cost of one more level in call stack, so the interp alias solution appears to be gentler.


SS demonstrated in the Tcl chatroom on 2005-03-29 how to do curry with Jim closures: Curry in Jim:

 proc curry {cmd args} {
     lambda args [list cmd [list pref $args]] {
         uplevel 1 [list $cmd {*}$pref {*}$args]
     }
 }

aspect discovered the following on Block-local variables (months too late):

 proc know what {proc unknown args $what\n[info body unknown]}
 know {
     if {[llength [lindex $args 0]]>1} {
         return [uplevel 1 [lindex $args 0] [lrange $args 1 end]]
     }
 }

If this isn't curry I don't know what is:

 % namespace path ::tcl::mathop
 % set double {* 2}
 % $double 5
 10

.. the above finally brought Let unknown know irrevocably into my standard prelude. If I was slightly less conservative I'd argue for it to be default behaviour in all circumstances -- who deliberately puts a space in a command name anyway? I think the above is really beautiful, very Tclish, and reflective of of how powerful Currying is in the language named after him (Haskell).

NEM Yes, this auto-expansion of leading word is really very powerful. Several of us would like to see it eventually become part of Tcl's default evaluation rules (in Tcl 9 or so). It was also one of the original motivations for my proposing namespace unknown, so that you can do these sort of tricks on a per-namespace basis:

 proc expand {next cmd args} {
     if {[llength $cmd] > 1} {
         uplevel 1 $cmd $args
     } else {
         uplevel 1 $next [linsert $args 0 $cmd]
     }
 }
 namespace unknown [list expand [namespace unknown]]

Lars H: But what need is there for having unknown do expansion, now that we've got {*}?

 % namespace path ::tcl::mathop
 % set double {* 2}
 % {*}$double 5
 10

Yes, this means you need to decide whether you're passing around command names or command prefixes. So what? Tcl has always been a Write What You Mean language.

NEM It's ugly. Command prefixes should be the default, not command names, so you'd end up using this everywhere.