简体   繁体   中英

foldr for squareOn - Haskell

In my lecture, we had to define the function squareOn such that

1

with foldr . The answer was

 squareOn :: (Eq a, Num a) => [a] -> a -> a        
 squareOn = foldr (\x acc y -> if y == x then x*x else acc y) id

I undestand how foldr works, but I'm new at lambda expressions in Haskell. Is acc any type of function from Haskell? It would be nice if someone could explain how squareOn works. :)

This is a sort-of advanced usage of foldr . Normally, we see foldr used as in

fun xs = foldr (\x acc -> something using x and acc) base xs

or equivalently

fun = foldr (\x acc -> something using x and acc) base

which corresponds to the following recursive function:

fun []     = base
fun (x:xs) = something using x and acc
   where acc = fun xs

Your case is a special case of this usage, where base , acc , and something using x and acc are functions . That is, we have

fun []     = \y -> base'
fun (x:xs) = \y -> something using x, acc, y
   where acc = \y -> fun xs y

Moving back to foldr , we get

fun = foldr (\x acc -> \y -> something using x, acc, y) (\y -> base')

which can also be written as

fun = foldr (\x acc y -> something using x, acc, y) (\y -> base')

where a somehow confusing three-argument function appears to be passed to foldr .

Your specific case,

squareOn = foldr (\x acc y -> if y == x then x*x else acc y) id

corresponds to the explicit recursion:

squareOn []     = id
squareOn (x:xs) = \y -> if y == x then x*x else acc y
   where acc = \y -> squareOn xs y

or

squareOn []     y = y
squareOn (x:xs) y = if y == x then x*x else squareOn xs y

which you should be able to understand.

Let's define this function without a lambda.

squareOn :: (Eq a, Num a) => [a] -> a -> a
squareOn = foldr f id
  where
    f x acc = g
      where
        g y | x == y = x * x
            | otherwise = acc y

Now it's become what foldr usually looks like. It takes a function taking two arguments f and an initial value id .

When you pass [2, 4] to squareOn , it'll be expanded to foldr f id [2, 4] , then f 2 (f 4 id) by the definition of foldr .

f 4 id returns a function that takes one argument y which returns 4 * 4 if y is 4 , and returns id y otherwise. Let's call this function p .

p y | 4 == y = 4 * 4
    | otherwise = id y

Now, f 2 (f 4 id) returns a function that takes one argument y which returns 2 * 2 if y is 2 , and returns py otherwise. When you name it q , it'll be like this.

q y | 2 == y = 2 * 2
    | otherwise = p y

So squareOn [2, 4] 3 , for example, is equivalent to q 3 .

Whoever skipped those explicit arguments just made it unnecessarily harder on yourself to learn this stuff. It's totally superficial. Adding the explicit arguments, as specified by the type signature, gives us

squareOn :: (Eq a, Num a) => [a] -> a -> a
squareOn      = foldr (\x acc y -> if y == x then x*x else acc y) id
squareOn xs   = foldr (\x acc y -> if y == x then x*x else acc y) id xs
squareOn xs y = foldr (\x acc y -> if y == x then x*x else acc y) id xs y
squareOn xs y = foldr g id xs y   where { g x acc y | y == x    = x*x 
                                                    | otherwise = acc y }
squareOn xs y = (case xs of { 
    []      -> id ;
    (x:xs2) -> g x (foldr g id xs2)
                          })  y   where { g x acc y | y == x    = x*x 
                                                    | otherwise = acc y }

Now we can see everything in play here, as opposed to having to keep it all in mind. There is playing chess, and then there's playing blindfold chess, and why play it blindfolded if you can just see?

So now it becomes obvious that passing that y around (*) from call to call unchanged actually has no purpose here, because it is the same y , and it is already in scope:

squareOn xs y = (case xs of { 
    []      -> y  ;
    (x:xs2) -> g x (foldr g y  xs2)
                          })      where { g x acc   | y == x    = x*x 
                                                    | otherwise = acc   }

which simplifies back as just

squareOn xs y = foldr g y  xs     where { g x acc   | y == x    = x*x 
                                                    | otherwise = acc   }
{- cf.
squareOn xs y = foldr g id xs y   where { g x acc y | y == x    = x*x 
                                                    | otherwise = acc y } -}

And to be pointlessly short and pointfree, like your original code,

squareOn      = flip (foldr g)    where { g x acc   | y == x    = x*x 
                                                    | otherwise = acc   }

Or it could be simplified to

squareOn xs y =  case xs of { 
    []             -> y   ;
    (x:_) | y == x -> x*x ;
    (_:xs2)        -> squareOn xs2 y }

and further to a worker/wrapper with nested unary worker, whichever is clearer for you.

Passing the unchanged quantity around to have it in scope is only really needed in languages without nested scope, like Prolog.

(*) (so that explanation in full, which you asked for, about how this technique works, is actually in the linked answer).

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