Nim Day 4: Function Calls =================================================================== Or: A Brief Discourse on The Variety of Ways a Function May be Called in Nim, With a Surprising Mention of Sloths Nim has a rather unique combination of syntax and features for function calls that add up to a very simple, flexible system that provides a lot of convenience. As a desired side effect, it also Nim's functions a syntactical parity with "traditional" Object Oriented styles as well as Functional styles of programming. [1] Proc vs Func ------------------------------------------------------------------- By the way, I named this post "function calls", but Nim uses a `proc` keyword, not `func`: proc hello() = echo "Hello phlog readers!" So what gives? What is the difference between a procedure and a function anyway? The key difference is that a function is supposed to return a value. Additionally, some purists will tell you that a function should not have any side effects (should not change _any_ program state outside the body of the function)! Procedures are just blocks of code which can be called - they do not have to return values and traditionally it was assumed that something called a "procedure" might have side effects. Of course, we know that many common procedural languages (such as C, JavaScript) have "functions" which do not have to return values and may have side-effects. Interestingly, C and its many descendants are considered "ALGOL-like" in syntax, yet ALGOL used the `procedure` keyword (and the unloved ALGOL 68 shortened it to `proc`, like Nim). Arguably "procedure" is a more correct general term for these callable chunks of code, since they may or may not fit a strict definition of "function". I don't think the semantics are worth getting too worked up about, but I think it's also good to understand and appreciate the difference in terminology. (It's also worth noting that depending on the circles you travel in, writing code with procedures that make all sorts of unstructured changes to program state in 2018 is akin to writing GOTO-laden "spaghetti code" in the 1970s and 80s (as Dijkstra's famous "GOTO Considered Harmful" letter became gospel.)) Anyway, I'm going to use the term "function" and "procedure" interchangeably from here on out. Optional parenthesis for function calls ------------------------------------------------------------------- You may have noticed the optional parenthesis when calling functions such as `echo` in previous examples (in fact, I have consistently left them out): echo "Hello World!" echo("Hello World!") # equivalent There is nothing special about the echo() function which allows this. Any function which takes at least one parameter may be called without parenthesis. Let's make a little example: # callsugar1.nim proc foo(num: int) = echo num foo(5) foo 5 And compile and run it: nim c -r callsugar1 ... 5 5 So `foo 5` is just syntactic sugar for `foo(5)`. Any time I can press the Spacebar key once instead of Shift+9 followed by Shift+0 "( ... )" is a Good Thing! Oh yes, sweet sugary syntax! I crave your nectar like a fruit fly craves the pungent flower that eventually traps and kills it. UFCS ------------------------------------------------------------------- Nim supports a really interesting feature called Uniform Function Call Syntax. Short explanation: it lets you call the function as if it were a method of the first parameter. Huh? Let's use that `foo()` function above as an example: foo(5) 5.foo() # same! This might look a little more familiar with a variable: var mynum = 5 mynum.foo() "Holy crap! That looks just like my Grandpa's old Java method calls we keep under a greasy tarp out in the barn!" That's the idea. Between UFCS and the optional parenthesis sugar, we can have quite a few variations. Let's see if we can try them all: # callsugar2.nim proc noargs() = echo "I don't take any arguments." noargs() # noargs <- this would be an error! proc onearg(a: int) = echo "I take this arg: " & $a onearg(5) onearg 5 5.onearg 5.onearg() proc twoargs(a: int, b: int) = echo "I take these args: " & $a & ", " & $b twoargs(5, 6) twoargs 5, 6 5.twoargs(6) 5.twoargs 6 And here's the results to prove it all works: I don't take any arguments. I take this arg: 5 I take this arg: 5 I take this arg: 5 I take this arg: 5 I take these args: 5, 6 I take these args: 5, 6 I take these args: 5, 6 I take these args: 5, 6 Now, you'll have noticed that `noargs()` is the exception to all of this excitement. Why can't we call it without parenthesis? The happy answer is that functions "first class" in Nim. (First-class functions are treated like any other value in the language - you can store references to them in variables, pass them to other functions, or put them in data structures. In other words, functions are "first-class citizens".) So we can't call `noargs` without parenthesis because Nim treats a bare function name as a reference to the function, not a function call. (The compiler gives us an error telling us that we have to _do_ something with the reference (such as discard it).) Stropping ------------------------------------------------------------------- Nim takes extensibility very seriously. I probably _won't_ get into templates or macros, but Nim has complete support for metaprogramming. But here's a fun thing to play with that takes two seconds to learn: put backticks (`) around operators and reserved keywords to redefine them! proc `!`(n: int) = for i in 1 .. n: echo "BANG!" ! 5 Results in: BANG! BANG! BANG! BANG! BANG! More practically, you can give your custom type array-style access by defining a method for the `[]` operator: proc `[]`(obj: MyWeirdObject, index: int) = ... Neat! Maybe we'll try one of those later when we look at defining our own types. Function overloading ------------------------------------------------------------------- If you've not encountered them before, "overloaded" functions have multiple definitions, each taking different numbers or types of parameters. This is a type of polymorphism (Greek for "many forms" and "there are demons in this code"). # foverload.nim proc sherlock(i: int) = echo "This is an integer!" proc sherlock(s: string) = echo "This is a string!" sherlock(5) sherlock("five") Let's see how smart Sherlock is: $ nim c -r foverload ... This is an integer! This is a string! This is a silly example, but there are lots of good reasons to use polymorphism such as using it in place of if/else logic. Also, it lets us program in Nim with... Object Oriented Style ------------------------------------------------------------------- So between the UFCS sugar and function overloading, we can make it appear that we have traditional OOP methods: cat.makeSound() # meow cow.makeSound() # moo But in fact, these would be defined like this: proc makeSound(x: Cat) = echo "meow" proc makeSound(x: Cow) = echo "moo" Pretty straight forward. This would be the end of the story, but there is actually a `method` keyword in Nim. What gives? Well, most of the time Nim can figure out which function to call at compile time. In our above pseudo-example, it's clear to us and the compiler which `makeSound()` to call based on the type of the incoming parameter. This is called "static dispatch". However, there are situations where the type of a parameter can't be known until runtime (this is not the same as having a function which can take _multiple_ types - that's generics!) and so we can't know which of the overloaded functions to call. That requires dynamic binding of the function and is called "dynamic dispatch". method foo(n: int) = echo "Dynamic!" This is one of those things to just file away in the big steel filing cabinet in the corner of your mind so you can pull it out when the situation arrives. File it under "Nim: Multi-methods". Functional style ------------------------------------------------------------------- Functional programming is all the rage these days. (I certainly caught the bug when I learned Scheme through a wonderful book called The Little Schemer about a decade ago. Since a lot of my professional work requires JavaScript, I've become enamored with a functional programming library called Ramda. I've barely scratched the surface of what Ramda can do - but it's amazing how much you can accomplish in a line or two when you embrace a "point-free" existence!) Nim isn't billed as a "functional programming" language and it's certainly not going to fit certain purist definitions of that term because it lets you get away with any sort of imperative nonsense you like and because it doesn't have a huge amount of FP "helpers" built into the language. But has the essentials you need to write your own "pure" functional code. We have first-class functions which can be passed as parameters: # procparam.nim proc foo(n: int) = echo "Foo: " & $n proc numberify(n: int, f: proc) = f(n) numberify(5, foo) (Crossing my fingers, I'm totally guessing that `proc` as the type will work...) $nim c -r procparam ... Foo: 5 (Man, I love it when I guess correctly!) You can assign functions to variables: # procvar.nim proc foo(n: int) = echo "Foo: " & $n let kittensLoveSkeletons = foo proc numberify(n: int, f: proc) = f(n) numberify(5, kittensLoveSkeletons) (The output is the same as procparam.nim: "Foo: 5".) And you can perform point-free (no variables) "method chaining", which can be very satisfying: # methodchain.nim proc x2(n: int): int = n * 2 proc plus10(n: int): int = n + 10 proc toString(n: int): string = $n proc show(s: string) = echo s 3.x2.plus10.toString.show Result should be 16: $nim c -r methodchain ... 16 Yessss!!! By the way, I clearly sneaked some new syntax I'd avoided until now, but which you probably implicitly understood: declaring the return value of a function. proc foo(var1: TypeA, var2: TypeB): TypeC = # body of function foo() takes a TypeA and TypeB and returns a TypeC. if you like thinking about this sort of thing, you're going to _love_ generics! Speaking of declaring, Nim requires that functions be declared before you call them. Then what about circular dependencies? Ah, well I said they have to be _declared_, but they don't have to be _defined_. You can do this: # who wrote this and why!? proc a() proc b() = a() proc a() = b() But don't. Generics ------------------------------------------------------------------- We'll just _touch_ on generics here. Nothing heavy. That can wait for the Type System Discussion we're going to have to have. You know, the one where I give you a stern look and say, "it's time we had a talk about the type system," and both of us are kind of embarrassed, but we know we have to go through with it to clear the air because we've been walking on eggshells for, like, four Nim sessions and it feels like a vacuum chamber in here, man. I can't breathe! Here's a really simple form of generic function: it can take either an int or a float (love that syntax): # genericproc1.nim proc foo(num: int | float) = echo "foozling: " & $num foo(5) # foozling: 5 foo(6.3) # foozling: 6.3 An even more generic form takes _any_ type, which we name "T" and use as the type of the parameter `num`. # genericproc2.nim proc foo[T](num: T) = echo "foozling: " & $num foo(5) foo(6.3) foo(true) $nim c -r genericproc2.nim ... foozling: 5 foozling: 6.3 foozling: true The compiler will still protect us from passing values of types which don't defined the `$` function to convert to string. Well, I really thought I was going to make a nice short entry when I started writing this at the beginning of the week. I never imagined it would take days to complete. On the other hand, I only had a few scattered hours here and there to spare. I've actually made very good use of my time and spent the rest with the wife and kids, so that's a small triumph of its own. I think I'll tackle the type system next. If it starts to get out of hand, I'll split it into multiple entries...but I'd much rather start focusing on things specific to the end goal: creating a Gopher server and Gopher client. [1] Some folks think Nim's mildly irreverent syntax is the product of an evil pickle-eating cult of sloth-monsters hell-bent on turning all the world's information into a brown, sludgy paste. That is simply not true. [2] [2] For most values of "paste".