Clojure
Getting started with Clojure for writing rules in LoanPro’s Automation Engine.
Clojure (pronounced like 'closure') is the programming language used to express rules in LoanPro’s Automation Engine (as well as trigger-based notifications and a few other areas of the software).
Most clients have LoanPro help write their rules, but some basic familiarity with the language can also help you communicate those requests to our team.
I'm a programmer. Can you tell me a little more technical info about why you use Clojure?
With so many options available for automation and system rules, you might ask why LoanPro chose Clojure. LoanPro could have created their own rules language or chosen a more mainstream language like Python. At LoanPro, we considered many options, and ultimately chose to use Clojure for these key reasons:
- Clojure is consistent because of its predictable structure and data immutability
- Clojure is performant
- Clojure provides all the functionality needed to create both simple and complex rules
- Clojure has a simple core for ease of learning and use
- Clojure rules can be executed securely within the LoanPro platform
Basic syntax
A Clojure formula hinges on operands, operators, and a whole bunch of parentheses.
- An operand is a number, value, or variable.
- An operator indicates an action performed on the operands.
For instance:
Standard Math | Clojure |
1 + 2 = | (+ 1 2) |
1 + 2 + 4 + 5 + 5 + 6 = | (+ 1 2 4 5 5 6) |
((3 + 6 + 10 + 18) * (5 - 3 - 2 - 1) * (10 * 5 * 5 * 4 * 2)) = | (* (+ 3 6 10 18) (- 5 3 2 1) (* 10 5 5 4 2)) |
In the Clojure formula (+ 1 2) above, we have two operands, 1 and 2, and one operator, +, all enclosed in a set of parentheses. The operator always comes first, and tells you what you'll be doing to each of the operands that follow. When we're adding more than two operands, we still only need one operator.
With the longer examples, you might see how Clojure is more efficient than standard math notation. Small equations (+ 1 2) and (1 + 2) look about the same, but the difference is more noticeable on larger equations.
Taking a closer look at that example, you'll notice how Clojure deals with multilayered equations. With a standard math equation, we might write (15 + 22) * (35 - 65), where parentheses show us that we should solve the two smaller questions before multiplying them together. Clojure similarly uses parentheses to show the order each operation should be performed in. This equation would be written (* (+ 15 22) (- 35 65)). Clojure works from the inside out: We first solve the two inner problems, then multiply them together.
With an understanding of basic Clojure syntax, you’re ready to learn how Clojure works in LMS. Clojure formulas become useful for automations when you add two other considerations:
- Operands don't have to be a static number. If you put a variable in a rule, the system will pull values from a specific loan or customer when it evaluates their account.
- Operators aren't limited to basic math. There's also comparison, logic, and utility functions.
Variables
Let's use the example (+ 1 x). In this rule, x is a variable, a placeholder for another value. As you plug in different values for X, the formula will give different answers. In LMS, there are dozens of values related to loans and customers, and we can use a variable to pull these values from the system. For example, let's write a Clojure formula that will give us the sum of the next payment and the amount that's already past due.
(+ status-next-payment-amount status-amount-due)
These variables are long and descriptive enough that you can tell at a glance what each means, but from a math perspective, this formula is as simple as (+ x y). When this formula runs on a loan, the system will pull the actual numbers and solve the equation, giving us a dollar amount as our answer.
While those two variables pull numbers (in this case, a dollar amount), others will pull dates or even text. The variable loan-status, for example, will return something like "Open - Repaying", depending on how your loan statuses are configured.
We maintain an article with a full list of available variables.
Other operators
So far, we've only seen examples with basic math functions as the operators. But there are also operators that perform different kinds of functions. Some return a number, like our math functions, but others will return a true-or-false value. These operators are used for the Automation Engine: When a rule evaluates to true, the system will apply an effect on the loan.
Comparisons
Comparisons refer to basic valuations of whether one value is bigger, smaller, or equal to the next. The basic comparisons include greater than >, less than <, greater than or equal to >=, and less than or equal to <=. There's also equality = and inequality not=. All of these operators can work on multiple operands, and each will will return a true-or-false answer.
Standard Math | Formula | Answer | Explanation |
5 > 4 > 3 | (> 5 4 3) | True | 5 is greater than 4, which in turn is greater than 3. |
5 ≥ 3 ≥ 4 | (>= 5 3 4) | False | 5 is greater than (or equal to) 3, but 3 is less than 4. The formula is false. |
1 < 2 < 2 | (< 1 2 2) | False | 1 is less than two, but two and two are equal. The numbers aren't in a strictly ascending order, so it's false. |
1 ≤ 2 ≤ 2 | (<= 1 2 2) | True | Unlike the last example, the less than or equal to <= operator will accept a series of numbers that are equal. As long as a number isn't greater than the one after it, the formula is true. |
(3 + 4) = (14 / 2) = 7 | (= (+ 3 4) (/ 14 2) 7) | True | 3 plus 4 is 7, 14 divided by 2 is 7, and 7 is of course equal to 7. All the operands are equal, so the formula is true. |
(3 + 4) ≠ (14 / 2) ≠ 7 | (not= (+ 3 4) (/ 14 2) 7) | False | If an equality = formula is true, its inequality not= counterpart is false. |
Logic
Logical operators can make judgements about their operands, which are always formulas. They include and, or, and not. The and operator evaluates to true if all of it's operands are true formulas. The or operator, on the other hand, will evaluate as true if at least one of the operands is true. Compare these two formulas:
English Equivalent | Formula | Answer | Explanation |
The equations 2 = 2 and 3 = 4 are both true. | (and (= 2 2) (= 3 4)) | False | This formula asks whether both the operands are true. Two does equal two, but three does not equal four, so the whole formula evaluates as false. |
At least one of the equations 2 = 2 or 3 = 4 is true. | (or (= 2 2) (= 3 4)) | True | This formula asks whether at least one of the operands is true. Three doesn't equal four, but two does equal two, so the whole formula evaluates as true. |
The not operator works a little differently, only evaluating a single operand. A not formula will be true if the operand is false.
English Equivalent | Formula | Answer | Explanation |
The equation 3 = 4 is not true. | (not (= 3 4) | True | Three doesn't equal four, so this not formula is true. |
A practical use for not formulas is writing a rule that excludes a specific loan status or portfolio:
(not (= loan-status "Closed - Paid Off"))
In that formula, loans with that status will evaluate as false, and the affect won't apply to them.
Utility functions
Utility functions perform special operations. They always start with v/ and will return different types of answers depending on the function.
Text to a whole number
You can extract a whole number from a string of text using the “v/parse-int” operator. Text is often enclosed in double quotes, and dates are automatically converted to text when needed.
Clojure | Result | Explanation |
(v/parse-int "80") | 80 | The operator turns the string "80" into just a number, 80. (You can't perform math operations on a string, so this converts a string to a number) |
(v/parse-int "2.984938") | 2 | This operator will only return a whole number, so it cuts off anything following a decimal. |
(v/parse-int "L42-20132") | 42 | It will look for the first whole number it can, skipping past text and ignoring everything after the number. |
(v/parse-int "2024-08-09") | 2024 | If used on a date, it'll only return the year. |
Text to a floating point number
The LoanPro Computation Rules also allow you to extract floating point numbers from a string of text using the “v/parse-float” operator. The examples used in the section above apply the same here but for floating point numbers, instead of whole numbers.
Clojure | Result | Explanation |
(v/parse-float "80.5") | 80.5 | The operator turns the string "80.5" into a number. |
Substring
The Substring operator (subs) evaluates a text operand and returns a smaller portion of it. The substring operator takes two to three operands: The text to use as a base, the starting character (the first character is 0, the second is 1, and so forth), and the ending character (if not provided then it will include everything to the right of the starting character).
Note that the ending character is exclusive; in other words, the result will not include the end character.
Clojure | Result | Explanation |
(subs "2022-02-14" 0 4) | 2022 | The operator starts at zero and goes four characters deep, then drops the rest. Note that the date will be a string, not a number. |
(subs "L212-5142b" 5) | 5142b | It starts after the fifth character and then includes everything that follows. This can be useful for redacting sensitive information, like contact info or account IDs. |
Maximum and minimum
The maximum (max) operator returns the highest operand given, and the minimum (min) operator returns the lowest. They can take any number of operands.
Clojure | Result | Explanation |
(max 35 192 32 -999 221 100 102 202) | 221 | 221 is the highest number of the bunch. |
(min 35 192 32 -999 221 100 102 202) | -999 | -999 is the lowest number. |
Positive and negative
The positive (pos?) and negative (neg?) operators look at a number and return either "True" or "False".
Clojure | Result | Explanation |
(pos? 4) | True | The pos? operator will return True for positive numbers. |
(neg? -5) | True | The neg? operator will return True for negative numbers. |
Dates
There's countless situations where you need to measure or calculate time on loans, but you can’t just add March 3rd to June 9th like a normal math equation. LoanPro has variables for dates and special functions that let you compare them.
LoanPro has two options for getting today’s date. The first is a special command:
(v/today :format :date)
This function, v/today, will get the current date from the system at the time the rule is evaluated. This date will be in UTC time, and is pulled from the LoanPro servers.
The other option is to use the current-date variable, which outputs the tenant time zone’s current date as a string.
Comparing dates
LoanPro uses the operator “v/date-compare” to find the number of days between two dates, subtracting the first date from the second date. If the second date is larger than the first date you will get a negative number; otherwise you will get a positive number.
So, let’s say you want to know how many days it’s been since the last payment date for a loan. You would enter the following:
(v/date-compare current-date last-payment-date)
If you want to know how many days until the next due date you would enter the following:
(v/date-compare current-date status-next-payment-date)
This would give me a positive number representing the number of days until the next due date for a loan, expressed as a negative number. If you flip the order of the dates, then you flip the sign of the result.
When you want to compute a specific date, make sure your output format is set to 'Date'. When you want to get how much time is between two dates, make sure your output format is set to 'Number'.
You can also use the operator "v/banking-days-between", which, as the name suggests, counts the number of banking days between two dates:
(v/banking-days-between current-date last-payment-date)
Adding and subtracting days from a date
Sometimes we want to know the date so many days before or after a different date. For example, maybe we want to know which date is five days past the next due date so we can send a late payment notice if the customer doesn’t pay on time. This is accomplished with two different operators, “v/date-+” to add days to a date and “v/date−−” to subtract days from a date. These operators require the first operand to be a date and the second operand to be a number. The following code examples would work:
✔ (v/date-+ current-date 5)
✔ (v/date-+ current-date (v/date-compare status-next-payment-date loan-create-time))
The following are examples of codes that don’t work:
✘ (v/date-+ 5 current-date)
✘ (v/date-+ current-date loan-create-time)
Dates before or after a specific day of the month
LoanPro provides two functions that let you evaluate whether a date is before or after a specific day of the month. These functions are:
(v/before-nth-of-month)
(v/after-nth-of-month)
Non-variable dates
Sometimes we want to compare dates to a specific point of time that won’t change based on loan settings. For example, we may want to know how many days have passed since February 23rd, 1994. To use a static or non-changing date, we must put it in quotation marks and have the year, then the month, and then the day. Once we do this, we can use it like any other date. For example, to use February 23rd, 1994 as a date, we would type:
“1994-02-23”
Rules Manager
Even once you've got the hang of Clojure, it can be time consuming to write out the same rule in several different places. The Rules Manager solves this problem—you can save a list of rules, and then pull from that list any time you're writing one.
In LMS, the rules manager is located in Settings > Company > Rules Manager.
You'll see a list of the existing rules saved in your system. These will be available anytime you're asked to write a rule. Each entry includes the name, the text of the rule itself, whether and how the answer is rounded, and the type of output it yields. To create a new rule, the 'Add' button in the top right corner. That'll bring up this window with fields for basic info, and one large field where you can enter your Clojure rule.
Was this article helpful?