Sweet, sweet, sweeeeeet Nim
Tags: nim, dsl, sugar
Published: 2020-07-02 04:41:23

Can you smell sweet air when you use [Nim language](http://nim-lang.org/) ? Yes, I can! Because Nim has excessive sugar :).
Why Nim is so sweet?  Because Nim has a powerful macro system which allows direct manipulation of the AST, offering nearly unlimited opportunities. This is usually called syntax sugar or DSL which reduces the complexity of programming.

## Requirements

Your Nim version is at least more than 1.2.0.

## with

Are you tired of writing the ugly chaining of function calls? 

### Put them in one line.

 Well, if I can afford a 24-inch display, I would like this.  

```nim
uglyChaining.add(substr = "hello").strip(leading = false).clear(total = true)
```

### Split them in multi-lines

Emm, it looks better. But where should I align my dots?  This will cause a war just like where braces should be placed in unless you have a better ide :P.

```nim
uglyChaining.add(substr = "hello")
 			.strip(leading = false)
 			.clear(total = true)
 			
uglyChaining.add(substr = "hello").
             strip(leading = false).
             clear(total = true)
```

### Nim saves you from this

Using `var` means it is mutable,  Now we have a unified way to do that.

```nim
# example.nim
import std/with


var x = "Hello, "

with x:
  add "Nim!\n"
  add "It's an elegant language.\n"
  add "Welcome to this beautiful world!"
```

What does `with` do? Nim provides a hands-on command to expand macros:

Type `nim c -r --expandMacro:with example.nim`, here are results:

```
example.nim(9, 6) Hint: expanded macro:
add x, "Nim!\n"
add x, "It\'s an elegant language.\n"
add x, "Welcome to this beautiful world!" [ExpandMacro]
```

Yes, they are transformed to plain function calls. But you just write DSLs without knowing its internal implementations.

Of course, there are some requirements you should obey: 

The type of first parameter must be in accordance with `x` . 

```nim
proc addTwoInts*(s: var string, a, b: int) =
  s.addInt a
  s.addInt b
 
proc addStatic*(s: var string) =
  s.add "static"
  
proc addSideEffect*(s: string) =
  echo s
```

These three functions are all allowed. There will be automatically expanded to:

```
example.nim(18, 6) Hint: expanded macro:
add x, "Nim!\n"
add x, "It\'s an elegant language.\n"
add x, "Welcome to this beautiful world!"
addTwoInts x, 12, 3
addStatic(x)
addSideEffect(x) [ExpandMacro]
```

So far so good, let’s look at the next feature.

## dup

When I wrote some function declarations, I was lost in thought: “To be or not to be, this is a question”.  Oh no, the Nim problems were too hard to deal with and caused me distract. The real problem is whether we should implement inplace version or outplace version instead. As I am a mature adult, I usually want both of them.

Inplace version and outplace version have their own advantages and disadvantages. Inplace version is more efficient in most situations.

```nim
proc addInplace(a: var string, b: string) =
  a.add b
 
proc addoutplace(a: string, b: string): string =
  a & b
```

In my computer, these two functions have big difference in performance.

First we write a simple test program for inplace version and use command `nim c –d:release test_inplace.nim` to compile program:

```nim
proc addInplace(a: var string, b: string) =
  a.add b


var s: string

for i in 1 .. 25:
  s.addInplace($1)

echo s[0]
```

Type `time ./test_inplace`

```
1

real	0m0.001s
user	0m0.001s
sys	    0m0.000s
```

Then we write test program for outplace version and use command `nim c –d:release test_outplace.nim` to compile program:

```nim
proc addOutplace(a: string, b: string): string =
  a & b

var s: string

for i in 1 .. 25:
  s.add addOutplace(s, $i)

echo s[0]
```

Type `time ./test_outplace`

```
1

real	0m0.084s
user	0m0.020s
sys	    0m0.064s
```

Yes, inplace version is really fast. However, sometimes we don’t want to modify original string, we need to generate a new string. Now outplace version comes to our rescue. When we design our APIs, we may have a bunch of questions. Which one should I write? Will my users will need another version of functions? Or should I write both of them for my function call? And If I write one two-version function call, should I make the rest of function call become two-version? 

Thanks to god! I met Nim! we have `dup` to reduce redundancy. Now we only need to implement inplace version.

Take this inplace function for example:

```nim
import sugar

proc addInplace(a: var string, b: string) =
  a.add b
  
var a = "Hello, "
doAssert a.dup(addInplace("Nim!")) == "Hello, Nim!"
doAssert dup(a, addInplace("Nim!")) == "Hello, Nim!"
```

You may want to ask, is this also the “magic” of macros? Bingo.  After all, we all knows that 

**The best way to learn macros is to expand macros. —— myself :)**

Now we know the macros are just paper tigers. They sound difficult and dreadful. But when you expand them, they are merely plain Nim statements or plain functions. Macros just construct plain Nim statement. But if you can grasp macros, you can say goodbye to duplicated codes.

Now we reduce temporary variable and make it one line.

```
example.nim(7, 11) Hint: expanded macro:
var dupResult_14505006 = a
addInplace(dupResult_14505006, "Nim!")
dupResult_14505006 [ExpandMacro]
```

It also allows multiple inplace function calls.

```nim
doAssert a.dup(addInplace("Nim:)"), removeSuffix(":)")) == "Hello, Nim"
doAssert dup(a, addInplace("Nim:)"), removeSuffix(":)")) == "Hello, Nim"
```

### collect

We may know that Python has list comprehensions. When we don‘t have too much nested structure, list comprehension is really elegant to use.

Nim language introduces `collect` macros to implement list, tables, set comprehensions. Different from Python, it is in the indentation forms. Even you have much nested and complex logic, it is still clear to use.

```nim
import sugar


let origin = [1, 2, 3, 4, 5]
let extra = [3, 5, 7, 9, 11]

let data = collect(newSeq):
  for idx in 0 ..< origin.len:
    let a = origin[idx]
    let b = extra[idx]
    if (a + b) mod 2 == 0:
      b - a

doassert data == @[2, 4, 6]
```

Let’s expand macros first. It just adds elements as usual. However `collect` macros encapsulate all needed block and do not introduce unnecessary temporary variables. It is more clear to show what we want to do.

```
example.nim(7, 19) Hint: expanded macro:
var collectResult_6206028 = newSeq(Natural(0))
for idx in 0 ..< len(origin):
  let a = origin[idx]
  let b = extra[idx]
  if (a + b) mod 2 == 0:
    add(collectResult_6206028, b - a)
collectResult_6206028
```

### debug-string

Sometimes, we want to use `echo` to debug programs. However, `echo` introduces many duplicates. I don’t want to write:

```nim
let language = "Nim"
echo "language = ", language
# Output: language = Nim
```

`language = ` is totally duplicated since I can get its name in compile time. That’s what `dump` does:

```nim
import sugar

let language = "Nim"
dump(language)
# Output: language = Nim
```

Sometimes, `dump` can’t satisfy our needs. For example, we want more verbose information such as filename and line info. We can extend `dump`  by writing our own debug macros. 

Let’s do it.

```nim
# import necessary modules
import macros, sugar


macro dumpVerbose*(s: untyped): untyped =
  let info = s.lineInfo # get line infos
  result = quote do:
    stdout.write `info` & "|>"
    dump(`s`)
  
# output:
# example.nim(15, 14)|> a + 12 = 26
```

Wow, our `dumpVerbose` is just five lines, but it can print its line info and filename. Excellent!

What if we want to print multiple error messages without introducing duplicates? Now Nim implements a new format debug string which reduces many duplicates. However it is in development. Make sure you install the latest Nim devel version(At least newer than 2020-07-01:

```
nim -v
Nim Compiler Version 1.3.5 [Windows: amd64]
Compiled at 2020-07-01
Copyright (c) 2006-2020 by Andreas Rumpf
```

Let’s start our journey:

```nim
import strformat


let language = "Nim"
echo fmt"{language=} {language=} {language=}"
# Output: language=Nim language=Nim language=Nim
```

Isn't it really difficult? Be careful. It is space sensitive.

```nim
import strformat


proc hello(a: string, b: float): int = 12

let a = "hello"
let b = 3.1415926
doAssert fmt"{hello(x, y) = }" == "hello(x, y) = 12"
doAssert fmt"{x.hello(y) = }" == "x.hello(y) = 12"
doAssert fmt"{hello x, y = }" == "hello x, y = 12"
```

### Karax

Finally Let’s look at how elegant macros are. Karax is a framework for developing single page applications in Nim. You can also use Karax for server side HTML rendering. Instead of writing endless braces, you can use Nim syntax to write HTML. It’s more powerful. You can easily abstract multiple components and combine them together.

```nim
import karax / [karaxdsl, vdom]

const languages = @["Nim", "Python", "C++", "Java"]

proc render*(): string =
  let vnode = buildHtml(tdiv(class = "mt-3")):
    h1: text "Which is your favourite programming language?"
    p: text "echo Hello world"
    ul:
      for language in languages:
        li: text language
    dl:
      dt: text "Can I use Karax for client side single page apps?"
      dd: text "Yes"

      dt: text "Can I use Karax for server side HTML rendering?"
      dd: text "Yes"
  result = $vnode

echo render()
```

output:

```
<div class="mt-3">
  <h1>Which is your favourite programming language?</h1>
  <p>echo Hello Nim</p>
  <ul>
    <li>Nim</li>
    <li>Python</li>
    <li>C++</li>
    <li>Java</li>
  </ul>
  <dl>
    <dt>Can I use Karax for client side single page apps?</dt>
    <dd>Yes</dd>
    <dt>Can I use Karax for server side HTML rendering?</dt>
    <dd>Yes</dd>
  </dl>
</div>
```

Now let’s open it on the browser:

```nim
import browsers


let filename = "example.html"
writeFile(filename, render())
openDefaultBrowser(filename)
```



![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/p49ql67oxz25loqazo5h.jpg)


That’s it. Although Nim is still young and sometimes bugs drive me crazy,  It has promising futures. In the first sight, you may find it wired(I’m different, in the first sight I think it is elegant :P). Write some simple programs and you will find whether Nim is suitable for you.

The road is tortuous and the future is bright. We will eventually arrive.