Github Copilot 101

Federicorudolf
9 min readFeb 15, 2023
Image credit

I’I’ve been meaning to start using GitHub’s Copilot for a while now, and since I’m about to, I decided to document the process, and note some of the cool things too.

Let’s start from the beginning…

What’s GitHub Copilot (Copilot from now on) and What’s It For?

The actual definition on the GitHub website is:

“GitHub Copilot is an AI pair programmer that helps you write code faster and with less work. It draws context from comments and code to suggest individual lines and whole functions instantly. GitHub Copilot is powered by OpenAI Codex, a generative pre-trained language model created by OpenAI. It is available as an extension for Visual Studio Code, Visual Studio, Neovim, and the JetBrains suite of integrated development environments (IDEs).”

Another definition that we could use, was given by the new and well-renowned OpenAI’s ChatGPT, who defines it as:

“GitHub Copilot is a new AI-powered tool that helps developers write code by providing suggestions and completing code snippets. It uses machine learning models to understand the context of the code and suggest appropriate modifications or additions. Copilot is currently in beta and available to select users on GitHub.”

My personal definition after trying it out for some time is: Copilot is a semi-senior dev (that learns pretty fast 😆) who’s sitting behind you while you code, and pitches new ideas as you go.

How To Start Using It

Before we start with the step-by-step process we should mention that even though it comes with a 60-day trial, it costs $10/month (there are a couple of free alternatives that I’ll mention later).

Ok, now let’s get to business.

  1. First of all, you need to decide which IDE you’ll be using (supports VS Code, Visual Studio, JetBrains, and Neovim as of February 2023).
  2. Once you’ve done that, go to the extensions menu on the sidebar (⌘ + ⇧ + X) and search for Copilot. It should be the first one.
  3. After you install it and reload VS Code, you’ll need to sign in and authorize VS Code with your GitHub account. If you encounter any issues here make sure to check the official docs that go through these steps in detail.
  4. Now we’re ready to start coding (or watching Copilot code for us 🤔). Just create a new file and tab along.

A quick note, I’ve discovered while using it that Copilot relies quite a bit on the files saved in the same directory as the one you’re using. I’ve tried typing along a couple .py files that I had and it was incredibly accurate in suggesting things. If you completely change the context, it takes some time to get a grasp on what you’re trying to achieve, but eventually, it gets there.

My Experience

As mentioned before, it’s way faster if it can learn from files in the same directory you’re working at. To show a couple of examples, I’ve been working on training a GPT model, which is usually quite specific code (depends a lot on context and data structures).

When copying a 100-line file by hand, it just took me typing the first two imports and then it would prompt line by line the code written in the first one. It took me only 23 tabs to copy 100 lines, and 1 minute and 48 seconds to do so. One interesting thing is that it had a line with an API key that it did not copy (unsure why).

On the other hand, I decided to create a simple JavaScript program to transform temperature units. I first tried to “make things easier” by naming things the right way, and I only had to type the following:

class UnitConversion {

to get this (note that I’ve omitted the closing bracket on purpose) :

class UnitConversion {
constructor() {
this.unit = 'miles';
}

convertToMiles(kilometers) {
return kilometers * 0.621371;
}

convertToKilometers(miles) {
return miles / 0.621371;
}

convertDistance(distance) {
if (this.unit === 'miles') {
return this.convertToMiles(distance);
} else {
return this.convertToKilometers(distance);
}
}

Even though it didn’t get the desired function from a simple tab, it just required me writing this:

convertToFarhenheit(celsius) {

to get this (here’s that missing bracket):

  converToCelcius(farhenheit) {
return (farhenheit - 32) / 1.8;
}

convertToKelvin(celsius) {
return celsius + 273.15;
}

convertTemperature(temperature) {
if (this.unit === 'celsius') {
return this.convertToKelvin(temperature);
} else if (this.unit === 'farhenheit') {
return this.converToCelcius(temperature);
} else {
return temperature;
}
}
}

This was pretty good, but let’s face it, a simple unit conversion class is not worth paying $10 a month (unless you’re working for OpenWeather or something 😆).

Let’s take it up a notch. Next thing is to write a .swift file to make a breadth-first search for a given node. No need to pay attention to the actual code (especially if you’re a Swift expert, since I’m not one 😅) rather than the complexity it carries (it’s not rocket science, but it’s a little more complex than the unit conversion class 😂).

I wanted an algorithm that would find me the solution to sort a 4-element array [4, 2, 3, 1] by just swapping two numbers at a time. That creates a tree node like the following:

Tree node image (L = left swap, C = center swap, R = right swap)

As we can see, I want to explode a 4-element array to find the solution we want: [1, 2, 3, 4]. For many reasons, this would be far easier to attempt in Python, but since we’re trying to make things a little difficult for Copilot, let’s take a shot with Swift.

The first thing I did (after importing Foundation) was to declare a Node class and a Child struct to use in our code. I had to type up to the following (note that, again, there’s no closing bracket):

struct Child {
var parent: Node
var value: [String]
}

class Node {
var data: [String]
var children: [Child]
var parent: Node
var cost: String

init(data: [String], children: [Child]) {
self.data = data
self.children = children
self.parent = children[0].parent
self.cost = ""
self.set_children(children: children)
}

func set_children(children: [Child]) {
self.children = children
if self.children.count != 0 {
for var child: Child in self.children {
child.parent = self
}
}
}

func get_children() -> [Child] {
return self.children
}

func set_parent(parent: Node) {
self.parent = parent
}

Up to this point, it was pretty much typing (with a few line completion exceptions). Once I started typing a couple of setters and getters Copilot caught up, and the following was pretty much just naming functions and letting the magic happen:

  func get_parent() -> Node {
return self.parent
}

func set_data(data: [String]) {
self.data = data
}

func get_data() -> [String] {
return self.data
}

func set_cost(cost: String) {
self.cost = cost
}

func get_cost() -> String {
return self.cost
}

After that, I typed two more functions that I needed, and here’s where I used a really cool feature about the Copilot extension (here’s the missing bracket):

  func equal(node: Node) -> Bool {
return self.data == node.data
}

func in_list(list: [Node]) -> Bool {
for node: Node in list {
if self.equal(node: node) {
return true
}
}
return false
}
}

For that last in_list function, which is really straightforward, I opened up the Copilot window (⌘ + ⇧ + P and search for Copilot) which synthesizes 10 solutions for the code you’re trying to write. This was helpful to avoid unnecessary code.

After that is where things got kinda complicated for our friend. We need a function that would generate children nodes for one given initial node, and compare each one of them to the solution we want, to see where & when that solution happens. For this, we want to keep a queue with the nodes we haven’t yet compared, and a list of the ones we have. We iterate over each of the items in the queue and create children (following the logic shown in the image above).

I typed the name of the function and apparently Copilot was feeling confident so it just prompted me with this solution:

func breadth_first_search(initial: Node, goal: Node) -> Node {
var solved: Bool = false
var visited: [Node] = []
var queue: [Node] = []
queue.append(initial)
while queue.count != 0 && !solved {
var node: Node = queue.remove(at: 0)
if node.equal(node: goal) {
return node
} else {
for child: Child in node.get_children() {
var child_node: Node =
Node(data: child.parent.get_data(),
children: child.parent.get_children())
child_node.set_parent(parent: node)
if !child_node.in_list(list: queue) {
queue.append(child_node)
}
}
}
}
return Node(data: [], children: [])
}

If you look at it, it feels kind of odd. The first few lines are what we would expect, but that while loop seems weird and on top of all, it’s returning an empty node 😐 (that overseeing the fact that’s not even swapping the elements as we would need).

Another cool thing Copilot allows you to do is this: You basically add a comment about what you need to do, and it would suggest code for it. I tried this with our little BFS exercise, and this is where it got really weird. This was my prompt:

function that would make a breadth first search algorithm on a 4 element array with three posible moves, swapping the first two elements, swapping the second and third elements, and swapping the third and fourth elements

func breath_first_search()start: [String], goal: [String]) -> [String] {
var start_node: Node = Node(data: start, children: [])
var goal_node: Node = Node(data: goal, children: [])
var frontier: [Node] = [start_node]
var explored: [Node] = []
var path: [String] = []
var current_node: Node = start_node
var children: [Child] = []
var child: Child = Child(parent: current_node, value: [])
var child_node: Node = Node(data: [], children: [])
var i: Int = 0
var j: Int = 0
var k: Int = 0
var l: Int = 0
var m: Int = 0
var n: Int = 0
var o: Int = 0
var p: Int = 0
var q: Int = 0
var r: Int = 0
var s: Int = 0
var t: Int = 0
var u: Int = 0
var v: Int = 0
var w: Int = 0
var x: Int = 0
var y: Int = 0
var z: Int = 0
var a: Int = 0
var b: Int = 0
var c: Int = 0
var d: Int = 0
var e: Int = 0
var f: Int = 0
var g: Int = 0
var h: Int = 0
var ii: Int = 0
var j: Int = 0
var k: Int = 0
var l: Int = 0
var m: Int = 0
var n: Int = 0
var o: Int = 0
var p: Int = 0
var q: Int = 0
var r: Int = 0
var s: Int = 0
var t: Int = 0
var u: Int = 0
var v: Int = 0
var w: Int = 0
var x: Int = 0

Yeah, this doesn’t seem like something we’d expect. In Copilot’s defense, they state that it works better with Python, JavaScript, TypeScript, Ruby, Go, C# y C++. So it’s safe to say we took things a little too far, even though I tried the same thing with Python, and got funky suggestions too (obviously, less funky ones). For reading purposes, I’ve omitted that code.

Conclusion

I’ve used Copilot for about 20 days, coming from Tabnine which offers a free alternative, and I have to say even though Copilot works really well on the more routine tasks such as writing a test (didn’t get into this one, but definitely a must if you don’t like writing specs), or working with simpler classes. I think it’s not a must-have, especially with solutions like Tabnine around. If you are working on one of those big projects where 95% coverage is a must and you write small components every sprint, then Copilot would be a really good tool to have around.

In my humble opinion, these AIs are not yet at that place where you just couldn’t code without them, but clearly gaining a lot of pace and rapidly making room in our disk and our budgets 😅. For now, I’m going to stick to Tabnine which has worked just fine for me on the tasks I need it for.

Well, that’s all for now. Let me know what you think, and if you’re using Copilot, make sure to leave your thoughts in the comments!

Thanks for reading!

— Fede

--

--