[mercury-users] The Logic of Mercury

Richard A. O'Keefe ok at hermes.otago.ac.nz
Wed Sep 8 10:44:25 AEST 1999


Haskell and Clean are wonderful languages.
I find SML's inability to make up its mind about currying vexatious
(for example, built-in operators like + are NOT curried).
Lee Naish is a Very Smart Person and one of logic programming's
long-time Good Guys.

So it's with some trepidation that I strongly disagree with Lee
about the desirability of currying in logic programs.

To keep things neutral, let's consider a sort of hybrid SML/Clean
rather than Mercury.

f :: A -> B

   There are no partial applications of f.  All we can do is give it an
   A and sit back.

g :: (A,B) -> C

   There are TWO ways to partially apply g.  Let's define

   c2a :: ((a,b)->c, a) -> (b->c)
   c2a(f,x) = \y.f(x,y)

   c2b :: ((a,b)->c, b) -> (a->c)
   c2b(f,y) = \x.f(x,y)

   Now we can do c2a(g, an_a) and c2b(g, a_b).  To lay such heavy stress
   on currying as Lee does is to assert that c2a (supply left operand)
   is *much* more useful than c2b (supply right operand).  That just
   isn't true.  In Haskell, I find myself using (++x) or (-x) more
   often than (x++) or (x-).

h :: (A,B,C) -> D

    There are THREE ways to partially apply h to one argument,
    and THREE ways to partially apply it to two arguments.  Currying
    gives you only one of each.  Let's define

    c3a :: ((a,b,c)->d, a) -> ((b,c)->d)
    c3a(f,x) = \(y,z).f(x,y,z)
    
    c3b :: ((a,b,c)->d, b) -> ((a,c)->d)
    c3b(f,y) = \(x,z).f(x,y,z)
    
    c3c :: ((a,b,c)->d, c) -> ((a,b)->d)
    c3c(f,z) = \(x,y).f(x,y,z)
    
    c3ab :: ((a,b,c)->d, a, b) -> (c->d)
    c3ab(f,x,y) = \z.f(x,y,z)
    
    c3ac :: ((a,b,c)->d, a, c) -> (b->d)
    c3ac(f,x,z) = \y.f(x,y,z)
    
    c3bc :: ((a,b,c)->d, b, c) -> (a->d)
    c3bc(f,y,z) = \x.f(x,y,z)

    To lay such heavy stress on currying as Lee does is to assert
    that c3a and c3ab are *much* more useful than the other four
    forms.  This is intrinsically implausible.

Just to double-check, let's take some functions from the SML library.
I picked OS.Path as the module to look in because that's what I've
been looking at most recently.  Most of the multiargument functions
therein don't just take uncurried (tuple) arguments, they take an
argument *record* so as to get named arguments.

OS.Path.joinDirFile {dir = d, file = f}

    is a good example.  (By the way, how DO you get named arguments
    in a heavily curried language?  They'd be nice in Mercury, wouldn't
    they.  Erlang (ab)uses records for much the same reason.)

    For example, OS.Path.joinDirFile {dir="foo", file="bar"}
    yields "foo/bar" in UNIX or "foo\\bar" in Windows.

    Suppose it were
        join_dir_file :: (String,String) -> String
	join_dir_file(dir,file) = ...

    Then c2a(join_dir_file,"/tmp") would be a useful function for
    naming files in the tmp directory, BUT
    
    exists(c2b(OS.FileSys.exists,A_READ),
      map(c2b(join_dir_file,"profile"), directory_list))

    would be a way of looking for a file called "profile" in each
    of a list of directories.  Note that OS.FileSys.exists has
    its arguments in a natural order, but is MORE likely to be
    partially applied to its second argument than its first.

OS.Path.joinBaseExt {base = b, ext = e}

    is just such another.

OS.Path.mkAbsolute(name, reference)
OS.Path.mkRelative(name, reference)

    These two functions take a file name and an absolute reference.
    The name is interpreted relative to the reference, unless it is
    already absolute.  mkAbsolute returns an absolute result.
    mkRelative returns a path relative to the reference, e.g.
    mkRelative("/a/b/c/d", "/a/b") -> "c/d"
    mkRelative("/c/a/b", "c/d") -> "../a/b"

    I would expect most partial applications of these functions to
    supply the SECOND argument, not the first.

What happens with a function that has four arguments?
There are FOUR ways to supply one argument, SIX ways to
supply two arguments, and FOUR ways to supply three arguments,
for a total of 14 partial applications, only three of which are
supported by currying.

I am *not* saying that currying is useless, or that languages based on
it do not have their pleasant side.  What I *am* saying is that

- in an uncurried polymorphic functional language (such as the
  functional sublanguage of Mercury) it is possible to define a
  kit of currying functions that can be applied to provide all
  of the combinations directly supported by built-in currying and then
  some, with only minor inconvenience.

- in my experience with SML, Haskell, and Clean, I have found that
  about half of the partial applications I want in practice are NOT
  supported by currying so that I have to use currying combinators
  or explicit lambda-expressions, so that curried Mercury would only
  save about half of the notational effort

- requiring people to use currying combinators like c3ac is NOT any
  kind of barrier to re-use, and if it were, Haskell would have the
  *same* barrier.

What about predicates?  Well, the average predicate has rather more
arguments than the average function.  Allowing predicate definitions
to be nested reduces that, but predicates still have somewhat more
than one argument more than functions, which exponentially increases
the number of potentially useful partial applications, and correspondingly
dilutes the practical utility of curried definitions.
--------------------------------------------------------------------------
mercury-users mailing list
post:  mercury-users at cs.mu.oz.au
administrative address: owner-mercury-users at cs.mu.oz.au
unsubscribe: Address: mercury-users-request at cs.mu.oz.au Message: unsubscribe
subscribe:   Address: mercury-users-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------



More information about the users mailing list