When I first learned about the Julia programming language, there were a few things that gave me the "wat" moments. One of those surprises involves both the and of functions. naming meaning Interestingly, my naive question triggered over in the Julia Discourse forum. That's one of my best records for motivating fellow developers! 😄 200 follow-up posts 200! What is the issue? Let's first take a look at a very simple example. Suppose that I have a module that contains the following code: CalendarApp Meeting subject:: start_time:: end_time:: struct String DateTime DateTime end Then, I want to create a function that calculates the length of a meeting. Super simple, right? Let's go for it: length(m::Meeting) = Hour(m.end_time - m.start_time) When I code, I like a REPL-based development workflow so I can test new code quickly: Meeting("COVID Response Committee", 2020-06-14T08:00:00, 2020-06-14T10:00:00) 2 hours julia> covid_meeting = Meeting( , ( , , , , , ), ( , , , , , )) "COVID Response Committee" DateTime 2020 6 14 8 0 0 DateTime 2020 6 14 10 0 0 julia> println(length(covid_meeting)) So far so good! Now, try to use function to determine the length of an array. length ERROR: MethodError: no method matching length(::Array{Int64,1}) You may have intended to import Base.length Closest candidates are: length(::Meeting) at REPL[3]:1 julia> length([ , , ]) 1 2 3 That's right. Here we get the exact "wat" moment. What happened to the regular function? Wat! length 😵 There are two length functions! The answer is quite simple. There are actually two functions around. One of them is defined in module for which everyone is familiar with, and the other one is just defined above. length Base Here's my own function: length length (generic function with 1 method) julia> length Now, to clear things up and try again: restart the REPL 3 length (generic function with 81 methods) julia> length([ , , ]) 1 2 3 julia> length Now, I am able to access the original again. You may also notice that this function is attached to 81 methods. length length So, how did that happen? It seems that I might have hidden the original function by defining our own function earlier. Out of curiosity, I can define my own function again: length length ERROR: error in method definition: function Base.length must be explicitly imported to be extended julia> Meeting subject:: start_time:: end_time:: struct String DateTime DateTime end julia> length(m::Meeting) = Hour(m.end_time - m.start_time) Man, now it's doing the exact opposite! It doesn't even let me define function anymore! length This is the second "wat" moment for the same problem. 🤔 Did I do anything wrong? It might worth a quick discussion here about why I did what I did. And, why I thought I was right. First of all, I came from an object-oriented programming background. To be more precise, I had many years of experience developing in the Java language. How would the same problem look in OOP? Well, in the object-oriented world, there is probably some kind of class that defines a method. In this case, I would also define a class with a method. For instance: Array length Meeting length my_array.length(); my_meeting.length(); // invokes the length method defined in Array class // invokes the length method defined in Meeting class When I call the method, there is no ambiguity. These are just two different methods from two different classes. But wait... Didn't I just do the same thing in Julia? If I look at the signature of my function, it accepts an argument of data type . So, why couldn't Julia just call my function when I pass a object, and call the regular function when I pass an array? length Meeting Meeting length Here is my primary misconception. Multiple dispatch only works for a single function. What I have done above actually introduced a second function, and that function is attached to a single method. length More precisely, the two functions are defined in their own modules. Let me prefix with their respective namespaces and the number of methods: length Base.length CalendarApp.length # 81 methods # 1 method 🐛 Here's the easy fix... As I want multiple dispatch to kick in, I just need to make sure that I define a new for the function rather than defining my own function. This is also called . There are two ways to archive that. method Base.length extending a function : prefix the function name with the module name. Option #1 (preferred) Base.length(m::Meeting) = Hour(m.end_time - m.start_time) : import the length function before defining it. Option #2 Base: length length(m::Meeting) = Hour(m.end_time - m.start_time) import Now, let's start a new REPL and try again: subject::String start_time::DateTime end_time::DateTime end length (generic function with 82 methods) julia> Meeting struct julia> Base.length(m::Meeting) = Hour(m.end_time - m.start_time) julia> length Alright, the function now has 82 methods attached. length Let's confirm its functionality. DateTime(2020, 6, 14, 8, 0, 0), DateTime(2020, 6, 14, 10, 0, 0)) Meeting("COVID Response Committee", 2020-06-14T08:00:00, 2020-06-14T10:00:00) 2 hours 3 julia> covid_meeting = Meeting( , "COVID Response Committee" julia> length(covid_meeting) julia> length([ , , ]) 1 2 3 Voila! Problem solved! 📌 Wait, why do I have to do that? There is already a simple solution once I understand how multiple dispatch works in Julia. So, how did I trigger 200+ follow-up posts in Discourse? The main controversy is why I have to be explicit about extending . Since has a name of , and has a name of , why wouldn't Julia just automatically merge them? Base.length Base.length length CalendarApp.length length The whole thread of discussion in Discourse goes about how it can be and for when the functions can be merged automatically. I will now argue (against my original opinion in the Discourse thread) that it is a bad idea to do so. more convenient less confusing new Julia users Here is the main reason: just because two functions have the same name doesn't imply that they mean the same thing. Every function is designed to have a specific meaning. In English, the meaning of function is pretty much aligned with what one commonly know what a length is. length To be clear, I will just show the first definition from Dictionary.com: Length (Noun): the longest extent of anything as measured from end to end. So, the length concept refers to a measurement. As with any kind of measurement, it means that I should expect it to return a numerical value. Hence, when anyone calls the function, a number is expected to be returned. length This is literally an . implicit contract Enforcing the same meaning for all methods turns out to be a very useful thing. Right off the bat, I can display a graphical user interface that shows a bar that represents a measurement. The same component works regardless of whether the object is an array, a , or a ! length String Meeting This is also the main reason why Julia packages interoperate so well with each other! As long as there is consistent names and meanings, we can build very powerful abstraction and interfaces. Then, everything just works with each other in harmony. You don't buy it yet? Just take a look at the various types of . These arrays can be used anywhere a regular array is accepted. Julia array implementations 😈 Playing devil's advocate... Now, what happens if I ignore the implicit contract and define the length of a meeting to be a string? For instance: Base.length(m::Meeting) m.end_time - m.start_time > Hour( ) function if 1 return "Long" else return "Short" end end Well, it's probably fine because is my own data type. Meeting However, it also means that I should not let anyone else use . Why? That's because another developer will probably get very confused to experience my function returning a string rather than a number, and that could cause serious problems. Meeting length Remember the GUI component I talked about earlier? It's going to be so broken. Not keeping a consistent meaning (implicit contract) for a function is a recipe for failure. It severely limits the reusability of functions. 🤓 What if I really want to use the same function name for different purpose? If I insist that my function should return a string, then I really have two options. length First, I can define my own function and not extend from . Second, I could choose a different name for the function. Base.length In the first scenario, I would be able to access both functions. The caveat is that I will have to use and instead of the short form. length Base.length CalendarApp.length This is needed to remove the ambiguity about which function I'm referring to. The best practice, however, is to avoid naming functions with the same name that has already been used in Base. Why? All of the exported Base functions are with the exception of bare modules. So, you will have a conflict just like how it was described at the beginning of this post. automatically imported into every module If you develop packages, then you don't want your users to be confused about your function versus the one in Base. Because the Base module is standard library that everyone uses, it's probably not a good idea to define a function with the same name but different meaning. 🛰️ What if the dependent module isn't Base? Now, suppose that I am using a different module rather than Base. As an example, I'm going to pick on one of my favorite packages Distributions.jl. A typical Julia user would do the following: Distributions using I do that, too, when I need to use it interactively. However, if I need to use it in my app, then I would want to import only the functions that I need into my namespace. For example, let's say I want to calculate the mean and mode of some randomly-generated data, I would do this: Distributions: mean, mode using This is actually quite important! First, by bringing only known functions into my namespace, it reduces the chance of function name collision. Just take a look at the . huge number of exported names by Distributions.jl Second, I'm making my code future-proof. Let's say I have already defined a function named in my module. My code will still work even if Distribution.jl happens to define and export their own in a future version. So, I don't need to worry naming conflict because I have only imported and into my namespace. dist dist mean mode Final thoughts... . Besides choosing the right word, it is also important to mean what you mean. Naming things properly is super important Over the years, I have developed a habit to ensure writing code that means what I mean. And, it's actually super simple. Just write documentations. In Julia, I would write a doc string for every function at the same time that I code that function. Sometimes I change the function name to match my doc string. At other times, I change the doc string to match my function name. It is quite amazing how effective this can be. I encourage you to give that a try today! Thank you for reading. For more tips in writing good code in Julia, consider picking up my book . P.S. Hands-on Design Patterns and Best Practices with Julia Lead image by Romain Vignes on Unsplash Previously published on: https://ahsmart.com/pub/the-meaning-of-functions/