100 Languages Speedrun: Episode 87: Sidef
Tags: sidef, perl
Published: 2022-02-12 14:14:55

Sidef is a prototype programming language, describing itself as inspired by Ruby, Raku, and Julia.

### Installation on OSX

It's not that easy to run Sidef. It runs on Perl 5 platform and you can install it through CPAN, but you'll run into serious problems.

Perl version bundled with OSX is not really recommended, so I used brew version. Unfortunately if we do this:

```
$ brew install perl
$ cpan Sidef
```

It will install `sidef` in `~/.cpan/build/Sidef-3.99-0/bin/sidef`, but it's hardcoded to use system Perl.

So I needed two additional steps. Symlink `~/.cpan/build/Sidef-3.99-0/bin/sidef` to some place in `$PATH` like `ln -s ~/.cpan/build/Sidef-3.99-0/bin/sidef ~/bin/`.

And edit `sidef` program to change its first line from `#!/usr/bin/perl` to `#!/usr/bin/env perl` so it will use the proper version, not the outdated one from OSX.

After all these steps, we're ready to go.

This is definitely something Sidef should just handle better. Oh and there's no VSCode syntax highlighting for Sidef. Often even very rarely used language have some.

### Hello, World!

Hello, World is completely unsurprising. No ugly semicolons here.

```
#!/usr/bin/env sidef

say "Hello, World!"
```

```
$ ./hello.sf
Hello, World!
```

### FizzBuzz

Sidef indeed looks like something between Ruby, Raku, and Julia.

```
#!/usr/bin/env sidef

(1..100).each { |n|
  if (n % 15 == 0) {
    say "FizzBuzz"
  } elsif (n % 5 == 0) {
    say "Buzz"
  } elsif (n % 3 == 0) {
    say "Fizz"
  } else {
    say n
  }
}
```

* range `1..100` goes from `1` to `100`
* there are no `...` ranges
* `{|args| ...}` blocks look like Ruby, but they're not used quite in the same way
* `if`  `elsif` `else` requires `()`s and `{}`s

### Blocks

Ruby blocks have special place in the language, and block argument is a separate thing from normal arguments. Not so in Sidef.

Sidef has very high level of syntax flexibility.

```
#!/usr/bin/env sidef

func twice(f) {
  say "Running it twice:"
  f()
  f()
}

var hi = { say "Hi!" }
twice(hi)
twice({ say "Hello!" })
twice { say "This does not work!" }

say ""
say "Iteration:"
{ |i| say "Block got: #{i}" }.each(10..12)
{ |i| say "Block got: #{i}" } << 13..14
for 15..16 { |i| say "Block got: #{i}" }
(17..18).each { |i| say "Block got: #{i}" }
{ |i| say "Block got: #{i+19}" } * 2
2.times { |i| say "Block got: #{i+21}" }

say ""
say "Single argument:"
60 |> { |i| say "Block got: #{i}" }
60 |> :inc |> { |i| say "Block got: #{i}" }
60 |> {|i| i + 2} |> { |i| say "Block got: #{i}" }
60 |> {_+3} |> { |i| say "Block got: #{i}" }
60 |> (:add, 4) |> { |i| say "Block got: #{i}" }
{ |i| say "Block got: #{i}" }(69)
```

```
$ ./blocks.sf
Running it twice:
Hi!
Hi!
Running it twice:
Hello!
Hello!

Iteration:
Block got: 10
Block got: 11
Block got: 12
Block got: 13
Block got: 14
Block got: 15
Block got: 16
Block got: 17
Block got: 18
Block got: 19
Block got: 20
Block got: 21
Block got: 22

Single argument:
Block got: 60
Block got: 61
Block got: 62
Block got: 63
Block got: 64
Block got: 69
```

Everything here works except for Ruby-style `twice { say "This does not work!" }` which does literally nothing, and I'm not sure why.

There are some debugging tools like `-c compile the code into a Perl program` and `-D dump the syntax tree of a program`, but the result is not really human readable.

I thought `-k keep track of potential unsafe parser interpretations` might say something about it, but that also doesn't say anything.

Having high degree of syntax flexibility is not that important for normal programming, but it's great for DSLs as it makes it easier for DSLs to pick something that works for them.

Especially the `|>` code is very cute.

### Fibonacci

Sidef supports `is cached` for memoization for free, which is a common enough use case that I don't know why more languages don't do this. There are also ways to clear the cache.

```
#!/usr/bin/env sidef

func fib(n) is cached {
  return 1 if (n <= 2)
  fib(n - 1) + fib(n - 2)
}

(1..100).each {|n|
  say "fib(#{n}) = #{fib(n)}"
}
```

```
$ ./fib.sf
fib(1) = 1
fib(2) = 1
fib(3) = 2
...
fib(98) = 135301852344706746049
fib(99) = 218922995834555169026
fib(100) = 354224848179261915075
```

### Operator Precedence

Sidef tries to be too cute with precedence rules.

```
#!/usr/bin/env sidef

say(2+3*4+5)
say(2 + 3 * 4 + 5)
say(2 + 3*4 + 5)
say(2+3 * 4+5)
```

Prints 4 different results:

```
$ ./math.sf
29
25
19
45
```

Sidef just doesn't have operator precedence, and instead tries to use spacing to determine what happens, in some completely insane way.

Not following standard operator precedence is the worst idea. Smalltalk tried that, and it killed the language. Every Smalltalk successor had to do all the painful things to unwind this stupid idea. And Smalltalk at least had simple consistent rules - operators always applies left to right. Sidef does something insane:

* without spaces, operators apply right to left (`2+3*4+5` is `2+(3*(4+5))`)
* with spaces, operators apply left to right (`2 + 3 * 4 + 5` is `((2+3)*4)+5`)
* with some spaces, operators apply left to right when there are spaces, and have extra parentheses in un-spaced group

This disqualifies the language.

### Wordle

Here's a Wordle game in Sidef:

```
#!/usr/bin/env sidef

var words = File.new("wordle-answers-alphabetical.txt").read.split
var word = words.rand
var guess = ""

while (guess != word) {
  print "Guess: "
  guess = STDIN.readline
  if (guess.size != 5) {
    say "Only 5 letter words allowed"
    next
  }
  {|i|
    if (word[i] == guess[i]) {
      print "🟩"
    } elsif (word.include(guess[i])) {
      print "🟨"
    } else {
      print "🟥"
    }
  } * 5
  print "\n"
}
```

It didn't go too bad:

```
$ ./wordle.sf
Guess: trial
🟥🟥🟥🟨🟥
Guess: maybe
🟥🟨🟥🟥🟩
Guess: snake
🟨🟥🟩🟥🟩
Guess: chase
🟥🟩🟩🟩🟩
Guess: phase
🟩🟩🟩🟩🟩
```

### Should you use Sidef?

No.

I support fun experiments, but Sidef has the absolute worst way of doing something as simple as adding numbers, making it pretty much unusable for anything.

An even bigger problem is what when you write some code, Sidef decides what it means, and half the time it will decide that you meant something else than you did. There's no error messages, no documentation, no syntax debugging tools, the code will just do whatever it feels like. Sometimes code does something else. Very often the code just doesn't do anything, and that's a lot more baffling.

I think to make Sidef usable for even casual play it would need at least:

* fix operator precedence
* have way better error messages
* have some kind of "print how it parsed" option, that adds a lot of `()`s and such to tell you what the hell Sidef thinks you just did
* working OSX package

With these issues fixed, maybe Sidef could become something I could recommend playing with for a weekend or two. In its current state, just no.

### Code

[All code examples for the series will be in this repository](https://github.com/taw/100-languages-speedrun).

[Code for the Sidef episode is available here](https://github.com/taw/100-languages-speedrun/tree/master/episode-87-sidef).