Messaging Apps powered by Machine Learning

Functions form the back-bone of any programming language, and pez is no different. What exactly is a function? At its most basic, a function is a black box that takes input and returns some output. In other words, a function is a consistent way to say “for some input x, give me y.” Functions are used to define specific behavior that can be reused in different situations. Not only do functions need a way to be defined, they also need a way to be applied to some input. This chapter discusses the basic mechanics of functions and moves on to more sophisticated uses of functions.

As a reminder, when practicing these examples yourself in Slack, you need to prepend each statement with !pez. This is a keyword that tells Panoptez to evaluate the following line as a pez statement. When we include !pez in an example, this is to distinguish the statement from the result, which will follow. If you haven’t signed up for the beta, do so now.

Function Definition

Functions are defined using a syntax that mimics ML. They begin with the keyword fn followed by the arguments to the function. Arguments are separated by white space. This is known as the signature of the function and is separated from the body by a colon. To illustrate, here’s an example with the successor function, which adds one to its input.

functions_anon

In this raw form, the function is anonymous since it’s not bound to a variable. In other words, despite defining the function, there is no way to reference it. This is like you having no name. How would someone indicate that she was talking about you? Perhaps she would refer to you as that person of medium height with a square face and dark hair. This might work in certain situations, but surely it’s easier if you have a unique name. The same idea applies to functions: it’s much easier to refer to them when they have a name. This is easily accomplished by assigning a function definition to a variable. To illustrate how this works, let’s assign our successor function to the variable succ.

functions_succ

Now we can use the function by applying it to a value.

We can even apply this function to a vector and get a corresponding vector back.

Some time later you might forget what the definition of a function is. Don’t worry, pez remembers. You can get this information using the source attribute on the function object.

Multiple arguments

Functions can have more than one argument. Here is the equation for calculating the hypotenuse of a triangle based on the length of the two legs. Notice how each argument simply follows the previous argument with white space in between. The same syntax is used if a function has two arguments or 20 arguments (although generally you don’t want functions with 20 inputs!).

Function arguments can be named whatever you like. For simple functions we tend to use single letters, while more complicated functions should have more descriptive variable names.

Default arguments

This section is for people with prior programming experience. Feel free to skip this and revisit later.

Pez does not support default arguments for functions. The reasoning is that functions form the grammar of a programming language. To properly communicate their behavior, the signatures need to be clear. As with written language, even though few are professional writers, there is nonetheless an expectation that an educated person is able to write properly. Without being pedantic, good grammar facilitates good communication. Functions are no different in that their naming and signatures facilitate communication. Unlike wine, a well-defined function signature is obvious and clear in its intent.

In contrast, default arguments have the opposite effect. They muddy meaning by providing too many choices. Often, one choice affects other choices, making it difficult to know exactly how a function will behave. When used sparingly, default arguments can subtly improve a function interface, but this is the exception rather than the rule.

Function Application

Using the successor function as an example, we showed how to define and also call a function. We also defined the function hypotenuse, which is evaluated similar to succ, except that now there are two values passed the function. The first value is bound to a, while the second value is bound to b. These are known as positional arguments because they are assigned to the function arguments based on their position in the function call.

You may have noticed that in these examples parantheses are not used when calling a function. This is not the only way to call a function. If using parantheses pleases you, the following syntax might suit you better.

From the interpreter’s perspective, there is no difference between the two syntaxes. Generally, parentheses are not required for simple function application. In certain situations, though, they may be necessary to help the interpreter disambiguate compound statements. This is because the interpreter is dumb. It relies on you to tell it what to do. For example, combining the successor function with the hypotenuse, the interpreter doesn’t know what the following statement means.

If you type this into Slack, you’ll get a mysterious error returned. What you need to do is give the interpreter a helping hand so it knows how to parse the statement. All of the following statements are valid. This first form tells the interpreter to group succ and 2 together as a single expression.

In this version, we call hypotenuse using parentheses, which gives the interpreter enough information to group the function calls properly.

This version is overkill and wraps each function call in parentheses! This is most explicit, but hardest to read. Do note that in certain cases, all parentheses are needed to ensure proper disambiguation.

When writing in pez, ultimately the choice is yours. That said, remember that code is communication, so you need to be able to understand how others write pez. You may encounter all of these styles, so you should be able to work comfortably with each.

Named arguments

When calling a function, positional arguments are sufficient in most situations. However, sometimes emphasizing a particular variable, or changing the order of variables can improve readability. In these cases, using named arguments is preferred. This style is only supported using explicit parentheses and commas separating arguments. Then we can explicitly specify the names of arguments.

As an example, suppose we want to model the lead growth of our business. To make a projection, we’ll assume a starting number of leads, a growth rate, and the number of periods to model. An expression that describes this is the sequence y_n = x \prod_i^n (1+r)^n. This is a standard expression for modeling compound growth and can be translated into pez like this:

The function can be called like the others in the chapter.

What if you forget the order of the arguments? In this case, it can be useful to specify the arguments explicitly.

While this example is rather contrived, in the chapter on advanced functions, we’ll see some examples that really take advantage of named arguments.

What’s Next

This chapter provides sufficient background to make use of functions in pez. From here, you can