Function decorators

Decorator syntax refers to the desugaring of

@g(...)
def f(...):
    ...

into

def f(...):
    ...
f = g(...)(f)

f is called the “decorated” function, and g is called the “decorator.”

Here we look at several examples using decorators in Python, compare them to similar situations in Haskell, and discuss how lambdas let us achieve the same result without having a special syntax for decorators.

Uses of decorators

We broadly categorize decorators into two kinds of uses:

  1. To make some alteration to the decorated function
  2. To perform some side effect during module initialization Although Python a module usually looks like a declarative list of functions, it’s important to remember that a module is actually a script that performs side effects (even I/O!) as it loads.

Example: Property testing

Hypothesis (Python)

The Hypothesis libraryHypothesis has a decorator called given. This is one that alters the decorated function.

In the example below, we want to verify the assertion that if you double a number x, the result will be greater than or equal to the input. We define a function test_double which expresses this.

from hypothesis import given
from hypothesis import strategies as st

@given(st.integers())
def test_double(x):
    assert x * 2 >= x

And we also applied the given decorator to it. This replaces the test_double function we wrote with a function that runs our function on many inputs in search of an input for which the assertion fails.

>>> test_double()
Falsifying example: test_double(x = -1)

And we see it did find a counterexample: The property we’re verifying was wrong, because it doesn’t hold for negative numbers.

QuickCheck (Haskell)

QuickCheck has a function called quickCheckquickCheck which serves a similar role: The actual type of quickCheck is more general than this, but here we give a specialized type for simplicity.

quickCheck :: Arbitrary a => (a -> Bool) -> IO ()

quickCheck transforms a function which expresses the property into an action which checks the property.

testDouble =
  quickCheck $ \x ->
    x * 2 >= x

And we can run it and see that we get a similar result:

λ> testDouble
*** Failed! Falsifiable (after 7 tests and 2 shrinks):
-1

Why aren’t we suffering from lack of a decorator syntax in the Haskell example? Because we were able to express the property as a lambda

\x -> x * 2 >= x

rather than as a named function

testDouble x =
  x * 2 >= x

and we can pass a lambda directly as an argument to the quickCheck function without any fuss.

Example: Web routing

Flask (Python)

With Flask,Flask we create a web server by first constructing a Flask instance and then adding routes via its route method.route

from flask import *

app = Flask("hello_world")

@app.route("/", methods = ["GET"])
def hello():
    return "Hello World!\n"

@app.route("/hello/<name>", methods = ["GET"])
def hello_name(name):
    return("Hello " + name + "!\n")
>>> app.run(port = 8000)
 * Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)
$ curl http://localhost:8000
Hello World!

$ curl http://localhost:8000/hello/Julie
Hello Julie!

Because Python has no syntax for writing multi-line anonymous functions, we needed two declarations to create each route –

  1. Define the route’s behavior by declaring a named function
  2. Apply app.route to the named function to create the route

– but decorator syntax gave us a nice concise way to perform the second step without having to repeat the name of the function.

Scotty (Haskell)

Now let’s build the same web server using Scotty.The scotty package on Hackage

We will use the addroute functionaddroute which serves a similar role to Flask.route:

addroute :: StdMethod -> RoutePattern -> ActionM () -> ScottyM ()
import Data.Semigroup
import Network.HTTP.Types.Method
import Web.Scotty

app :: ScottyM ()
app =
  do
    addroute GET "/"
      do
        text "Hello World!"
    addroute GET "/hello/:name" $
      do
        name <- param "name"
        text ("Hello " <> name <> "!\n")
λ> scotty 8000 app
Setting phasers to stun... (port 8000) (ctrl-c to quit)

In contrast with the Python server, the Haskell routes didn’t require two steps. This is because we didn’t need to name the route behaviors; we just passed them directly as arguments to Scotty’s addroute function.

Example: Timing

Timing decorator (Python)

For this example we’ll write our own decorator. The stopwatch decorator modifies a function so that when the function runs, it also prints a line that says how long it took.

from datetime import datetime
import time

def stopwatch(name):
    def f(action):
        def g(x):
            start = datetime.now()
            result = action(x)
            end = datetime.now()
            print(name + ": " + str(end - start))
            return result
        return g
    return f

@stopwatch("delay")
def delay(x):
    time.sleep(x)
>>> delay(1)
delay: 0:00:01.001225

Timing function (Haskell)

Now we’ll do the same thing in Haskell.Control.Concurrent We’ll be using the Control.Concurrent module from base, andtime the Data.Time.Clock module from the time package.

import Control.Concurrent
import Data.Time.Clock

stopwatch :: String -> (a -> IO b) -> (a -> IO b)
stopwatch name action x =
  do
    start <- getCurrentTime
    result <- action x
    end <- getCurrentTime
    putStrLn (name ++ ": " ++ show (end `diffUTCTime` start))
    return result

delay :: Int -> IO ()
delay = stopwatch "delay" $ \x ->
    threadDelay x
λ> delay 1000000
delay: 1.001164885s

Why is the “decorator” (the stopwatch function) more concise in Haskell? Because Python requires a decorator to be curried – it is used via three separate invocations

stopwatch(name)(action)(x)

rather than taking all arguments at once

stopwatch(name, action, x)

– but Python syntax doesn’t easily support currying, so we had to use some funny workarounds like declaring a named function f and then returning it.

Using a higher-order function in Haskell is extremely similar to using a decorator in Python. That’s because that’s almost exactly what a decorator is. The Python glossaryPython glossary: ‘decorator’ entry for “decorator” actually defines it as: We find this definition slightly inaccurate; more precisely, a Python decorator is a function that accepts a function argument, and usually (but not necessarily) returns a function.

A function returning another function

So it should be no surprise that this is something at which Haskell excels!

To emphasize the similarity between decorators in Python and higher-order functions in Haskell, let’s put the final examples side-by-side to see how close they are:

@stopwatch("delay")
def delay(x):
    time.sleep(x)
delay = stopwatch "delay" $ \x ->
    threadDelay x

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.