1

I have a high-order function declaration to apply a function given as an argument twice:

twice :: (a -> a) -> a -> a
twice f x = f (f x)

The confusion comes from this GHCi session:

*Main> let _4 = twice twice
*Main> let __4 = twice twice (*2)
*Main> let _16 = _4 _4
*Main> let __16 = _4 __4
*Main> _16 (*2) 2
231584178474632390847141970017375815706539969331281128078915168015826259279872
*Main> __16 2
131072

It is kinda clear with __16, because what's going on is just "multiplying" of this function call, so we'll actually get (2 ^ 16) * 2 after it's invocation. As far as I can understood it happens because function given as a parameter is already partially applied, so the type of either __4 and __16 is (Num a) => a -> a.

But the result of invocation of _16 with given function and integer arguments just leads me into confusion. I can understand that the type of either _4 and _16 are raw (are equal to twice function's signature, but are nested under the hood), but it gives me no clue about why the results differ so much. I just can't get program's semantics after providing a function that is not partially applied as an argument. Can somebody please explain why is this number just SO GREAT?

David Young
  • 10,713
  • 2
  • 33
  • 47
nyarian
  • 4,085
  • 1
  • 19
  • 51

3 Answers3

2

With Church numerals, the application of two numerals a b is equivalent to the exponential b^a. So, _4 _4 corresponds to 4^4=256, not 16.

Roughly: _4 f means f . f . f . f, i.e. "doing f four times, or "multiplying f by four". So, _4 _4 f means "multiplying f by four, four times", hence 4^4.

Indeed:

> 2^256 * 2 :: Integer
231584178474632390847141970017375815706539969331281128078915168015826259279872
chi
  • 111,837
  • 3
  • 133
  • 218
2

Looking at a reducing __16 2 a bit:

__16 2 = _4 __4 2
       = (twice twice) (twice twice (*2)) 2
       = twice (twice (twice twice (*2)) 2
       = twice (twice (twice (twice (*2)) 2

compared to

_16 (*2) 2 = _4 _4 (*2) 2
           = (twice twice) (twice twice) (*2) 2
           = twice (twice (twice twice)) (*2) 2

Notice with the __16 version, you are just directly doubling the number of times you apply (*2) with each twice. If you look carefully, you will see that the _16 version is parenthesized slightly differently. You are first doubling the doubling operation itself, and then you are applying that to (*2) and 2.

The first few steps of reducing _16 (*2) 2 might look like this, following from above

     twice (twice (twice twice)) (*2) 2
   = twice (twice (\x -> twice (twice x))) (*2) 2
   = twice (\z -> (\x -> twice (twice x)) ((\y -> twice (twice y)) z)) (*2) 2
   = twice (\z -> (\x -> twice (twice x)) (twice (twice z))) (*2) 2
   = twice (\z -> twice (twice (twice (twice z)))) (*2) 2
   = (\z -> twice (twice (twice (twice z)))) ((\w -> twice (twice (twice (twice w)))) (*2)) 2
   = ((\z -> twice (twice (twice (twice z)))) (twice (twice (twice (twice (*2)))))) 2
   = twice (twice (twice (twice (twice (twice (twice (twice (*2)))))))) 2
   = ...

The innermost twice (*2) gives you two (*2)s. The next twice doubles that to 4 (*2)s, the one after that doubles that again to 8 (*2)s and so on. There are eight twices in the above expression, so you end up with 2^8 = 256 (*2)s, so the result is 2 * (2^(2^8)) = 2 * (2^256) = 231584178474632390847141970017375815706539969331281128078915168015826259279872.

David Young
  • 10,713
  • 2
  • 33
  • 47
  • Seems I've got it, finally. We'll get `twice^8`, which is equal to `16 times f(x)`, where `f = (*2)` and the argument is `2`. Thanks! This description it clear enough for a newbie like me! – nyarian Oct 29 '18 at 11:11
  • @AndreyIlyunin no, `16 times (*2) is (* (2^16)) = (* 65536)` – Will Ness Oct 29 '18 at 11:15
  • Oops, yes, messed up with numbers. From the last reducing step it is clear that `twice` will be applied at eight levels, so twice will be invoked `2^8` times. So with the argument we'll get `2^(2^8)` with a single `*2` added from the argument itself. Thus, we'll have `2^257` – nyarian Oct 29 '18 at 11:21
2

twice isn't "twice", it's "squared" :

(^.) :: (a -> a) -> Int -> (a -> a)
(f^.n) x = foldr ($) x [f | _ <- [1..n]]   

((^.m) . (^.n)) f x = ((f^.n)^.m) x     
                  = foldr ($) x [f^.n | _ <- [1..m]]
                  = (f^.(m * n)) x
                  = (^.(m * n)) f x      

twice = (^.2)      -- f^.2 = f . f      f squared

_4   = twice twice
_4 f = (^.2) (^.2) f = ((^.2) . (^.2)) f = (f^.2)^.2 = f^.4    
_4   = (^.4)

       (^.3) (^.3) f = ((^.3) . (^.3) . (^.3)) f =
                     = ((^.3) . (^.3)) (f^.3)
                     =  (^.3) ((f^.3)^.3)
                     =   ((f^.3)^.3)^.3 = f^.(3*3*3) = f^.(3^3) = f^27 

       (^.4) (^.3) f = (((f^.3)^.3)^.3)^.3 = f^.(3*3*3*3) = f^.(3^4) = f^81

And in general,

       (^.m) (^.n) f = f^.(n^m)     

Functional composition is multiplication, and application is (reverse) exponentiation.

Thus we have

_16 f x = _4 _4 f x = (^.4) (^.4) f x = (f^.(4^4)) x = (f^.256) x

_16 (*2) 2 = ((*2)^.256) 2 = (* (2^256)) 2 = 2^257

*Main> _16 (*2) 2
231584178474632390847141970017375815706539969331281128078915168015826259279872

*Main> 2^257
231584178474632390847141970017375815706539969331281128078915168015826259279872
Will Ness
  • 70,110
  • 9
  • 98
  • 181