vitorsalmeidaThoughts. Learning about math and programming. Currently working as a Software Engineer at MeteorJS ☄️https://vitorsalmeida.com/Building a JSON Parser from scratch with JShttps://vitorsalmeida.com/building-json-parser-from-scratch/https://vitorsalmeida.com/building-json-parser-from-scratch/Sat, 12 Aug 2023 00:00:00 GMT<h2>Introduction</h2>
<p>A parser can have various applications in everyday life, and you probably use some parser daily. <a href="https://babeljs.io/">Babel</a>, <a href="https://webpack.js.org/">webpack</a>, <a href="https://eslint.org/">eslint</a>, <a href="https://prettier.io/">prettier</a>, and <a href="https://github.com/facebook/jscodeshift">jscodeshift</a>. All of them, behind the scenes, run a parser that manipulates an Abstract Syntax Tree (AST) to do what you need - we'll talk about that later, don't worry.</p>
<p>The idea of this text is to introduce the concept of lexing and parsing, implementing them using JavaScript to analyze expressions in JSON. The goal will be to separate this process into functions, explain these functions, and, in the end, have you implement a JSON parser generating an AST.</p>
<p>It's worth noting that my repository is open, and you can access it <a href="https://github.com/vit0rr/json-parser">here</a>.</p>
<h2>Lexing</h2>
<p>A <code>lexer</code> will be responsible for converting an expression, whatever it may be, into tokens. These tokens are identifiable elements that have an assigned meaning.</p>
<p>You can divide these tokens in several ways, such as <a href="https://en.wikipedia.org/wiki/Identifier_(computer_languages)">identifier</a>, <a href="https://en.wikipedia.org/wiki/Reserved_word">keyword</a>, <a href="https://en.wikipedia.org/wiki/Delimiter">delimiter</a>, <a href="https://en.wikipedia.org/wiki/Operator_(computer_programming)">operator</a>, <a href="https://en.wikipedia.org/wiki/Literal_(computer_programming)">literal</a>, and <a href="https://en.wikipedia.org/wiki/Comment_(computer_programming)">comment</a>.</p>
<p><code>{ "type": "LEFT_BRACE", "value": undefined }</code> is an example of a delimiter. <code>{ "type": "STRING", "value": "name" }</code> is an example of a literal.</p>
<p>Example: <code>{"name":"Vitor","age":18}</code></p>
<pre><code>[
{ "type": "LEFT_BRACE", "value": undefined },
{ "type": "STRING", "value": "name" },
{ "type": "COLON", "value": undefined },
{ "type": "STRING", "value": "Vitor" },
{ "type": "COMMA", "value": undefined },
{ "type": "STRING", "value": "age" },
{ "type": "COLON", "value": undefined },
{ "type": "NUMBER", "value": "18" },
{ "type": "RIGHT_BRACE", "value": undefined }
]
</code></pre>
<p>Note that in lexical analysis, you separate your expression into tokens, and each token has its identification.</p>
<p>To code this, let's first understand what we will be doing exactly. The idea of the <code>lexer</code> function is to receive an argument of type String and return an Array of tokens, which will be our JSON divided into specific types of information, as we have already seen and discussed.</p>
<p>To achieve this, we will create a variable called <code>current</code>, which will store the current position of the character in the <code>input</code> being analyzed by the <code>lexer</code>. In other words, it represents the position it is currently at in our JSON. Additionally, we will have a constant called <code>tokens</code>, which will be an array that will hold all of our tokens in the end.</p>
<pre><code>export const lexer = (input: string): Token[] => {
let current = 0;
const tokens: Token:[] = [];
}
</code></pre>
<p>Now, we need to run a loop that will iterate until all the characters of the input have been processed.</p>
<blockquote>
<p>Note that it's possible to refactor all these <code>if</code> blocks into a <code>switch</code> statement. However, I followed an imperative style.</p>
</blockquote>
<pre><code>export const lexer = (input: string): Token[] => {
let current = 0
const tokens: Token[] = []
while (current < input.length) {
let char = input[current]
if (char === '{') {
tokens.push(createToken(TOKEN_TYPES.LEFT_BRACE))
current++
continue
}
if (char === '}') {
tokens.push(createToken(TOKEN_TYPES.RIGHT_BRACE))
current++
continue
}
if (char === '[') {
tokens.push(createToken(TOKEN_TYPES.LEFT_BRACKET))
current++
continue
}
if (char === ']') {
tokens.push(createToken(TOKEN_TYPES.RIGHT_BRACKET))
current++
continue
}
if (char === ':') {
tokens.push(createToken(TOKEN_TYPES.COLON))
current++
continue
}
if (char === ',') {
tokens.push(createToken(TOKEN_TYPES.COMMA))
current++
continue
}
const WHITESPACE = /\s/
if (WHITESPACE.test(char)) {
current++
continue
}
const NUMBERS = /[0-9]/
if (NUMBERS.test(char)) {
let value = ''
while (NUMBERS.test(char)) {
value += char
char = input[++current]
}
tokens.push(createToken(TOKEN_TYPES.NUMBER, value))
continue
}
if (char === '"') {
let value = ''
char = input[++current]
while (char !== '"') {
value += char
char = input[++current]
}
char = input[++current]
tokens.push(createToken(TOKEN_TYPES.STRING, value))
continue
}
if (
char === 't' &&
input[current + 1] === 'r' &&
input[current + 2] === 'u' &&
input[current + 3] === 'e'
) {
tokens.push(createToken(TOKEN_TYPES.TRUE))
current += 4
continue
}
if (
char === 'f' &&
input[current + 1] === 'a' &&
input[current + 2] === 'l' &&
input[current + 3] === 's' &&
input[current + 4] === 'e'
) {
tokens.push(createToken(TOKEN_TYPES.FALSE))
current += 5
continue
}
if (
char === 'n' &&
input[current + 1] === 'u' &&
input[current + 2] === 'l' &&
input[current + 3] === 'l'
) {
tokens.push(createToken(TOKEN_TYPES.NULL))
current += 4
continue
}
throw new TypeError('I dont know what this character is: ' + char)
}
return tokens
}
</code></pre>
<p>This code may seem complicated, but it's actually quite simple. Inside my loop, I start by defining the variable <code>char</code>, which will store the character currently being analyzed in that iteration of the loop. Then, for each type of character, we want a specific action.</p>
<p>If the <code>char</code> is equal to <code>{</code>, we push into the tokens array, passing to it the ENUM of Tokens, which will be the name of each token.</p>
<pre><code>export enum TOKEN_TYPES {
LEFT_BRACE = 'LEFT_BRACE',
RIGHT_BRACE = 'RIGHT_BRACE',
LEFT_BRACKET = 'LEFT_BRACKET',
RIGHT_BRACKET = 'RIGHT_BRACKET',
COLON = 'COLON',
COMMA = 'COMMA',
STRING = 'STRING',
NUMBER = 'NUMBER',
TRUE = 'TRUE',
FALSE = 'FALSE',
NULL = 'NULL',
}
</code></pre>
<p>In the function <code>createToken</code>, it only returns an object with the <code>type</code> or <code>value</code> if it exists.</p>
<pre><code>export const createToken = (type: TOKEN_TYPES, value?: string): Token => {
return {
type,
value,
}
}
</code></pre>
<p>After that, it increments +1 to the <code>current</code> variable to move to the next <code>char</code> in our string. This process is quite repetitive and straightforward. I won't explain each one of them, but it's worth paying attention to those that deviate a bit from the pattern.</p>
<pre><code>if (char === '"') {
let value = ''
char = input[++current]
while (char !== '"') {
value += char
char = input[++current]
}
char = input[++current]
tokens.push(createToken(TOKEN_TYPES.STRING, value))
continue
}
if (
char === 't' &&
input[current + 1] === 'r' &&
input[current + 2] === 'u' &&
input[current + 3] === 'e'
) {
tokens.push(createToken(TOKEN_TYPES.TRUE))
current += 4
continue
}
if (
char === 'f' &&
input[current + 1] === 'a' &&
input[current + 2] === 'l' &&
input[current + 3] === 's' &&
input[current + 4] === 'e'
) {
tokens.push(createToken(TOKEN_TYPES.FALSE))
current += 5
continue
}
if (
char === 'n' &&
input[current + 1] === 'u' &&
input[current + 2] === 'l' &&
input[current + 3] === 'l'
) {
tokens.push(createToken(TOKEN_TYPES.NULL))
current += 4
continue
}
</code></pre>
<p>If the <code>char</code> is equal to <code>"</code>, we enter a loop to continue reading the subsequent characters until we find another quotation mark, as this indicates the end of the string. All the characters read during this loop are concatenated into the "value" variable. Then, a new token is added to the "tokens" array.</p>
<p>The other lines are used to set boolean values. If the current character is "f" and the subsequent characters form the word <code>false</code>, we add false to the <code>tokens</code> array. The same process is repeated for <code>true</code>.</p>
<p>In all cases, the <code>current</code> variable is incremented to point to the next character to be processed, just like we did throughout the rest of our code.</p>
<h2>Parsing</h2>
<p>A parser is responsible for transforming a sequence of tokens into a data structure, in this case, an <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">AST</a>.</p>
<blockquote>
<p>Illustration of an AST taken from the book "Modern Compiler Implementation in ML."</p>
</blockquote>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yafyeyuq1phdzbjhhnud.png" /></p>
<p>An Abstract Syntax Tree (AST) is a data structure that represents the syntactic structure of a program. Within the AST, there are several nodes, and each node represents a valid syntactic construct of the program. For example:</p>
<pre><code>parser: {
"type": "Program",
"body": [
{
"type": "ObjectExpression",
"properties": [
{
"type": "Property",
"key": {
"type": "STRING",
"value": "name"
},
"value": {
"type": "StringLiteral",
"value": "Vitor"
}
},
{
"type": "Property",
"key": {
"type": "STRING",
"value": "age"
},
"value": {
"type": "NumberLiteral",
"value": "18"
}
}
]
}
]
}
</code></pre>
<p>This is the AST of the JSON I exemplified at the beginning. In this example, we have a total of 8 nodes. The <code>Program</code> node represents the main program. <code>ObjectExpression</code> represents an object, <code>Property</code> represents a property within an object, consisting of a key and a value. <code>STRING</code> represents a string used as a key, StringLiteral represents a string within a property, and <code>NumberLiteral</code> represents a numeric value within a property.</p>
<p>Through an AST, it's possible to optimize code, transform one code into another, perform static analysis, generate code, and more. For example, you could implement a new syntax, create a parser, and generate JavaScript code that would execute normally.</p>
<p>To generate our AST, we will need a function that receives our array of tokens, iterates through it, and generates the AST according to the tokens it encounters. For this purpose, we will create a function called <code>walk</code>, which will traverse the tokens and return the nodes of the AST.</p>
<pre><code>export const parser = (tokens: Array<{ type: string; value?: any }>) => {
let current = 0;
const walk = () => {
let token = tokens[current];
if (token.type === TOKEN_TYPES.LEFT_BRACE) {
token = tokens[++current];
const node: {
type: string;
properties?: Array<{ type: string; key: any; value: any }>;
} = {
type: 'ObjectExpression',
properties: [],
};
while (token.type !== TOKEN_TYPES.RIGHT_BRACE) {
const property: { type: string; key: any; value: any } = {
type: 'Property',
key: token,
value: null,
};
token = tokens[++current];
token = tokens[++current];
property.value = walk();
node.properties.push(property);
token = tokens[current];
if (token.type === TOKEN_TYPES.COMMA) {
token = tokens[++current];
}
}
current++;
return node;
}
</code></pre>
<p>The first check we perform is whether the current token is <code>{</code>. If it is, we create a new node of type <code>ObjectExpression</code> and iterate through the following tokens, adding them as properties of the object until we find the end of the key, which is <code>}</code>. Each property is represented by a node of type <code>Property</code>. This <code>Property</code> type has a value that is generated by the <code>walk()</code> function, which is called recursively.</p>
<p>Note that I use <code>tokens[++current]</code>. This is to advance the cursor to the next token. And if a token of type , (comma) is found, we advance the cursor again to skip the comma.</p>
<p>The rest of the code is quite similar to what I've just explained, so it's worth the effort to look and try to understand or implement the rest on your own. It's not very complex.</p>
<p>Finally, I create the constant <code>ast</code>, which will contain the type <code>Program</code> and the body of the AST, generated by the <code>walk()</code> function. The while loop ensures that <code>current</code> does not exceed the size of the tokens array.</p>
<p>After that, we simply return the AST.</p>
<pre><code>export const parser = (tokens: Array<{ type: string; value?: any }>) => {
let current = 0
const walk = () => {
let token = tokens[current]
if (token.type === TOKEN_TYPES.LEFT_BRACE) {
token = tokens[++current]
const node: {
type: string
properties?: Array<{ type: string; key: any; value: any }>
} = {
type: 'ObjectExpression',
properties: [],
}
while (token.type !== TOKEN_TYPES.RIGHT_BRACE) {
const property: { type: string; key: any; value: any } = {
type: 'Property',
key: token,
value: null,
}
token = tokens[++current]
token = tokens[++current]
property.value = walk()
node.properties.push(property)
token = tokens[current]
if (token.type === TOKEN_TYPES.COMMA) {
token = tokens[++current]
}
}
current++
return node
}
if (token.type === TOKEN_TYPES.RIGHT_BRACE) {
current++
return {
type: 'ObjectExpression',
properties: [],
}
}
if (token.type === TOKEN_TYPES.LEFT_BRACKET) {
token = tokens[++current]
const node: {
type: string
elements?: Array<{ type?: string; value?: any }>
} = {
type: 'ArrayExpression',
elements: [],
}
while (token.type !== TOKEN_TYPES.RIGHT_BRACKET) {
node.elements.push(walk())
token = tokens[current]
if (token.type === TOKEN_TYPES.COMMA) {
token = tokens[++current]
}
}
current++
return node
}
if (token.type === TOKEN_TYPES.STRING) {
current++
return {
type: 'StringLiteral',
value: token.value,
}
}
if (token.type === TOKEN_TYPES.NUMBER) {
current++
return {
type: 'NumberLiteral',
value: token.value,
}
}
if (token.type === TOKEN_TYPES.TRUE) {
current++
return {
type: 'BooleanLiteral',
value: true,
}
}
if (token.type === TOKEN_TYPES.FALSE) {
current++
return {
type: 'BooleanLiteral',
value: false,
}
}
if (token.type === TOKEN_TYPES.NULL) {
current++
return {
type: 'NullLiteral',
value: null,
}
}
throw new TypeError(token.type)
}
const ast = {
type: 'Program',
body: [],
}
while (current < tokens.length) {
ast.body.push(walk())
}
return ast
}
</code></pre>
<pre><code>const tokens = lexer('{"name":"Vitor","age":18}')
console.log('tokens', tokens)
const json = parser(tokens)
console.log('parser:', JSON.stringify(json, null, 2))
</code></pre>
<p>Parsing can be fun, but in reality, it's not often written, and you probably don't need to write your own parser. If you are implementing a programming language, for example, there are already various tools that can do this job for you, such as OCamllex, Menhir, or Nearley.</p>
<p>It's also essential to note that, as this is an introductory article, I didn't cover the different tokenization and parsing techniques, such as <a href="https://en.wikipedia.org/wiki/LR_parser">LR(0)</a>, <a href="https://en.wikipedia.org/wiki/Canonical_LR_parser">LR(1)</a>, <a href="https://en.wikipedia.org/wiki/SLR_grammar">SLR(1)</a>, etc. However, be aware that these techniques exist, and you can research more about them. There are also many books that cover these topics.</p>
<p>If you want to see how the AST of popular languages looks, I recommend the <a href="https://astexplorer.net/">AST Explorer</a>. It supports various languages, and you can view the complete AST and navigate through the nodes. If you want to go further, you can try to copy some logic from an existing parser and implement it in your own, such as calculating an expression according to precedence order, for example: <code>1 + 2 * 3</code> (which is 7, not 9).</p>
<p>If you're interested in learning more, I recommend the book "Modern Compiler Implementation in ML." Despite the title being in ML, you can study from it without necessarily writing ML code, as there are other versions written in C, C++, and Java.</p>
Introduction to Big O notationhttps://vitorsalmeida.com/intro-bigo/https://vitorsalmeida.com/intro-bigo/Sat, 12 Aug 2023 00:00:00 GMT<h3>Introduction</h3>
<p>In this post, we will explore the concept of algorithm efficiency and how to measure this efficiency using big O notation. Additionally, we will see how this can help us write more performance code. Big O notation allows us to evaluate the performance of an algorithm according to the size of its data set.</p>
<h3>What is big O notation and algorithm efficiency?</h3>
<p>Algorithm efficiency is the ability to solve a problem in a reasonable time and with efficient use of computational resources such as processing and memory. Big O notation is a way of measuring the efficiency of an algorithm, describing the asymptotic behavior of a function. This means that we can evaluate the rate of growth of the function, comparing it to other algorithms.</p>
<p>Big O notation is represented by the letter O and is used as follows: Θ(f(n)), where f(n) is the measure by which the size of the input (n) increases. For example, an algorithm that grows quadratically, that is, increases proportionally to the square of each additional input, is represented by Θ(n²).</p>
<ul>
<li>Constant time: Θ(1)</li>
<li>Linear time: Θ(n)</li>
<li>Logarithmic time: Θ(log(n))</li>
<li>Quadratic time: Θ(n²)</li>
</ul>
<h3>Big O notation in code</h3>
<p>Suppose you have a list and need to find a certain element 'x'. This algorithm is called sequential search.</p>
<p>Another example is when you need to sort a list. This algorithm is called insertion sort.</p>
<p>Sequential search example</p>
<pre><code>// linear time
function sequentialSearch(arr: number[], x: number) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === x) {
return i
}
}
return -1
}
</code></pre>
<p>Insertion sort example</p>
<pre><code>// quadratic time
function insertionSort(arr: number[]) {
for (let i = 1; i < arr.length; i++) {
let currentVal = arr[i]
for (var j = i - 1; j >= 0 && arr[j] > currentVal; j--) {
arr[j + 1] = arr[j]
}
arr[j + 1] = currentVal
console.log(arr)
}
return arr
}
</code></pre>
<p>Both codes work and solve the proposed problem, but one is more performative than the other. The first code is linear, which means that the for loop will be executed a number of times directly proportional to the size of the array. This means that if the array has n elements, the loop will be executed n times, which can be represented by Θ(n).</p>
<p>The advantage of this approach is that, in case of larger arrays, the code will run faster, as the number of iterations is proportional to the size of the array. This makes the time complexity of the code limited by the size of the array, resulting in a less steep growth chart compared to a code with quadratic complexity. In other words, the first code is more performative and efficient in situations where the array can be very large.</p>
<p><img src="/introbigo/bigo.jpg" /></p>
<p>The second code is an example of quadratic complexity Θ(n²). This means that the for loop inside the for loop will be executed a number of times proportional to the square of the size of the array. In other words, if the array has n elements, the inner loop will be executed n * n times, which can be represented by Θ(n²).</p>
<p>The implications of this complexity are that, in larger arrays, the growth chart will be steeper, resulting in slower and more complex code as the input gets larger.</p>
<p>Now, let's see some examples of code with logarithmic complexities O(log(n)) and O(n log(n)).</p>
<p>Suppose you receive a list of numbers and need to find a certain number x in the list. To do this, you can use the binary search algorithm, which has complexity O(log(n)).</p>
<p>Another example is when you need to sort a list of numbers in a logarithmic way. To do this, you can use the merge sort algorithm, which has complexity O(n log(n)).</p>
<p>These algorithms are more performative and efficient than linear or quadratic approaches in situations where the size of the input can be very large.</p>
<p>Binary search example</p>
<pre><code>// O(log(n))
function binarySearch(arr: number[], x: number) {
let left = 0
let right = arr.length - 1
while (left <= right) {
let mid = Math.floor((left + right) / 2)
if (arr[mid] === x) {
return mid
}
if (arr[mid] < x) {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
</code></pre>
<p>Merge sort example</p>
<pre><code>// O(n log(n))
function mergeSort(arr: number[]) {
if (arr.length === 1) {
return arr
}
let mid = Math.floor(arr.length / 2)
let left = arr.slice(0, mid)
let right = arr.slice(mid)
return merge(mergeSort(left), mergeSort(right))
}
function merge(left: number[], right: number[]) {
let result = []
let i = 0
let j = 0
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i])
i++
} else {
result.push(right[j])
j++
}
}
return result.concat(left.slice(i)).concat(right.slice(j))
}
</code></pre>
<p>Both examples are valid, but they have different time complexities. The first one is O(log(n)), which means that the running time increases logarithmically with respect to the size of the input. In other words, in the worst case, if the array has 8 elements, the algorithm will be executed 3 times. For example: log2(8) = 3.</p>
<p>The second example, O(n log(n)), is a notation that indicates that the running time of an algorithm increases proportionally to the product of the size of the input data and the logarithm of that size. This means that, in the worst case, if the array has 8 elements, the algorithm will be executed 24 times. For example: 8 * log2(8) = 24.</p>
<p>However, it is important to remember that the temporal complexity of an algorithm does not necessarily imply higher or lower speed. It is possible that an algorithm with worse complexity is faster than an algorithm with better complexity, depending on the specific input. However, in general, it is safe to say that the lower the complexity, the faster the algorithm will be, as the input increases.</p>
<p>For those who want to delve deeper into the subject, I recommend reading the book “Introduction to Algorithms” by Thomas H. Cormen.</p>
Is Data Struct about memory?https://vitorsalmeida.com/is-data-struct-about-memory/https://vitorsalmeida.com/is-data-struct-about-memory/Sun, 27 Aug 2023 00:00:00 GMT<p>A principle that I try to follow is to think about obvious concepts. Since we are introduced to society, we meet with a lot of concepts that we don't even question. Some authoritary figure like a teacher or a parent tells us things that we just accept.</p>
<p>If you think about data structure, you can relate it immediately to memory. Today, I want to discuss why data structures <strong>are not only</strong> about memory.</p>
<h3>Introduction</h3>
<p>For instance, let’s understand that we do not have any problem thinking about data structures to organize data in memory. The problem is never the idea that data structures are indifferent to implementation.</p>
<p><a href="https://en.wikipedia.org/wiki/Data_structure#Language_support">Data Structure</a> is a way to organize, manage, and store data (yes, you can store things in math. If you think about <a href="https://en.wikipedia.org/wiki/Matrix_(mathematics)">matrices</a> or <a href="https://en.wikipedia.org/wiki/Set_(mathematics)">sets</a>, you can notice that is a way to organize data. In practice, store things is about to organize things). It is a collection of values that we can realize operations. In essence, it is an <strong>Algebraic Structure</strong> of data.</p>
<h3>Algebraic Structure</h3>
<p>An algebraic Structure is a set of elements with one or more operations that satisfy some specific properties (axioms). An example is Boolean Algebra, a set with two binary operations, union and intersection. You can follow this link to understand more about <a href="https://en.wikipedia.org/wiki/Boolean_algebra#Boolean_algebras">Boolean Algebra</a>.</p>
<p>At this point, I want to highlight that algebraic structures are essentially about math. It describes the properties of a set of elements: a <a href="https://en.wikipedia.org/wiki/Group_(mathematics)">group</a>, a <a href="https://en.wikipedia.org/wiki/Ring_(mathematics)">ring</a>, an <a href="https://en.wikipedia.org/wiki/Algebra_over_a_field">algebra</a>, etc. If you want to dive into this topic, read <a href="https://en.wikipedia.org/wiki/Algebraic_structure">Algebraic Structures</a>.</p>
<p>It is a big mistake to take an abstract and mathematical concept and reduce it to a simple implementation that needs physical and limited resources. You do not need any computer or memory to understand or use data structures. You need <a href="https://en.wikipedia.org/wiki/Data">data</a>. And data in math is just a set of elements.</p>
<h3>Let's to think about computational models</h3>
<p>The root of this problem is that you maybe don't know about computational models. It is a important step because if you think that in computer science we are using math to model computational problems, and computational problems are mathematical problems, you probably never will reduce any computational problem to a phisical and limited resource (like memory).</p>
<p>Computer science problems follow math problems, and we use math to model them. It means that the problem that you're trying to solve, can be modeled by a mathematical problem before you start to think about some computational implementation.</p>
<h3>Conclusion</h3>
<p>I hope that you understand that data structures <strong>are not only</strong> about memory. It is a mathematical concept that we use to model computational problems.</p>
<p>This text is a little talk about some ideas that I would like to share. I tried to give some references to help if you want to dive into this topic. Also, it's <strong>very</strong> important to think about obvious concepts and try to understand <strong>why</strong> the things are the way they are.</p>
Proving natural numbers are infinity in Coqhttps://vitorsalmeida.com/proving-naturals-infinity/https://vitorsalmeida.com/proving-naturals-infinity/Sun, 13 Aug 2023 00:00:00 GMT<h2>Introduction</h2>
<p>An interesting fact about math is that you can demonstrate things. A lot of people thinks that demonstrate is exemplify something, but it is not. It's common to see people saying that $1 + 1 = 2$, just like one apple plus one apple is two apples. But, this is not a demonstration. A demonstration is a logical proof that something is true, by a formal way. You cannot say that every 1 plus itself is equal to two. Boolean algebra is a good example of this. In Boolean algebra, $1 + 1 = 1$. So, you can't say that aways $1 + 1 = 2$.</p>
<h2>What is Coq?</h2>
<p>Coq is a software that allows you to write proofs. It is a proof assistant based on the calculus of inductive constructions. It is a functional programming language based on lambda calculus. I won't talk about lambda calculus and the calculus of inductive constructions, but you can find more information about them on Wikipedia.</p>
<h2>What is a formal proof?</h2>
<p>A formal proof is a process based on logical rules and axioms used to demonstrate some theorem. The goal is to show an absolute truth that some affirmation is faithful by following strict rules.</p>
<h2>The proof</h2>
<p>A simple way to prove that natural numbers are infinite is by defining that given a natural number ($n: nat$), $n + 1$ will return natural numbers greater than $n$.</p>
<p>You need to make it because this theorem proves that natural numbers are infinite by showing that given <strong>any</strong> natural numbers, you can <strong>always</strong> find a natural number greater than it, by summing $1$ to it.</p>
<p>So, lets to define our theorem in Coq:</p>
<pre><code>Theorem plus_1_natural : forall n : nat, 1 + n = S n /\ S n > n.
</code></pre>
<p>The <code>forall</code> keyword means the theorem is valid for all natural numbers. The $\land$ means the theorem is a conjunction, and the $S$ is the successor function. So, the theorem says that given a natural number $n$, $1 + n$ is equal to $S n$, and $S n$ is greater than $n$.</p>
<p>Lets to prove the theorem:</p>
<pre><code>Proof.
intros n.
split.
- reflexivity.
- apply le_n_S. apply le_n.
Qed.
</code></pre>
<p><code>intros n</code> introduces the universal quantifier <code>forall</code> and the arbitrary natural number $n$ as a variable. The <code>split.</code> splits the objective into two subgoals. One for each conjunct $\land$. The hyphen is to refer to the subgoal. The <code>reflexivity.</code> is to prove that something equals to itself. Like $1 + n = S n$, because $1 + n$ is equal to $S n$. You can reduce it, like:</p>
<ul>
<li>$1 + 1 = 2$.</li>
<li>$2 = 2$.</li>
</ul>
<p>Coq have some macros that abstracts some proofs. We can see this reducing using <code>simpl.</code>:</p>
<p><img src="/natinfinity/simpl.gif" /></p>
<p>Coq gives us some theorems related to natural numbers ordering, like <code>le_n_S</code> and <code>le_n</code>. These theorems can be used to reduce steps of our proof, replacing them with the applied theorem. In math, the <code>le</code> it is $\leq$, less than or equal.</p>
<ul>
<li>
<p><code>le_n</code> any natural number is less than or equal to itself. $n \leq n$ is a true statement for any natural number $n$.</p>
</li>
<li>
<p><code>le_n_S</code> says that if $n \leq m$, then $S n \leq S m$. So, if $n \leq n$, then $S n \leq S n$.</p>
</li>
</ul>
<p>The <code>apply le_n_S.</code> definition is <code>le_n_S : forall n m : nat, n <= m -> S n <= S m</code>, and <code>le_n</code> that <code>le_n : forall n : nat, n <= n</code>. So, to read the <code>apply le_n_S. apply le_n.</code> is: "Given a natural number <code>n</code>, if <code>n <= n</code>, then <code>S n <= S n</code>". And this is true, because <code>n <= n</code> is true, and <code>S n <= S n</code> is true too. So, the theorem is proved. <code>Qed.</code> means that the proof is finished.</p>
<p>You can also see it on CoqIDE:</p>
<p><img src="/natinfinity/le.gif" /></p>
<h2>Conclusion</h2>
<p>You can also show the output of the proof and run the proof. So, lets do it.</p>
<p>You can use <code>Print</code> to show the definition of some theorem, and <code>Eval compute in</code> to run the proof. In the end, our proof will be:</p>
<pre><code>Theorem plus_1_natural : forall n : nat, 1 + n = S n /\ S n > n.
Proof.
intros n.
split.
- reflexivity.
- apply le_n_S. apply le_n.
Qed.
Print plus_1_natural.
Eval compute in (plus_1_natural 1).
</code></pre>
<p>And the output will be:</p>
<pre><code>plus_1_natural = fun n : nat => conj eq_refl (le_n_S n n (le_n n))
: forall n : nat, 1 + n = S n /\ S n > n
</code></pre>
<pre><code>= plus_1_natural 1
: 1 + 1 = 2 /\ 2 > 1
</code></pre>