简体   繁体   中英

Curried function defined in terms of its own partial application

The following SML code is taken from a homework assignment from a course at University of Washington. (Specifically it is part of the code provided so that students could use it to complete Homework 3 listed on the course webpage .) I am not asking for homework help here–I think I understand what the code is saying. What I don't quite understand is how a curried function is allowed to be defined in terms of its own partial application.

 datatype pattern = 
     WildcardP
   | VariableP of string
   | UnitP
   | ConstantP of int
   | ConstructorP of string * pattern
   | TupleP of pattern list

fun g f1 f2 p =
  let 
    val r = g f1 f2      (* Why does this not cause an infinite loop? *)
  in
    case p of
        WildcardP         => f1 ()
      | VariableP x       => f2 x
      | ConstructorP(_,p) => r p
      | TupleP ps         => List.foldl (fn (p,i) => (r p) + i) 0 ps
      | _                 => 0
  end

The function binding is a recursive definition that makes use of the recursive structure in the datatype binding for pattern . But when we get to the line val r = g f1 f2 , why doesn't that cause the execution to think, "wait, what is g f1 f2 ? That's what I get by passing f2 to the function created by passing f1 to g . So let's go back to the definition of g " and enter an infinite loop?

The function g is never recursively called with any value other than f1 and f2 . It stands to reason that g f1 f2 will always have the same result, no matter how many times it is called.

I suggest reading the Staging section of the section on currying at https://smlhelp.github.io/book/ .

A simpler, boiled down example of this same thing in action. A contrived function that counts an int down until a supplied function returns true.

fun x (f: int -> bool) (a: int) : int =
  let 
    val g = x f
  in
    if a = 0 then 0
    else if f a then a
    else g (a - 1)  
  end

We can see the same translating this to OCaml as well.

let rec x f a =
  let g = x f in
  if a = 0 then 0
  else if f a then a
  else g (a - 1)

With lambda calculus abstraction, the curried function g is g = (^f1. (^f2. (^p. ...body...))) and so

g a b = (^f1. (^f2. (^p. ...body...))) a      b 
      =       (^f2. (^p. ...body...)) [a/f1] 
      =             (^p. ...body...)  [a/f1] [b/f2]

Partial application just produces the inner lambda function paired with the two parameters bindings closed over -- without entering the body at all.

So defining r = g f1 f2 is just as if defining it as the lambda function, r = (^p. g f1 f2 p) (this, again, is LC-based, so doesn't deal with any of the SML-specific stuff, like types).

And defining a lambda function (a closure) is just a constant-time operation, it doesn't enter the g function body, so there is no looping at all, let alone infinite looping.

These are both equivalent, and neither makes it look like there could be an infinite recursion:

You can substitute r with its value in the function,

fun g f1 f2 p =
    case p of
        WildcardP         => f1 ()
      | VariableP x       => f2 x
      | ConstructorP(_,p) => g f1 f2 p
      | TupleP ps         => List.foldl (fn (p,i) => (g f1 f2 p) + i) 0 ps
      | _                 => 0

or "lift" the pattern matching,

fun g f1 _ WildCardP = f1 ()
  | g _ f2 (VariableP x) = f2 x
  | g f1 f2 (ConstructorP (_, p)) = g f1 f2 p
  | g f1 f2 (TupleP ps) = List.foldl (fn (p,i) => (g f1 f2 p) + i) 0 ps
  | g _ _ _ = 0 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM