Home

Daniel Șuteu edited this page Jan 11, 2018 · 439 revisions
Clone this wiki locally

BOOK

The Sidef Programming Language: http://trizen.gitbooks.io/sidef-lang/content/

Introduction

Sidef is a modern, high-level, general-purpose programming language, focusing on simplicity, readability and elegance, taking the best from languages like Ruby, Go, Perl 6 and Julia.

Installation

Sidef can be installed automatically from the CPAN, by invoking the following command:

$ cpan Sidef

When the cpan command is not available, try:

$ perl -MCPAN -e "CPAN::Shell->install(q{Sidef})"

IMPORTANT: Sidef needs the GMP, MPFR and MPC C libraries.

To install Sidef manually, download the latest version, unzip it and follow the installation steps:

$ perl Build.PL
# ./Build installdeps
# ./Build install

When Module::Build is not installed, try:

$ perl Makefile.PL
$ make test
# make install

For packaging Sidef, run:

$ perl Build.PL --destdir "/my/package/path" --installdirs vendor
$ ./Build test
$ ./Build install --install_path script=/usr/bin

If the installation succeeded, you should be able to run the sidef command:

$ sidef -h

Run Sidef without installing it

It is also possible to run Sidef without having to install it. If you're using a Unix-like OS, all you need to do is to execute the following commands:

$ wget 'https://github.com/trizen/sidef/archive/master.zip' -O 'master.zip'
$ unzip 'master.zip'
$ cd 'sidef-master/bin/'
$ ./sidef -v

Those commands will download and unpack the latest version of Sidef and will execute the bin/sidef script which will print out the current version of the language.

To execute a Sidef script, run:

$ ./sidef ../scripts/sierpinski_carpet.sf

You can, also, add the following alias to your ~/.bashrc or ~/.zshrc:

alias sidef="/path/to/bin/sidef"

Creating the first Sidef script

A Sidef script can be written in any text editor and, by convention, it has the .sf extension.

The content of a simple Hello World program looks like this:

#!/usr/bin/sidef

say "Hello, 世界";

If we save the content in a new file called hello.sf, we can execute the code by running:

$ sidef hello.sf

Real code

Before taking a closer look at the syntax of the language, let's take a brief look at how a real program might look like. The following program defines the Bitmap class and generates a PPM file with a color palette:

subset Int   < Number {|n| n.is_int  }
subset UInt  < Int    {|n| n >= 0    }
subset UInt8 < Int    {|n| n ~~ ^256 }

struct Pixel {
    R < UInt8,
    G < UInt8,
    B < UInt8
}

class Bitmap(width < UInt, height < UInt) {
    has data = []

    method fill(Pixel p) {
        data = (width*height -> of { Pixel(p.R, p.G, p.B) })
    }

    method setpixel(i < UInt, j < UInt, Pixel p) {

        subset WidthLimit  < UInt { |n| n ~~ ^width  }
        subset HeightLimit < UInt { |n| n ~~ ^height }

        func (w < WidthLimit, h < HeightLimit) {
            data[w*height + h] = p
        }(i, j)
    }

    method p6 {
        <<-EOT + data.map {|p| [p.R, p.G, p.B].pack('C3') }.join
        P6
        #{width} #{height}
        255
        EOT
    }
}

var b = Bitmap(width: 125, height: 125)

for i,j in (^b.height ~X ^b.width) {
    b.setpixel(i, j, Pixel(2*i, 2*j, 255 - 2*i))
}

%f"palette.ppm".write(b.p6, :raw)

By executing the code, the following image is produced:

palette

Another example is the implementation of the LCG algorithm, illustrating modules and classes.

module LCG {

  # Creates a linear congruential generator with a given seed.
  class Common(seed) {
     has r = seed
  }

  # LCG::Berkeley generates 31-bit integers using the same formula
  # as BSD rand().
  class Berkeley < Common {
    method rand {
      self.r = ((1103515245 * self.r + 12345) & 0x7fff_ffff);
    }
  }

  # LCG::Microsoft generates 15-bit integers using the same formula
  # as rand() from the Microsoft C Runtime.
  class Microsoft < Common {
    method rand {
      self.r = ((214013 * self.r + 2531011) & 0x7fff_ffff);
      self.r >> 16;
    }
  }
}

var lcg1 = LCG::Berkeley(1)
say 5.of { lcg1.rand }

var lcg2 = LCG::Microsoft(1)
say 5.of { lcg2.rand }

Output:

[1103527590, 377401575, 662824084, 1147902781, 2035015474]
[41, 18467, 6334, 26500, 19169]

A last example, implementing a simple algorithm that finds the longest common substring from two given strings, illustrating the declaration of functions with typed parameters, the gather/take construct and the set-intersection operator (&).

func createSubstrings(String word) -> Array {
  gather {
    combinations(word.len+1, 2, {|i,j|
        take(word.substr(i, j-i))
    })
  }
}

func findLongestCommon(String first, String second) -> String {
    createSubstrings(first) & createSubstrings(second) -> max_by { .len }
}

say findLongestCommon("thisisatest", "testing123testing")

Syntax

The syntax of Sidef looks very much like the syntax of Ruby or JavaScript, but we'll see that there are some important differences regarding the semantics.

All operators have the same precedence, which is controlled by the lack of whitespace between the operands.

1+2 * 3+4   # means: (1+2) * (3+4)

In the above example, the lack of whitespace between 1, + and 2, classifies the operation as a distinct expression.

The implications are the following:

var n = 1 + 2       # incorrect -- it means: (var n = 1) + 2
var n = 1+2         # correct
var n = (1 + 2)     # correct

When no precedence is defined, the order of operations is from left to right:

1 + 2 * 3       # means: ((1 + 2) * 3)

On the other hand, when too much precedence is defined, the order is from right to left:

1+2*3           # means: (1 + (2 * 3))

Starting with Sidef 2.30, the precedence can also be controlled by backslashing or preceding the operator with a dot.

1 + 2 \* 3      # means: (1 + (2 * 3))
1 + 2 .* 3      # =//=

The infix backslash (\) removes any leading or trailing whitespace at that current position and it's useful for expanding method calls on multiple lines:

say "abc".uc      \
         .reverse \
         .chars

is equivalent with:

say "abc".uc.reverse.chars

Keywords

Initially, Sidef was designed without any keywords. However, as it turned out, keywords can simplify the writing of code, at the cost of not having a variable with the same name as that of a keyword.

var             # declare a lexical variable
local           # declare a local dynamic variable
func            # declare a function
class           # declare a class
module          # declare a module
subset          # declare a subset
struct          # declare a structure
const           # declare a run-time dynamic constant
static          # declare a run-time static variable
define          # declare and assign a compile-time static constant
enum            # declare and assign a list of constants with distinct numbers

eval            # evaluates any arbitrary Sidef code in the current scope
warn            # prints an warning to STDERR followed by the current file name and the line number
die             # raises an error followed by the current file name and the line number
read            # reads from standard input a type of data (e.g.: read(Number))
print           # writes to the standard output
say             # same as print, except that it also writes a newline character at the end

assert          # terminate the program if the argument is false
assert_eq       # terminate the program if two arguments are not equal to each other
assert_ne       # terminate the program if two arguments are equal to each other

nil             # the `not initialized` value
true            # boolean representation for a true value
false           # boolean representation for a false value

if              # `if` statement
with            # `with` statement
while           # `while` statement
loop            # infinite loop
for             # `for` loop
given           # `given/when` statement
gather          # `gather/take` statement
continue        # fall-through in a `given/when` statement
return          # stop anything and return a value from a function
break           # break the current loop
next            # go to the next loop iteration

include         # load a Sidef module
import          # import a list of identifiers from a given module

Prefix operators

Sidef defines the following list of built-in prefix operators:

>               # alias for `say`
>>              # alias for `print`
+               # same object (no-op)
-               # the negative value of an object (calls `neg`)
++              # increments a variable's value (calls `inc`)
--              # decrements a variable's value (calls `dec`)
~               # the 'not' value of an onject (calls `not`)
\               # takes reference to a variable
*               # dereferences a reference
:               # initializes a new hash
!               # the negation of an object in Boolean context
^               # an exclusive range (from 0 to n-1) (calls `range`)
@               # array context (calls `to_a`)
@|              # list context (calls `...`)# square root of a number

Postfix operators

Additionally to prefix operators, Sidef also defines a small list of postfix operators:

++               # post-increments a variable's value (calls `inc`)
--               # post-decrements a variable's value (calls `dec`)
!                # factorial operation
!!               # double-factorial operation
...              # unpacks an object into a list

Built-in types

This objects deal with the data types in Sidef and implements the useful methods visible to the user.

File
FileHandle
Dir
DirHandle
Arr Array
Pair
Hash
Str String
Num Number
RangeStr RangeString
RangeNum RangeNumber
Complex
Math
Pipe
Ref
Socket
Bool
Sys
Sig
Regex Regexp
Time
Perl
Sidef
Object
Parser
Block
Backtick
LazyMethod

Ignoring the performance differences, a string "" is just the same thing as String().

[1,2,3]                    # same as: Array(1,2,3)

true                       # same as: Bool(1)
false                      # same as: Bool(0)

/^some[regex]?/i           # same as: Regex('^some[regex]?', 'i')
:(key => "value")          # same as: Hash("key", "value")
"first":"second"           # same as: Pair("first", "second")

Method invocations

A method is a function defined for a specific type of object. For strings, we have a method called length, which differs from the method with the same name that is defined for array-type objects.

"string".length         #=> 6
[1,2,3].length          #=> 3

In many languages, method invocations require parentheses, but in Sidef the parentheses are optional when we call a method without any argument.

Methods

For calling a method, we have the following notation:

obj.method_name(arg)

For convenience, methods can also be invoked using the following prefix notation:

method_name(obj, arg)

The prefix and postfix notations can be used interchangeably:

log("string".length)    # means: "string".length.log
length("string").log    # =//=
log(length("string"))   # =//=

Starting with Sidef 2.30, a method can be invoked, using the prefix notation, even when a function with the same name is declared in the same scope:

func sqrt(n) { "sqrt of #{n} is #{n.sqrt}" }

say   sqrt(42)     # calls the `sqrt` function defined above
say ::sqrt(42)     # calls the `Number.sqrt()` method

Also, by placing :: in front of a method name, Sidef will parse it as a prefix operator, which allows the parenthesis to be omitted when invoked on only one object:

::log 42       # means: log(42)
::sqrt 1+2     # means: sqrt(1+2)

There are two method-invocation operators in Sidef: . and ->:

20 + 5.sqrt    # means: 20 + sqrt(5)
20 + 5->sqrt   # means: sqrt(20 + 5)

-> will make everything, from the left-most side of it, a single expression and applies the method on the result returned by this expression, while . will simply apply the method on the object which precedes the dot.

Additionally, any alphanumeric method name can be used as an infix operator, by surrounding it with backticks:

(1 `add` 2)           # means: 1.add(2)
(Math `sum` (1,2,3))  # means: Math.sum(1,2,3)

There is also support for calling a method of which name is not known until at run-time, which can be any expression that evaluates to a string:

say ( 50.(["+", "-"].rand)(30) )    # prints 20 or 80

If a method is not found for a given object, Sidef will throw a run-time error.

Built-in classes

In the current implementation of the language, we have the following built-in classes:

Variables

Variables are commonly declared using the var keyword:

var num = 42
var str = "42"
var bool = true

Variable types

In Sidef exists four types of variables: static variables, lexical variables, global variables and local variables.

STATIC VARIABLES

This type of variables are static, block-scoped and initialized only once.

static x = 42   # sets the static x to 42
say x           # prints the static value of x

LEXICAL VARIABLES

This kind of variables are dynamic, but statically block scoped. This is the usual way of declaring variables in Sidef.

var x = 42    # sets the lexical x to 42
say x         # prints the lexical value of x

GLOBAL VARIABLES

Global variables are declared at the top-level of the current namespace. They can be accessed from everywhere, anytime. However, try to avoid using them, unless you really don't have any better alternative.

global x = 42     # sets global x to 42
say x             # prints the global value of x

LOCAL VARIABLES

Local variables (also known as "dynamically scoped variables") are used to localize array/hash lvalues or global variables to a limited scope.

global x = 42    # sets the global x to 42
do {
   local x = 100     # localizes x inside this block to 100
   say x             # prints the local value of x (100)
}
say x            # prints the global value of x (42)

Variable scoping

All variables, including functions and classes, are block scoped in the following way:

var x = 'o'

do {
    say x              # o
    var x = 'm'
    say x              # m
    do {
        say x          # m
        var x = 'b'
        say x          # b
    }
    say x              # m
}

say x                  # o

Declaring multiple variables on the same line works like expected:

var (x, y, z) = (3.14, false, "foo")

We can, also, declare variables with some default values:

var (x, y=755, z=777) = (666, 655)

say x        # prints: 666
say y        # prints: 655
say z        # prints: 777

Slurpy variables:

Slurpy variables are a special type of variables which can be initialized with a list of values, creating automatically a container to hold the data.

var *arr = (1,2,3)    # creates an Array
say arr               # prints: [1,2,3]

var :hash = (a => 1, b => 2)   # creates an Hash
say hash                       # prints: Hash(a => 1, b => 2)

Working with variables

Any method applied to a variable is applied to the object stored inside the variable.

var x = 'sidef'
say x.uc        # prints: `SIDEF`
say x           # prints: `sidef`

Special ! at the end of a method changes the variable in-place (almost like in Ruby):

var x = 'sidef'
x.uc!            # notice the `!`
say x            # prints: `SIDEF`

Appending the = sign at the end of arithmetic operators, the variable will be changed in place:

var x = 5
x += 10            # adds 10 to "x"
say x              # prints: 15

Special variables

Currently, there are only two real predefined variables: ARGV and ENV.

ARGV.each { |arg|
   say arg
}

say ENV{:HOME}

This variables are defined only if they are used anywhere inside the program.

Topic variable

The special topic variable (_) is declared at compile-time in all the block-objects of a program. You may not see its real name very often, because it has been overtaken by the elegant unary dot (.) operator:

[25,36,49].map {.sqrt} \
          .each{.log.say}

.sqrt really means _.sqrt, and .log.say means _.log.say.

The map method iterates over the anonymous array and calls the block for each element of the array, which gets set to the topic variable _. When the iteration is complete, the map method will return the new array, on which we call the each method with another block as the argument.

The each method, as the map method, will iterate over the array and will run the block for each element of the array, which will get assigned to the topic variable _, on which we call the log method, followed by say.

Additionally, same as in Perl 6, we can lookup an element from an array or an hash stored inside the topic variable, using the following syntax:

say [[41, 'a'], [42, 'b'], [43, 'c']].map { .[0] }          #=> [41, 42, 43]

and

say [Hash(a=>41), Hash(a=>42), Hash(a=>43)].map { .{:a} }   #=> [41, 42, 43]

Magic variables

Sidef's magic variables are directly bound to Perl's magic variables.

local $/ = nil        # changes the input record separator
local $\ = "\n"       # change the output record separator
local $, = "\n"       # changes the field record separator
say $^PERL            # prints the path to perl executable
say $^SIDEF           # prints the path to sidef executable

File-handle constants

This constants look like variables, but are actually file-handles.

STDERR.print("Some error!")
STDOUT.print("Some output...")
STDIN.readline()   # reads a line from the standard input

Another interesting file-handle is the ARGF which will read lines from argument-files or from standard-input when no argument has been specified.

Here is the implementation of a very basic cat-like program:

ARGF.each { |line|
    print line
}

Like in Perl, there is also the DATA file-handle which will point to the data stored after the __END__ or __DATA__ tokens.

DATA.each { |line|
    say "=>> #{line.chomp}"
}

__DATA__
here are
some data
lines

Constants

Sidef implements three kinds of constants:

const

The common way of declaring constants in Sidef, is by using the const keyword:

const pi = 3.14
say pi                 # prints: 3.14
#pi = 3                # compile-time error: can't modify non-lvalue constant

This kind of constants are created dynamically at runtime and cannot be changed after initialization.

When declared inside function or a class, the constant is created and initialized dynamically, as illustrated in the following example:

func f(a) {
    const x = a          # created dynamically at each function call
    return (x + 2)
}

say f(40)       #=> 42
say f(50)       #=> 52

define

This keyword will define a compile-time evaluated constant and will point directly to the object at which it evaluated to.

define PHI =   (1.25.sqrt + 0.5)
define IHP = (-(1.25.sqrt - 0.5))

say (PHI**7 - IHP**7 / PHI-IHP)

This type of constants are the most efficient ones.

enum

enum will automatically declare and assign a list of constants with ascending numeric values (starting with 0):

enum |Black, White|
say Black             # prints: 0
say White             # prints: 1

Alternatively, we have the possibility for specifying an initial value, which will get incremented after each declaration, by calling the method inc.

enum |α="a", β|
say α             # prints: 'a'
say β             # prints: 'b'

Variable references

Like other programming languages, Sidef is capable of taking references to variables.

var name = "sidef"
var ref = \name        
var original = *ref

Variable references are useful when passing them to functions (or methods) for assigning values.

func assign2ref (ref, value) {
    *ref = value
}

var x = 10
assign2ref(\x, 20)
say x                 # prints: 20

Blocks

In Sidef, a block of code is an object which encapsulates one or more expressions. The block is delimited by a pair of curly braces ({}).

var block = {
    say "Hello, World!"
}

Block callbacks

Blocks are also used as arguments to many built-in methods as callback blocks:

{ print "Sidef! " } * 3        # prints "Sidef! Sidef! Sidef! "
5.times {|x| print x }         # prints "12345"
[1,2,3].sort {|a,b| b <=> a }  # returns a new array: [3,2,1]

Starting with version 3.00, Sidef introduced the following Block methods:

say {|n| n**2 }.map(1..5)        #=> [1, 4, 9, 16, 25]
say { .is_odd }.grep([1,2,3,4])  #=> [1, 3]

...and the Block.each method, which is also aliased as <<:

{|n| say n**2 }.each(1..5)
{|n| say n**2 } << 1..5

Each of those methods accept more than one argument, which can be any object that accepts the .iter() method, as in the following example:

{|n| say n**2 } << (1..3, 100..103, 1000..1003)

Block parameters

For declaring block parameters, Sidef borrows Ruby's way of doing this, by using the |arg1, arg2, ...| special syntax.

{  |a, b|

   say a            # prints: 1
   say b            # prints: 2

}(1, 2)

Default block parameter values

We can also specify default values for block parameters. This can be done by using the syntax: arg=value.

say { | a=3, b=4 | a + b }(9)        # prints the result of: 9 + 4

The default value can be any expression:

say { | a=1/2, b=(a**2) | a + b }(5)      # prints the result of: 5 + 5**2

Lazy evaluation

Lazy evaluation is a very common feature in functional programming languages and provides an way to delay the evaluation of an expression until the result is actually needed.

By default, Sidef is an eagerly evaluated language, but it still supports a form of lazy evaluation, which is provided by the method Object.lazy():

say (^Inf -> lazy.grep{ .is_prime }.first(10))     # first 10 primes

The .lazy method returns a Lazy object, which behaves almost like an Array, except that it executes the methods in a pipeline fashion and dynamically stops when no more elements are required, without creating any temporary arrays in memory:

for line in (DATA.lazy.grep{ _ < 'd' }.map{ print ">> "; .uc }) {
   print line
}

__DATA__
a
b
c
d

Output (which shows that .map{} is really lazy):

>> A
>> B
>> C

This mechanism is generic enough to support any kind of object that implements the .iter() method, which returns a Block that gives back one element at a time when it's called with no arguments. When the iteration ends, it must return nil.

class Example(data) {
    method iter {
        var p = 0
        {
            data[p++]
        }
    }
}

var obj = Example([1,2,3,4,5])
say obj.lazy.grep{.is_prime}.to_a       # filters all the primes lazily

Currently, the .iter() method is defined in the following built-in classes: Array, String, FileHandle, DirHandle, RangeString, RangeNumber and Lazy.

Lazy methods

A lazy method is an interesting concept of partially applying a method on a given object, delaying the evaluation until the result is needed.

var lz = 42.method('>')        #=> LazyMethod
say lz(41)                     #=> true (i.e.: 42 > 41)

Object.method() returns a LazyMethod object which can be called just like any function, producing the result by calling the method which is stored inside the LazyMethod object.

This allow us to store or pass around partial expressions, which can be evaluated multiple times at any point in the future:

var str = "a-b-c"
var lzsplit = str.method(:uc).method(:split)

say lzsplit('')         #=> ["A", "-", "B", "-", "C"]
say lzsplit('-')        #=> ["A", "B", "C"]

This concept can also be used to create partial virtual functions:

var add1 = 1.method('+')
var eqv2 = 2.method('==')

say (1..100 -> map{ add1(_) }.grep{ eqv2(_) })    #=> [2]

Method introspection

Object.methods() provides a simple way to find the methods implemented in the class of the self object. It returns a Hash with the method names as keys and LazyMethod objects as values.

Example:

var methods = (1..10 -> methods)
say methods.keys                   #=> ["call", "dump", "iter", "new", "prod", "sum"]
say methods{:sum}()                #=> 55

Functions

Like mathematical functions, Sidef's functions can be recursive, take arguments and return values.

func hello (name) {
   say "Hello, #{name}!"
}

hello("Sidef")

Recursive functions:

func factorial (n) {
   if (n > 1) {
       return (n * factorial(n - 1))
   }
   return 1
}

say factorial(5);     # prints: 120

Anonymous functions:

func recmap(repeat, seed, transform, callback) {
    func (repeat, seed) {
        callback(seed)
        repeat > 0 && __FUNC__(repeat-1, transform(seed))
    }(repeat, seed)
}
 
recmap(6, "0", func(s) { s + s.tr('01', '10') }, func(s) { say s })

Storing functions in variables:

var f = func (name) {
     say "#{name} says 'Hello!'"
}
f('Sidef')

Function re-declaration:

func f(name) {
   say "Hello, #{name}"
}
f("Sidef")

f = func (name, age) {
   say "Hello, #{name}! You claim to be #{age} years old."
}
f("Sidef", 100)

Closures

In Sidef, all functions are first-class objects which can be passed around like any other object. Additionally, all functions and methods are lexical closures.

func curry(f, *args1) {
    func (*args2) {
        f(args1..., args2...)
    }
}

func add(a, b) {
    a + b
}

var adder = curry(add, 1)
say adder(3)                   #=>4

Automatically cached functions

By specifying the cached trait to a function or a method, Sidef will automatically cache it for you.

func fib(n) is cached {
    return n if (n <= 1)
    fib(n-1) + fib(n-2)
}
say fib(100)              # prints: 354224848179261915075

Starting with Sidef 3.04, the method Block.cache() will turn on the memoization on the self-block. Correspondingly, Block.uncache() will disable the memoization mechanism and cleans-up the cache on the self-block.

func fib(n) {
   n <= 1 ? n : fib(n-1)+fib(n-2)
}

fib.cache       # enables memoization
say fib(100)    #=> 354224848179261915075
fib.uncache     # disables memoization

Function parameters

The parameters of a function can be defined to have a default value when the function is called with a lower number of arguments than required.

func hello (name="Sidef") {
    say "Hello, #{name}!"
}

hello()           # prints: "Hello, Sidef!"
hello("World")    # prints: "Hello, World!"

The default value of a parameter is evaluated only when an argument is not provided for that particular parameter, and it can be any expression:

func foo (a = 1.25.sqrt, b = 1/2) {
     a + b
}

say foo()           # prints the result of: sqrt(1.25) + 1/2
say foo(21, 21)     # prints: 42

An interesting feature is the possibility of referring to a previously defined parameter as a default value:

func foo(a=10, b=a+1) {
    a + b
}

say foo()              # prints: 21 (the result of: 10 + 10+1)
say foo(1)             # prints: 3  (the result of: 1 + 1+1)
say foo(21,21)         # prints: 42 (the result of: 21 + 21)

Named parameters (a.k.a. keyword arguments)

This is a very nice feature which allows a function to be called with named parameters, giving us the flexibility to put the arguments in no specific order:

func div(a, b) {
   a / b
}

say div(b: 5, a: 35)  # prints: 7

Variadic functions

A slurpy variable in the form of *name can be used as a function parameter to collect the remaining arguments inside an array:

func f(*args) {
   say args       #=> [1, 2, 3]
}

f(1, 2, 3)

Alternatively, by using a named variable in the form of :name, the arguments are collected inside an hash:

func f(:pairs) {
   say pairs      #=> Hash(a => 1, b => 2)
}

f(a => 1, b => 2)

Typed parameters

A function can be declared with typed parameters, which are checked at run-time.

func concat(String a, String b) {
    a + b
}

Now, the function can only be called with strings as arguments:

say concat("o", "k")  # ok
say concat(1, 2)      # run-time error

The typed parameters require a specific type of object, but they do not default to anything when no value is provided.

This means that all the typed-parameters are mandatory, unless a default value is provided:

func concat(String a="foo", String b="bar") {
   a + b
}

say concat()          # prints: "foobar"
say concat("mini")    # prints: "minibar"
say concat(1, 2)      # this is still a run-time error

Subsets

A subset is a definition which specifies the upper limit of inheritance, with optional argument validation.

subset Integer     < Number  { |n| n.is_int }
subset Natural     < Integer { |n| n.is_pos }
subset EvenNatural < Natural { |n| n.is_even }

func foo(n < EvenNatural) {
    say n
}

foo(42)         # ok
foo(43)         # failed assertion at run-time

In some sense, a subset is the opposite of a type. For example, let's consider the following class hierarchy:

class Hello(name) {
    method greet { say "Hello, #{self.name}!" }
}

class Hi < Hello {
    method greet { say "Hi, #{self.name}!" }
}

class Hey < Hi {
    method greet { say "Hey, #{self.name}!" }
}

If we declare a function that accepts a subset of Hi, it will accept Hello, but it cannot accept Hey:

func greet(obj < Hi) { obj.greet }       # `Hi` is the upper limit

greet(Hi("Foo"))        # ok
greet(Hello("Bar"))     # ok
greet(Hey("Baz"))       # fail: `Hey` is too evolved

On the other hand, if we use Hi as a type assertion, it will accept Hey, but not Hello:

func greet(Hi obj) { obj.greet }        # `Hi` is the lower limit

greet(Hi("Foo"))        # ok
greet(Hey("Baz"))       # ok
greet(Hello("Bar"))     # fail: `Hello` is too primitive

Subsets can also be used for combining multiple types into one type, creating an union type:

subset StrNum < String, Number

func concat(a < StrNum, b < StrNum) {
    a + b
}

say concat("o", "k")       # ok
say concat(13, 29)         # 42
say concat([41], [42])     # runtime error

Multiple dispatch

Starting with version 2.12, Sidef includes multiple dispatch for functions and methods, based on the number of arguments and their types.

func test(String a){
    say "Got a string: #{a}";
}

func test(Number n) {
    say "Got a number: #{n}";
}

func test(Number n, Array m) {
    say "Got a number: #{n} and an array: #{m.dump}";
}

func test(String s, Number p) {
    say "Got a string: #{s} and a number: #{p}";
}

test("hello", 21);
test("sidef");
test(12, [1,1]);
test(42);

Output:

Got a string: hello and a number: 21
Got a string: sidef
Got a number: 12 and an array: [1, 1]
Got a number: 42

Functional pattern matching

This is one of the newest and nicest features of Sidef and it's available starting with version 2.12.

The feature looks like this:

func fib ((0)) { 0 }
func fib ((1)) { 1 }
func fib  (n)  { fib(n-1) + fib(n-2) }

say fib(12)      # prints: 144

We have three functions, where the first two, each have an expression as a parameter. Sidef will check each expression for equality with a given argument and will call the corresponding function when it passes the test. Otherwise, it will default to the third function.

Alternatively, we can specify a block instead of an expression:

func fib (Number n { _ <= 1} = 0) {
    return n
}

func fib (Number n) {
    fib(n-1) + fib(n-2)
}

say fib(12)      # prints: 144

When a block is specified, the type and the name of the variable must come before the block and any default values goes after the block. The types and the default values can be omitted.

The name of the parameters can be omitted as well:

func fib({.is_neg})  { NaN }
func fib({.is_zero}) { 0 }
func fib({.is_one})  { 1 }
func fib(n)          { fib(n-1) + fib(n-2) }

say fib(12)      # prints: 144

Combining the power of multiple dispatch with subsets and pattern matching, we can achieve impressive results, as illustrated in the following example, which implements the arithmetic derivative recursively for all integers and fractions:

subset Integer  < Number   { .is_int   }
subset Positive < Integer  { .is_pos   }
subset Negative < Integer  { .is_neg   }
subset Prime    < Positive { .is_prime }

func arithmetic_derivative((0)) { 0 }
func arithmetic_derivative((1)) { 1 }

func arithmetic_derivative(_ < Prime) { 1 }

func arithmetic_derivative(n < Negative) {
    -arithmetic_derivative(-n)
}

func arithmetic_derivative(n < Positive) is cached {

    var a = n.factor.rand
    var b = n/a

    arithmetic_derivative(a)*b + a*arithmetic_derivative(b)
}

func arithmetic_derivative(Number n) {
    var (a, b) = n.parts
    (arithmetic_derivative(a)*b - arithmetic_derivative(b)*a) / b**2
}

printf("(42!)' = %s\n", arithmetic_derivative(42!))

Returned type

Sidef also has the capability to check the return type of a function and stop the execution of the program if the returned type doesn't match the type defined in the function declaration.

func ieq(a, b) -> Bool {
   a.lc == b.lc
}

say ieq("Test", "tEsT")    # true

On the other hand:

func concat(a, b) -> Array {
    a + b
}

say concat([1,2,3], [4,5,6])   # ok
say concat("123", "456")       # run-time error

Multiple return-type checks are supported as well:

func foo() -> (Number, String) {
   (42, "foo")
}
var (a, b) = foo()

Alternative function declaration

A function can also be declared by using the fancy unary operator ->, which is synonym with the func or method keywords, depending on the context where it is used.

-> my_add(a,b) { a + b }

If the function is declared inside a class, it will be defined as a method belonging to that class. However, if the function is declared inside another method, it will be defined as a private function in the scope of that method, as expected.

The name of the function is optional. If the name is omitted, an anonymous function will be created.

[1,2,3].sort(->(a,b) { b <=> a })

This notation is close to a lambda function, as illustrated in the following declaration of the Y-combinator:

var y = ->(f) {->(g) {g(g)}(->(g) { f(->(*args) {g(g)(args...)})})}

var fib = ->(f) { ->(n) { n < 2 ? n : (f(n-2) + f(n-1)) } }
say 10.of { |i| y(fib)(i) }

Output:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Loops

Creating loops in Sidef is as simple as it can get. Any loop can be stopped with the break keyword.

Infinite looping with the loop keyword:

loop {
  say "Sidef is looping!"
}

while loop:

var x = 123456789
while (x > 0) {
   var (div, mod) = x.divmod(10)
   say mod
   x = div
}

do-while loop:

var n = 1
do {
   say n
} while (++n <= 10)

for loop:

for (var i = 0; i <= 10; i++) {
   say i
}

for-in loop:

for item in (1..5) {
    say item
}

foreach loop:

for (1..5) { |item|
    say item
}

.each method:

(1..5).each { |i|
    say i**2
}

Repetition block:

{ |n|
  say "Hello there! (#{n})"
} * 10

or:

n.times { ... }

Recursive block:

{
  say "Hello, World!"
  __BLOCK__.run
}.run

Things to remember:

  1. run is used for running a block.
  2. call is used for calling a function.
  3. .each{} method is used for iteration over arrays.
  4. while is used to run the block as long as the condition is true.
  5. |x,y,z| is used to capture the block arguments in variables.
  6. The keyword break will break a loop.
  7. Any block is an object and can be stored in variables and executed at any time.

Strings

"I am a string"

A string is a group of characters which exists inside the same object.

var new_str = ("a" + "b")
say new_str   # prints: "ab"

A Sidef-string have many methods which will really help working with strings in a new exciting way.

Most used methods are:

.uc                     # uppercase the string
.lc                     # lowercase the string
.wc                     # capitalize each word
.tc                     # capitalize the string
.reverse                # reverse the string
.trim                   # removes leading and trailing whitespace

.length                 # string size, in characters
.is_empty               # true if string has 0 length

.div(N)                 # divide the string into N chunks
.split(N)               # split the string by N characters
.split('str')           # split the string by 'str'
.split(/regex/)         # split the string by a regular expression
.each{|c| ...}          # iterate over string characters
.each_word{|w| ...}     # iterate over words
.each_line{|l| ...}     # iterate over lines

.char(3)                # returns the character at the specified index
.substr(beg, len)       # returns a sub-string from position beg
.begins_with('str')     # true if string begins with 'str'
.ends_with('str')       # true if string ends with 'str'
.contains('str')        # returns true if string contains 'str'
.index('str')           # returns the position where 'str' begins
.match(/regex/)         # returns a Match object

.sub(/regex/, 'str')    # regexp substitution (returns a new string)
.gsub(/regex/, 'str')   # global regexp substitution (returns a new string)

For more methods, see: String.pod

String quotes

Being a new programming language, Sidef has built-in support for Unicode quotes.

var dstr = „double quoted”
var sstr = ‚single quoted’

The difference between double-quoted and single-quoted strings is the following:

  1. double-quoted strings can interpolate code.
    • example: "code: #{say 'inside string'}"
  2. double-quoted strings understand special escapes.
    • example: "\n, \t, \Q...\E"

while the single-quoted strings can't do any of this.

say 'single\tquoted'   # prints the string as it is
say "double\tquoted"   # replaces '\t' with a tab-character

var name = "string";
say 'single quoted #{name}'     # prints the string as it is
say "double quoted #{name}"     # prints: "double quoted string"

A single word can also be quoted by placing : in front of it. This feature makes things easier when it's used in hash look-ups and hash literal definitions.

:word == 'word'
:another_word == 'another_word'

Sidef also borrow from Ruby some operator-like quotes and implements some new ones:

var sstr = %q{single {} quoted string}
var dstr = %Q«double «» quoted string»
var arr1 = %w(word1 word2)
var arr2 = %W(double quoted words)
var arr3 = <single <quoted> words>
var arr4 = «double «quoted» words»
var reg  = %r「some\h+regxp?」
var file = %f„filename.txt”
var dir  = %d‘/my/dir’
var pipe = %p(ls -l)
var tick = %x(uname -r)

Simple HERE-doc support:

var str = <<'EOF'
some
text
EOF

Indented HERE-document (with interpolation):

func hello(arg) {
    return <<-"EOT"
    Hello, #{arg}!
    EOT
}

print hello('Sidef')    # prints: 'Hello, Sidef!'

You can also single-quote an indented HERE-document, which will not interpolate:

{
    print <<-'EOT';
    Not interpolated: #{1+2}
    EOT
}.run;     # prints: 'Not interpolated: #{1+2}'

Nested HERE-docs are supported as well:

print(<<'EOF', "- - -\n", <<'EOT' + <<"EOD");
1 2 3
EOF
4 5 6
EOT
7 8 9
EOD

Numbers

In Sidef, numbers play an important role and are treated correspondingly. Starting with version 2.20, the numerical system is implemented with Math::GMPq, Math::GMPz, Math::MPFR, and Math::MPC, giving us a really good precision in calculations with an astonishing performance.

say Number.pi       # prints: 3.1415926535897932384626433832795
say 2**999          # 535754303593133660474212524530...0214915826312193418602834034688

Integers:

Here is 255 written as integer in different bases:

255;              # decimal
0xff;             # hexadecimal
0377;             # octal
0b1111_1111;      # binary

Float

In Sidef, there is no real distinction between integers and floating-point numbers, as all numbers are represented by Math::GMPq in rational form.

1.234;          # 1.234
.1234;          # 0.1234
1234e-5;        # 0.01234
12.34e5;        # 1234000

Arrays

var array = [1, 2, 3, 4, 5]

array[0] = 6
array[1] = 7

say array

Arrays are simple objects which can store other objects, and provide a zero-based indexing. In Sidef, an array can grow as much as the system memory permits it.

Array autovivification:

var array = []
array[3][4] = "hei"
say array

If you're familiar with Perl, you already know about autovivification. It's the feature responsible for the dynamic creation of data structures.

Array filtering

The Array object has many interesting methods for making it safer and easier to work with arrays in a pure OO style.

greping some elements from an array:

var new_arr = arr.grep { _ > 10 }      # returns a new array containing numbers greater than 10

maping an array:

var new_arr = arr.map {|n| 2*n + n**2}   # returns a new array with the result returned from the "map" block

sorting an array:

# generic sort
var new_arr = arr.sort

# naive string case-insensitive sort
var new_arr arr.sort {|a,b| a.lc <=> b.lc}

# efficient string case-insensitive sort
var new_arr arr.sort_by { .lc }

Unroll operator

It's a nice metaoperator borrowed from Perl 6, which unrolls two arrays and applies the operator on each two element-wise objects, creating a new array with the results. The operator can be a method or any other valid operator and must be enclosed between » « or >> <<.

[1,2,3] »+« [4,5,6]           # [1+4, 2+5, 3+6]
%w(a b c) >>cmp<< %w(c b a)   # [-1, 0, 1]

Internally, the unroll_operator method is called, which can, also, be implemented in user-defined classes.

Map operator

The array map operator works exactly like the Array.map{} method, but it's slightly more efficient and easier to write. The map operator must be enclosed between » » or >> >>.

[1,2,3] »*» 4      # [1*4, 2*4, 3*4]

Internally, the map_operator method is called.

Pam operator

The pam operator is kind of a reversed mapping of the array ("pam" is "map" spelled backwards), where the provided argument is used as the first operand to the operator provided. The operator must be enclosed between « « or << <<.

[1,2,3] «/« 10     # [10/1, 10/2, 10/3]

Internally, the pam_operator method is called.

Reduce operator

This metaoperator reduces an array to a single element. The operator needs to be enclosed inside « » or << >>.

[1,2,3+»      # 1 + 2 + 3
[1,2,3/»      # 1 / 2 / 3

Internally, the reduce_operator method is called.

Cross operator

The metaoperator ~X or ~Xop crosses two arrays and returns a new one.

[1,2] ~X+ [3,4]    # [1+3, 1+4, 2+3, 2+4]
[1,2] ~X  [3,4]    # [[1,3], [1,4], [2,3], [2,4]]

Internally, the cross_operator method is called.

Zip operator

The metaoperator ~Z or ~Zop zips two arrays and returns a new one.

[1,2] ~Z+ [3,4]    # [1+3, 2+4]
[1,2] ~Z  [3,4]    # [[1,3], [2,4]]

Internally, the zip_operator method is called.

Wise operator

Almost equivalent with the zip metaoperator, it does element-wise folding on two arbitrary nested arrays, where both arrays must have the same structure.

[1,2]       ~W  [3,4]         # [[1,3], [2,4]]
[1,2]       ~W+ [3,4]         # [1+3, 2+4]
[[[1]],[2]] ~W+ [[[3]],[4]]   # [[[1+3]], [2+4]]

Internally, the wise_operator method is called.

Scalar operator

The scalar operator applies a given operator to the elements of an arbitrary nested array, where the provided scalar is used as the second operand to the given operator.

[1,2,3]       ~S  5  # [[1,5], [2,5], [3,5]]
[1,2,3]       ~S* 5  # [1*5, 2*5, 3*5]
[1,[[2,[3]]]] ~S+ 5  # [1+5, [[2+5, [3+5]]]]

Internally, the scalar_operator method is called.

Reverse scalar operator

The reverse scalar operator uses the given scalar as a first operand to the given operator and is also defined for arbitrary nested arrays.

[3,4,5]       ~RS  1  # [[1,3], [1,4], [1,5]]
[3,4,5]       ~RS/ 1  # [1/3, 1/4, 1/5]
[3,[[4,[5]]]] ~RS/ 1  # [1/3, [[1/4, [1/5]]]]

Internally, the rscalar_operator method is called.

Matrices

In Sidef, matrices can be represented with 2-dimensional arrays:

var A = [
          [1, 2],
          [3, 4],
          [5, 6],
          [7, 8],
        ]

var B = [
          [1, 2, 3],
          [4, 5, 6],
        ]

Starting with version 3.10, Sidef provides a set of basic matrix operations, such as addition, subtraction, multiplication, transposition, inverse, determinant and support for solving systems of linear equations. This operations are described bellow:

A `mmul` B      # matrix multiplication
A `madd` A      # matrix addition (entrywise)
A `msub` A      # matrix subtraction (entrywise)

The last two methods are provided by Array.wise_op(), which takes two arbitrary nested arrays and an operator, folding each element (entrywise) with the provided operator.

Example:

say ([1,2,[3,[4]]] `madd` [42,43,[44,[45]]])    #=> [43, 45, [47, [49]]]
say ([1,2,[3,[4]]] ~W+ [42,43,[44,[45]]])       #=> [43, 45, [47, [49]]]

Alternatively:

say wise_op([1,2,[3,[4]]], '+', [42,43,[44,[45]]])   #=> [43, 45, [47, [49]]]

When the provided operator is an empty string (''), the pairwise elements are combined together in a new array:

say wise_op([1,2,3], '', [4,5,6])                  #=> [[1, 4], [2, 5], [3, 6]]
say wise_op([1,2,[3,[4]]], '', [42,43,[44,[45]]])  #=> [[1, 42], [2, 43], [[3, 44], [[4, 45]]]]

Scalar operations:

A `scalar_add` 42   # scalar addition       (aliased as `sadd`)
A `scalar_sub` 42   # scalar subtraction    (aliased as `ssub`)
A `scalar_mul` 42   # scalar multiplication (aliased as `smul`)
A `scalar_div` 42   # scalar division       (aliased as `sdiv`)

This methods are provided by Array.scalar_op(), which, just like Array.wise_op(), also supports arbitrary nested arrays.

Examples:

say ([1,2,[3,[4]]] `sadd` 42)   #=> [43, 44, [45,  [46]]]
say ([1,2,[3,[4]]] `smul` 42)   #=> [42, 84, [126, [168]]]

...which is equivalent with:

say scalar_op([1,2,[3,[4]]], '+', 42)  #=> [43, 44, [45,  [46]]]
say scalar_op([1,2,[3,[4]]], '*', 42)  #=> [42, 84, [126, [168]]]

...and also equivalent with:

say ([1,2,[3,[4]]] ~S+ 42)   #=> [43, 44, [45,  [46]]]
say ([1,2,[3,[4]]] ~S* 42)   #=> [42, 84, [126, [168]]]

2-dimensional array exclusive operations:

A.transpose         # matrix transposition
A.inv               # matrix inverse
A.det               # matrix determinant
A.msolve(vector)    # solves a system of linear equations

Example:

var A = [
    [2, -1,  5,  1],
    [3,  2,  2, -6],
    [1,  3,  3, -1],
    [5, -2, -3,  3],
]

say A.det                            #=> 684
say (A `msolve` [-3, -32, -47, 49])  #=> [2, -12, -4, 1]

Matrix iteration

The extended for-in loop, introduced in Sidef 2.23, provides built-in support for matrix iteration, which is also useful in combination with the cross or zip metaoperators.

for a,b in ([1,2] ~X [3,4]) {
    say "#{a} #{b}"
}

Output:

1 3
1 4
2 3
2 4

Lists

An array can be converted into a list using the following notations:

var arr = ["a", 1, "b", 2]
say Hash(arr...)             # creates an Hash by passing the array as a list of values
say Hash(@|arr)              # ==//==

The difference between postfix ... and prefix @| consists in the fact that @| invokes the ... method only when its argument can respond to this method, while in the first case, the ... method is invoked unconditionally.

Slices

A slice is a sub-array, just like a sub-string is for a string.

var arr = ["foo", "bar", "baz"]

var *slice1 = arr[0, 1]         # fetches the first two values

var indices = [-2, -1]
var *slice2 = arr[indices]      # automatically unpacks the `indices` and fetches the last two values

say slice1                      # prints: ["foo", "bar"]
say slice2                      # prints: ["bar", "baz"]

Using slices, it's also possible to change multiple values inside an array:

var arr = ["foo", "bar", "baz"]
arr[0, 1] = ("a", "b")          # changes the first two values
say arr                         # prints: ["a", "b", "baz"]

Alternatively, using indices stored inside an array:

var arr = ["foo", "bar", "baz"]
var indices = [0, 1]
arr[indices] = ("a", "b")       # changes the first two values
say arr                         # prints: ["a", "b", "baz"]

An empty [], will return the entire array as a list of lvalues:

var arr = ['a', 'b', 'c']
arr[] = ('foo', 'bar')         # replaces the entire array
say arr                        # prints: ['foo', 'bar']

Ranges

A range is a definition of some consecutive values, either in increasing or decreasing order. The main advantage of a range over an array is that a range can be infinite, while an array can't, and this is because ranges are lazy.

A range can be created with one of the following operators: .., ^.. or ..^;

Mnemonics:

  • .. go from left to right (inclusive)
  • ..^ begin from down and go up (exclusive)
  • ^.. begin from up (exclusive) and go down

Count from 1 to 10:

for i in (1 .. 10) {
   say i
}

Count from 1 to 9:

for i in (1 ..^ 10) {
   say i
}

Count from 9 to 1:

for i in (10 ^.. 1) {
   say i
}

A range can be shifted (+, -), stretched (*) and shrank (/):

(1..10) + 2       #=>   3 .. 12
(1..10) - 2       #=>  -1 ..  8
(1..10) * 2       #=>   2 .. 20
(1..10) / 2       #=> 0.5 ..  5

Also, ranges can be reversed (.reverse) and granularized (.by):

(1..10).reverse        # a new reversed range from 10 down to 1
(1..10).by(0.5)        # a range from 1 up to 10, counting by 0.5

Alternatively, ranges can be created with the RangeNum data-type:

var evens = RangeNum(0, Inf, 2)      # range of all even numbers
say evens.lazy.first(10)             # the first 10 even numbers

A negative third argument will create a descending range:

for i in RangeNum(10, 5, -1) {    # count from 10 down to 5
    say i
}

The RangeNumber class inherits methods from the Range class, but it also implements some useful methods for working with numerical ranges, such as:

say  sum(1..10, {|n| n**3 })     # sum of the first 10 cubes
say prod(1..10, {|n| n**2 })     # product of the first 10 squares

Hashes

Hashes are used to quickly locate a data record (e.g., a dictionary definition) given its search key.

var hash = Hash(
    name => 'foo',
    age  => 42,
)

Working with hashes is almost the same as working with arrays, but instead of specifying a position index in square brackets, we now lookup with a string specified in curly brackets.

say hash{:name}    # prints the value associated with the "name" key
say hash.{"name"}  # ==//==

Changing a hash value:

hash{:age} = 99    # sets the key "age" to value 99
hash.{"age"} = 99  # ==//==

Multiple values

Just like arrays, hashes also support the retrieving of multiple values at once.

var hash = Hash(
    a => 1,
    b => 2,
    c => 3,
)

# Returns a list of values
var *vals = hash{:a, :b, :c}

# Print the values
say vals                        #=> [1,2,3]

# Using the keys defined inside an array
var keys = %w(a b c)
var *vals = hash{keys...}

# Print the values
say vals                        #=> [1,2,3]

An empty {} will return the entire hash as a list of lvalue-pairs:

var hash = Hash(a => 1, b => 2)
hash{} = (c => 3, d => 4)         # replaces the entire hash
say hash                          # prints: Hash(c => 3, d => 4)

Working with hashes

Hashes, like everything else, are objects which have many methods built-in, helping in dealing with hash tables.

Sorting by value:

# Sorting in ascending order
var asc_array = hash.sort_by{|_,v| v }

# Sorting in descending order
var des_array = hash.sort_by{|_,v| v }.reverse

For sorting the keys based on values, you can use traditional key-sorting:

# Sorting in ascending order
var keys_array = hash.keys.sort{|a,b| hash{a} <=> hash{b}}

# Sorting in descending order
var keys_array = hash.keys.sort{|a,b| hash{b} <=> hash{a}}

For more methods, see: Hash.pod

Pairs

Creating a long chained list in Sidef is as simple as it can get:

var tree = 'root':'child':'grandchild':'end'

The above code creates a pair of pairs, which looks like this:

Pair('root',
    Pair('child',
        Pair('grandchild', 'end')
    )
)

For traversing the list, we can use:

loop {
    say tree.first
    tree.second.is_a(Pair) || break
    tree = tree.second
}

This is also useful in creating an array of pairs:

var array_of_pairs = [
    "red":   9,
    "blue":  4,
    "green": 0,
]

Now, each element of the array is a Pair and can be accessed by using the first and second methods:

array_of_pairs.each { |pair|
    say "#{pair.first} == #{pair.second}"
}

Structs

A structure is very similar with a class without methods and can be used as a stricter alternative to hashes.

struct Person {
    String name,
    Number age,
}

# Create a new person
var john = Person(name: "John Smith", age: 42);

# Change a value
john.name = "Dr. #{john.name}";

# Increment a value
john.age++;

say john.name;       #=> Dr. John Smith
say john.age;        #=> 43

Files

A File is a built-in type in Sidef, in the same way as a String or an Array is.

Declaring a File object:

var file1 = File('/tmp/abc.txt')
var file2 = %f(/tmp/abc.txt)            # same thing

Being an object, it can have some interesting methods, and it does. The following code will simply edit a file in place:

File('/tmp/abc.txt').edit { |line|
    line.gsub(/this/, 'that')               # replaces 'this' with 'that' anywhere inside the file
}

File info

Here is a list with the most important methods which verifies some attributes of the file.

file.exists;       # true if file exists
file.size;         # size of the file
file.is_emtpy;     # true if size is zero
file.is_dir;       # true if file is a directory
file.is_readable;  # true if file is readable
file.is_link;      # true if file is a link
file.is_text;      # true if file is a text-file

Some more information can be achieved by using the stat or lstat method:

var info = file.stat;    # or 'lstat'
say info.atime;
say info.mtime;
say info.ctime;
say info.size;

Information related to the self object file:

file.name;        # the original name of the file
file.base;        # the base name of the file
file.abs;         # the absolute path the file
file.dir;         # the parent directory of the file

Manipulating files

We can also delete and rename files and do other things to files.

file.rename("new-name.ext");       # rename file
file.move("new-name.ext");         # rename file safer
file.copy("new-name.ext");         # copy file
file.unlink;                       # delete file
file.touch;                        # create file if it doesn't exists
file.chmod(0666);                  # change the permissions
file.utime(atime, mtime);          # change the access and modification times

Open files

A File object has a main open method which is directly bound to Perl's open function.

file.open('<:utf8', \var fh, \var err) ->
     || die "Can't open file #{file}: #{err}\n"

A simpler interface is provided by the open_* methods:

var fh   = file.open_r              # open the file for reading
var bool = file.open_r(\var fh)     # same thing, but returns a Boolean value

File handles

For reading the content of a file into a string, we can use the FileHandle.slurp() method:

var str = fh.slurp         # reads the content of the file into a string

Alternatively, the FileHandle.lines() method will return an array will each line from the file:

var arr = fh.lines         # reads the content of the file into an array

For reading one line at a time, we can use the FileHandle.each{} method:

fh.each { |line|
     say line
}

Conditional expressions

Conditional expression are almost the same in most programming languages and Sidef will not make an exception, so we'll have the classic if, while and for conditional expressions.

The if statement is one of the most basic conditional constructs.

if (bool) {

}
elsif (bool) {

}
else {

}

The with statement behaves almost like the if statement, but instead of testing for trueness, it checks to see if the given argument is not a nil value.

with (obj) {

}
orwith (obj) {

}
else {

}

In addition to the if statement, it also supports capturing of a defined value in a block variable:

with (some_function()) { |value|
    say value
}

The while statement is almost like the if construct, except that it will keep executing its block as long the given expression evaluates to a true value.

while (bool) {

}

The for statement it's usually used for iteration over collections and for counting.

for (var i = 0; i <= 10; i++) {

}

Also, we have the ternary operator and the case and switch statements:

bool ? (true) : (false)

Given/when is used to compare two values using the rules of the smartmatch operator (~~):

given ('b') {
  when ('a') { say "a" }
  when ('b') { say "b" }
  default    { say "Unknown!" }
}

Additionally, to test expressions for trueness, Sidef has the case statement:

given (-1) {
  case (.is_zero) {
       say "Null value!"
  }
  case (.is_neg) {
       say "Negative value!"
  }
  default {
       say "Positive value!"
  }
}

case and when statements can be mixed together:

given (42) { |value|
  case (value < 0) {
       say "Negative value!"
  }
  when (0) {
       say "Null value!"
  }
  case (value > 1) {
       say "Positive value!"
  }
}

When a value is found, the construct breaks automatically, but we have the continue keyword which will prevent this.

given (1) {
   when (1) {
      say "true once"
      continue        # will fall through
   }
   when (1) {
      say "true twice"
   }
}

Regular expressions

Sidef borrows the regular expressions from Perl. Any regular expression is analyzed and compiled by Perl's regexp engine, so we have all the good stuff we are already familiar with.

var regex = /^my+[regex]?\z/ixmsu

Matching against regular expressions:

var string = 'something'

if (string =~ regex) {
   say "Matches!"
}

Storing and using the captured matches:

var string = "Sidef <3 Perl"
var match = string.match(/(\w+)\h+<3\h+(\w+)/)

if (match) {
   var captures = match.captures
   say captures[0]                 # prints: Sidef
   say captures[1]                 # prints: Perl
}

The returned match object, it's a special object which accepts array indexing of values.

var m = "hello world".match(/^(\w+) (\w+)/)
say m[0]                                   # prints: hello
say m[1]                                   # prints: world

Global matching

A regex can match multiple times inside a given string, therefore Sidef provides support for global matching.

var str = "a cat, a dog and a fox"
while (var m = str.match(/\ba\h+(\w+)/g)) {
    say m[0]
}

Alternatively, there is the String.gmatch() method:

var string = 'Sidef <3 regular expressions'
while (var m = string.gmatch(/(\S+)/)) {
    say m[0]
}

Smart-matching

The smart-match operator (~~) take two operands and compare them based on their type and their order.

"hello" ~~ /^h/;        # true: string matches regex
"oo" ~~ "foobar";       # false: "oo" doesn't equal "foobar"
"a" ~~ %w(a b c);       # true: item exists in array
/^b/ ~~ %w(foo bar);    # true: regex matches an element from array
/^f/ ~~ Hash(foo => 1); # true: regex matches a key from hash

There is also !~ which simply flips the Boolean value returned by ~~.

/abc/ !~ "abcdef";     # false

Modules

A module in Sidef is an independent piece of code with its own namespace and variables.

module Fibonacci {
    func nth(n) {
        n > 1 ? nth(n-2)+nth(n-1) : n
    }
}

say Fibonacci::nth(12)    # prints: 144

A module is independent in the way that you can't access variables from other namespaces:

var x = 42
module Test {
   say x        # parse-time error: can't find variable 'x'
}

Classes

A class is just a distinct group of functions, which we now call them methods, and a bunch of instance-variables. When a class is called, an instance-object of that class is returned, which encapsulates the given data provided at class initialization. Each call to a class, returns a different instance-object.

class Person (name, age, address) {
    method position {
        # GPS.locate(self.address)
    }

    method increment_age(amount=1) {
         self.age += amount
    }
}

var obj = Person(name: "Foo", age: 50, address: "St. Bar")
say obj.age                # prints: 50
say obj.name               # prints: "Foo"
say obj.address            # prints: "St. Bar"

obj.name = "Baz"           # changes name to "Baz"
say obj.name               # prints: "Baz"

obj.increment_age          # increments age by 1
say obj.age                # prints: 51

Classes in Sidef are a little bit different than traditional classes. Values of a class are accessible via a method call which can either get or set a value, so the programmer don't have to worry about creating separated get_* and set_* methods.

Class inheritance:

A class can inherit methods from other classes by using the special operator <, followed by the name of the inherited class:

class Animal(String name, Number age)  {
    method speak { "..." }
}

class Dog(String color) < Animal {
    method speak { "woof" }
    method ageHumanYears { self.age * 7 }
}

class Cat < Animal {
    method speak { "meow" }
}

var dog = Dog(name: "Sparky", age: 6, color: "white")
var cat = Cat(name: "Mitten", age: 3)

say dog.speak          #=> woof
say cat.speak          #=> meow
say cat.age            #=> 3
say dog.ageHumanYears  #=> 42
say dog.color          #=> white

Multiple inheritance is declared with the << operator, followed by two or more class names, separated by commas:

class Camera { }
class MobilePhone { }
class CameraPhone << Camera, MobilePhone { }

Metaprogramming

An interesting feature is the definition of methods at run-time:

var colors = Hash(
               'black'   => "000",
               'red'     => "f00",
               'green'   => "0f0",
               'yellow'  => "ff0",
               'blue'    => "00f",
               'magenta' => "f0f",
               'cyan'    => "0ff",
               'white'   => "fff",
             )

for color,code in colors {
    String.def_method("in_#{color}", func (self) {
        '<span style="color: #' + code + '">' + self + '</span>'
    })
}

say "blue".in_blue
say "red".in_red
say "white".in_white

Output:

<span style="color: #00f">blue</span>
<span style="color: #f00">red</span>
<span style="color: #fff">white</span>

Methods can have variable-like names (a, hello, etc...), or operator-like names (+, **, etc...).

class Number {
    method ⊕(arg) {
        self + arg
    }
}

say (2142)

The special method AUTOLOAD is called when a given method is missing.

class Example {
    method foo {
        say "this is foo"
    }
    method bar {
        say "this is bar"
    }
    method AUTOLOAD(_, name, *args) {
        say ("tried to handle unknown method %s" % name);
        if (args.len > 0) {
            say ("it had arguments: %s" % args.join(', '));
        }
    }
}

var example = Example()

example.foo           # prints “this is foo”
example.bar           # prints “this is bar”
example.grill         # prints “tried to handle unknown method grill”
example.ding("dong")  # prints “tried to handle unknown method ding”
                      # prints “it had arguments: dong”

Parallel computation

Sidef has a very basic support for parallel computation, but pretty powerful. There is the Block.ffork() method which creates a new system process that executes (in parallel) the content of a given block and returns a new Fork object which accepts the wait (or get) method that waits for the process to finish and returns the computed value.

To take advantage of this mechanism, fork two or more processes and store the returned objects in variables or inside an array and get their values at a later time. The process is executed soon after it has been forked.

Example for the quicksort algorithm in parallel:

func quicksort(arr {.len <= 1}) { arr }

func quicksort(arr) {

    say arr;
    var p = arr.pop_rand;

    var forks = [
        quicksort.ffork(arr.grep { _ <= p }),
        quicksort.ffork(arr.grep { _ >  p }),
    ];

    forks[0].wait + [p] + forks[1].wait
}

say quicksort(@("a".."z") -> shuffle)

Alternatively, Sidef provides the Block.thr method, which creates a deprecated Perl thread, or a system fork if forks is installed.

Interacting with Perl modules

Sidef can interact with Perl modules in a very easy way. There is the require keyword which will try to load an object-oriented Perl module.

var lwp  = require('LWP::UserAgent')
var ua   = lwp.new(show_progress => 1)
var resp = ua.get('http://example.net')

if (resp.is_success) {
    say resp.decoded_content.length
}

For functional Perl modules, the frequire keyword should be used instead to denote that the module is function-oriented.

var spec = frequire('File::Spec::Functions')
say spec.rel2abs(spec.curdir)

Literal Perl modules

There is a special syntax for literal Perl modules, which creates a special module-caller object, without require-ing the module in the first place.

%O<LWP::UserAgent>;         # object-oriented module
%S<File::Spec::Functions>;  # subroutine/function oriented module

This allows us to ignore the return value of require and declare the module-caller afterwards.

require('ntheory')

var nt = %S<ntheory>

if (nt.is_prime(43)) {
    say "43 is prime!";
}

nt.forprimes({|p| say p }, 0, 100)

Graphical interface

Due to the fact that we can interact with Perl modules, we can also create graphical interfaces from Sidef, by using the Gtk2 library.

require('Gtk2') -> init

var gtk2   = %O<Gtk2>
var window = %O<Gtk2::Window>.new
var label  = %O<Gtk2::Label>.new('Goodbye, World!')

window.set_title('Goodbye, World!')
window.signal_connect(destroy => { gtk2.main_quit })

window.add(label)
window.show_all

gtk2.main

Inlining Perl code in Sidef

Perl.eval() can evaluate arbitrary Perl code and convert the result into a Sidef data structure which can be used later in your program.

var perl_code = <<'CODE';

sub fact {
    my $p = 1;
    $p *= $_ for 2..$_[0];
    $p;
}

my %data = (
    result => fact(10)
);

\%data;
CODE

var data = Perl.eval(perl_code);
say data{:result};                 #=> 3628800
say Sys.ref(data{:result});        #=> Sidef::Types::Number::Number

A practical example would be the creation of Sidef blocks which incorporate arbitrary Perl code. Doing so, the returned object will behave exactly like a native Sidef block:

var perl_code = <<'CODE';

Sidef::Types::Block::Block->new(
    code => sub {
        my ($n) = @_;
        ($n <= 1) ? $n
                  : (CORE::__SUB__->($n-1) + CORE::__SUB__->($n-2));
    },
    type => 'func',
    name => 'fib',
    table => {'n' => 0},
    vars => [{name => 'n'}],
);

CODE

var block = Perl.eval(perl_code)
say block(28)