Sidef is a modern, high-level programming language designed for versatile general-purpose applications, drawing inspiration from the principles of Ruby, Raku, and Julia.
** ** **** * ********* *********
* * ** * * **** ** ** ** ** ** ** **
** ** **** *** ********* * * *
** ** ** **** * * ****** ******
* * * * * * * * * **** ** ** ** ** ** **
** ** ** **** ****** ****** * *
** ** **** * * * ********* ***
* * ** * * **** ** ** ** ** ** ** **
** ** **** ********* ********* *
The Sidef Programming Language: https://trizen.gitbook.io/sidef-lang/ (legacy) (PDF).
This section details the installation process for Sidef across different operating systems.
Access the latest release at:
IMPORTANT: Sidef needs the GMP, MPFR and MPC C libraries.
For Windows, Sidef is available as a portable 32-bit executable:
Sidef is available in the AUR and can be installed using an AUR helper, like trizen:
trizen -S sidef
On Debian-based distributions, Sidef can be installed from the MetaCPAN, by executing the following commands:
sudo apt install libgmp-dev libmpfr-dev libmpc-dev libc-dev cpanminus
cpanm --sudo -n Sidef
It's also possible to install Sidef on Android, by installing Termux and executing the following commands:
pkg install perl make clang libgmp libmpfr libmpc
cpan -T Sidef
If the installation succeeded, the sidef
command should be available:
sidef -h
Sidef can be installed from the MetaCPAN, by invoking the following command:
cpan Sidef
If the testing takes a long time, add the -T
flag to build and install Sidef without testing:
cpan -T Sidef
When the cpan
command is not available, try:
perl -MCPAN -e "CPAN::Shell->install(q{Sidef})"
To install Sidef manually, download the latest version, unzip it and follow the installation steps:
perl Build.PL
sudo ./Build installdeps
sudo ./Build install
When Module::Build is not installed, try:
perl Makefile.PL
make test
sudo make install
It is also possible to run Sidef without having to install it. In a Unix-like environment, the following commands can be executed:
wget 'https://github.com/trizen/sidef/archive/master.zip' -O 'master.zip'
unzip 'master.zip'
cd 'sidef-master/bin/'
./sidef -v
Those commands help obtain and unpack the latest GitHub version of Sidef, subsequently running the bin/sidef
script to display the language's current version.
To execute a Sidef script, run:
./sidef ../scripts/sierpinski_carpet.sf
It's also possible to add the following alias to ~/.bashrc
or ~/.zshrc
:
alias sidef="/path/to/bin/sidef"
For packaging Sidef, run:
perl Build.PL --destdir "/my/package/path" --installdirs vendor
./Build test
./Build install --install_path script=/usr/bin
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
Before delving into the syntax of the language, let's briefly examine the structure of a typical program.
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 {
UInt8 R,
UInt8 G,
UInt8 B,
}
class Bitmap(UInt width, UInt height) {
has data = []
method setpixel(UInt i, UInt j, Pixel p) {
subset WidthLimit < UInt { |n| n ~~ ^width }
subset HeightLimit < UInt { |n| n ~~ ^height }
func (WidthLimit w, HeightLimit h) {
data[w*height + h] = p
}(i, j)
}
method p6 {
<<-EOT + data.map { [.R, .G, .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:
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]
The Sidef syntax closely resembles that of Ruby or JavaScript, yet significant differences in semantics become apparent upon examination. Notably, all operators share an identical precedence level, which is controlled by the absence of whitespace between operands:
1+2 * 3+4 # means: (1+2) * (3+4)
In the above example, the absence of whitespace between 1
, +
, and 2
, classifies the operation as a discrete 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) # better
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))
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
Originally, Sidef was designed without incorporating 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 runtime dynamic constant
static # declare a runtime static variable
define # declare and assign a compile-time static constant
enum # declare and assign a list of constants with distinct numbers
del # delete an identifier from the lexical scope
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
try # `try/catch` statement
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
Sidef defines the following list of built-in prefix operators:
> # alias for `say`
>> # alias for `print`
+ # scalar context
- # 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
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
These classes deal with the standard data types in Sidef and implement the useful methods visible to the user.
File
FileHandle
Dir
DirHandle
Arr Array
Vector
Matrix
Pair
Hash
Set
Bag
Str String
Num Number
RangeStr RangeString
RangeNum RangeNumber
Mod
Complex
Fraction
Gauss
Quadratic
Quaternion
Polynomial
PolynomialMod
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")
A method is a function defined for a specific type of object. For strings, we have a method named 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 arguments.
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")) # =//=
This provides very good expressivity, as illustrated with the following 4 statements, which are all equivalent:
grep(1..100, {.is_prime}) # calls Range.grep()
1..100 -> grep {.is_prime} # =//=
grep({.is_prime}, 1..100) # calls Block.grep()
{.is_prime} -> grep(1..100) # =//=
Using the prefix notation, a method can be invoked even when a function with the same name is declared in the same scope, by preceding the method-name with ::
:
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
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)
The ->
takes everything on its left as one expression and applies the method to the result of that expression. Meanwhile, .
simply applies the method to the object that comes before 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 the pipeline operator |>
, which is defined for any object, except nil
:
25 |> :sqrt |> :say # means: 25.sqrt.say
25 |> (:pow, 2) |> :say # means: 25.pow(2).say
It can also be used for making function-calls in postfix notation:
func increment(n) { n+1 }
func double(n) { 2*n }
func triple(n) { 3*n }
say increment(double(triple(42))) #=> 253
42 |> triple |> double |> increment |> :say #=> 253
42 |> {_*3} |> {_*2} |> {_+1} |> :say #=> 253
42 |> (->(a,b){a*b}, 3) |> {_+_} |> :inc |> :say #=> 253
There is also support for calling a method of which name is not known until at runtime, 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 runtime error.
In the current implementation of the language, we have the following built-in classes:
Variables are commonly declared using the var
keyword:
var num = 42
var str = "42"
var bool = true
In Sidef exists four types of variables: lexical variables, static variables, global variables and local 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
This type of variables are static, block-scoped and initialized only once.
for k in (1..10) {
static x = 41+k # will assign to x only once, setting it to 41+1
say x # prints 42
}
Multiple static variables can be declared and initialized, using the syntax:
static (
a = 42,
*b = (1,2,3,4), # slurpy array
:c = (x => 1, y => 2), # slurpy hash
)
say a #=> 42
say b #=> [1,2,3,4]
say c #=> Hash(x => 1, y => 2)
Global variables are declared at the highest level of the current namespace and are accessible from any part of the code at any time. However, it's advisable to minimize their usage, opting for better alternatives whenever possible.
global x = 42 # sets global x to 42
say x # prints the global value of x
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)
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 default values:
var (x, y=755, z=777) = (666, 655)
say x # prints: 666
say y # prints: 655
say z # prints: 777
It's also possible to omit the second assignment:
var (
a = 42,
*b = (1,2,3,4), # slurpy array
:c = (x => 1, y => 2), # slurpy hash
)
say a #=> 42
say b #=> [1,2,3,4]
say c #=> Hash(x => 1, y => 2)
Additionally, referring to a previously defined variable as a default value is also supported:
var (
a = 42,
b = 10+a
)
say a #=> 42
say b #=> 52
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)
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
The special assignment defined-or operator :=
can be used for changing a variable only when its value is nil
:
var x = nil
x := 42 # sets x to 42
x := 99 # x is already defined
say x # prints 42
This operator is commonly used in creating an hash of arrays:
var hash = Hash()
hash{:key} := [] << (1,2)
hash{:key} := [] << 3
say hash{:key} #=> [1,2,3]
Additionally, the :=
operator returns an lvalue:
var hash = Hash()
hash{:key} := 0 -> max!(10)
hash{:key} := 0 -> max!(42)
say hash{:key} #=> 42
In addition to :=
, there is also the defined-or operator \\
, which can be used for checking if a value is defined:
var x = nil
x \\ say "x is not defined" # prints "x is not defined"
Currently, there are only two predefined variables: ARGV
and ENV
.
ARGV.each { |arg|
say arg
}
say ENV{:HOME}
Any identifier can be deleted using the del
keyword followed by the name of the identifier.
var foo = 42
del foo
say foo # parse-time error: attempt to use deleted identifier
The special _
variable is pre-declared within all block objects during program compilation. Its usage might not be prevalent due to the prevalent use of the sleek unary dot (.
) operator:
[25,36,49].map {.sqrt} \
.each{.log.say}
.sqrt
really means _.sqrt
, and .log.say
means _.log.say
.
The map
method cycles through the anonymous array, executing the block for each array element, designated to the variable _
. Upon completion, map
yields the new array, initiating a subsequent each
method call with another block as its argument.
Similar to map
, each
iterates over the array, executing the block for each element designated to the variable _
, and subsequently applies the log
method, followed by say
.
Additionally, same as in Raku, 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]
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
These constants look like variables, but are actually file-handles.
STDERR.say("Some error!")
STDOUT.say("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|
say 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}"
}
__DATA__
here are
some data
lines
Sidef implements three kinds of constants:
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 # runtime error: can't modify non-lvalue constant
These constants are generated dynamically during runtime and remain immutable once initialized.
Multiple constant values can be declared and initialized, using the syntax:
const (
a = 42,
*b = (1,2,3,4), # slurpy array
:c = (x => 1, y => 2), # slurpy hash
)
say a #=> 42
say b #=> [1,2,3,4]
say c #=> Hash(x => 1, y => 2)
When declared inside a 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
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**12 - IHP**12 / PHI-IHP) #=> 144
The value of a define
constant must be a stand-alone constant expression that can be computed at compile-time.
Multiple constant values can be declared and initialized, using the syntax:
define (
a = 42,
*b = (1,2,3,4), # slurpy array
:c = (x => 1, y => 2), # slurpy hash
)
say a #=> 42
say b #=> [1,2,3,4]
say c #=> Hash(x => 1, y => 2)
Attempting to change a define
constant, will result in a compile-time error:
define pi = 3.14
say pi # prints: 3.14
#pi = 3 # compile-time error: Can't modify constant item
This type of constants are the most efficient ones.
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'
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
The Ref
special type can be used for representing a variable reference.
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!"
}
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 "01234"
[1,2,3].sort {|a,b| b <=> a } # returns a new array: [3,2,1]
Additionally, the following Block methods are also available:
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, 101..103, 1001..1003)
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)
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 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' && print ">" }.map{ print "> "; .uc }) {
say line
}
__DATA__
a
b
c
d
Output (which shows that .grep{}
and .map{}
are 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.
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 add10 = 10.method('+')
var isprime = Num.method(:is_prime)
say (1..10 -> map(add10).grep(isprime)) #=> [11, 13, 17, 19]
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
Like mathematical functions, Sidef's functions can be recursive, take arguments and return values.
func hello (name) {
say "Hello, #{name}!"
}
hello("Sidef")
Unlike other programming languages, Sidef requires the arguments to be enclosed in ()
when calling a function:
func f(g) {
g()
}
f({ say "foo" }) # prints "foo"
f { say "bar" } # this does nothing
By executing the above script with the -r
argument, we will see how the code is parsed:
$ sidef -r script.sf
which outputs:
func f(g) { (g->call()) };
f({|_| (say("foo")) });
f;
{|_| (say("bar")) };
Recursive functions:
func factorial (n) {
if (n > 1) {
return (n * factorial(n - 1))
}
return 1
}
say factorial(5) # prints: 120
Anonymous recursion can be achieved by using the __FUNC__
keyword, which refers to the current function:
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 })
Additionally, the __BLOCK__
keyword can be used for referring to the current block:
func fib(n) {
return NaN if (n < 0)
{|n|
n < 2 ? n
: (__BLOCK__(n-1) + __BLOCK__(n-2))
}(n)
}
say fib(12) #=> 144
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)
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
By specifying the cached
trait to a function or a method, Sidef will automatically cache it.
func fib(n) is cached {
return n if (n <= 1)
fib(n-1) + fib(n-2)
}
say fib(100) # prints: 354224848179261915075
Additionally, the Block.cache()
method enables memoization on the self-block, whereas Block.uncache()
disables memoization and also clears the cache.
func fib(n) {
n <= 1 ? n : fib(n-1)+fib(n-2)
}
fib.cache # enables memoization
say fib(100) #=> 354224848179261915075
fib.uncache # disables memoization
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)
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
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)
A function can be declared with typed parameters, which are checked at runtime.
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) # runtime 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 runtime error
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(EvenNatural n) {
say n
}
foo(42) # ok
foo(43) # failed assertion at runtime
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}!" }
}
A class name can be used as a subset, by using the syntax variable < ClassName
. 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(StrNum a, StrNum b) {
a + b
}
say concat("o", "k") # ok
say concat(13, 29) # 42
say concat([41], [42]) # runtime error
Sidef also 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
This 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 parameter must come before the block, while an optional default value 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(Negative n) {
-arithmetic_derivative(-n)
}
func arithmetic_derivative(Positive n) is cached {
var a = n.lpf
var b = n/a
arithmetic_derivative(a)*b + a*arithmetic_derivative(b)
}
func arithmetic_derivative(Number n) {
var (a, b) = n.nude
(arithmetic_derivative(a)*b - arithmetic_derivative(b)*a) / b**2
}
printf("(42!)' = %s\n", arithmetic_derivative(42!))
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") # runtime error
Multiple return-type checks are supported as well:
func foo() -> (Number, String) {
(42, "foo")
}
var (a, b) = foo()
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 lexical 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]
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
}
for
each 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 { ... }
Collecting n
items can be done by using the n.of {...}
method, which calls the given block with the values 0
to n-1
and returns an array with the block-values from each call:
say 5.of {|n| 2**n } #=> [1, 2, 4, 8, 16]
Similarly, there is the n.by { ... }
method, which returns the first n
integers (>= 0
) for which the block evaluates to a true value:
say 5.by { .is_prime } #=> [2, 3, 5, 7, 11]
Recursive block:
{
say "Hello, World!"
__BLOCK__.run
}.run
Things to remember:
|x,y,z|
is used to capture the block arguments in variables.- The keyword
next
will skip one interation of the loop. - The keyword
break
will break a loop.
The gather/take
construct, borrowed from Raku:
var arr = gather {
take(1)
take(2)
take(3)
}
say arr #=> [1, 2, 3]
The try/catch
construct:
try {
# unsafe code here
}
catch { |msg|
say "try/catch failed with message: #{msg}"
}
The value of msg
is a String containing the error text produced in the try
branch.
Additionally, the catch
branch is optional and can be omitted. If omitted and the try
branch fails, the nil
value is returned.
"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
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:
- double-quoted strings can interpolate code.
- example:
"code: #{say 'inside string'}"
- example:
- double-quoted strings understand special escapes.
- example:
"\n, \t, \Q...\E"
- example:
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!'
By single-quoting the name of an HERE-document, interpolation will be disabled:
{
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
In Sidef, numbers play an important role and are treated correspondingly. 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
Additionally, the Number class can be used for constructing new Number objects:
Number("1234.52") # new decimal number
Number("10110111", 2) # new binary number
Number("deadbeef", 16) # new hexadecimal number
The documentation for each Number method can be read at: https://metacpan.org/pod/Sidef::Types::Number::Number
Here is 255
written as integer in different bases:
255 # decimal
0xff # hexadecimal
0377 # octal
0b1111_1111 # binary
In Sidef, decimal literals are always represented in rational form, using Math::GMPz or Math::GMPq.
1.234 # 1.234
.1234 # 0.1234
1234e-5 # 0.01234
12.34e5 # 1234000
Example:
say 1.234.dump #=> 617/500
say (0.1 + 0.2 == 0.3) #=> true
Floating-point values are represented by Math::MPFR with a default precision of 192 bits.
A floating-point value is usually returned by various mathematical functions (including sin(x)
, log(x)
, etc...), but it can also be created as a literal value, using the suffix f
:
12345f # floating-point value
12.34f # ==//==
1.5e9f # ==//==
At run-time, a number can be explicitly converted to a floating-point value by calling the .float
method on it:
var n = 12.345
var f = n.float # floating-point value
Complex numbers are represented by Math::MPC in floating-point form, with a default precision of 192 bits for each component.
A complex number can be created by using either one of the following ways:
3:4 # 3+4i
3+4i # 3+4i
3+4.i # 3+4i
Complex(3,4) # 3+4i
Complex numbers are deeply integrated into the language and can be used in combination with all the other Number types (with implicit propagation):
sqrt(-1) # 1i
log(-1) # 3.14159265358979323846264338327950288419716939938i
4 + sqrt(-1) # 4+i
(3+4i)**2 # -7+24i
The default floating-point precision can be changed with the -P int
command-line flag passed to sidef
, which specifies the number of decimals of precision.
Example:
$ sidef -P100 script.sf # executes "script.sf" with 100 decimals of precision
It's also possible to dynamically change the floating-point precision at runtime, by modifying the Num!PREC
class variable:
say sqrt(2) #=> 1.41421356237309504880168872420969807856967187538
local Num!PREC = 42.numify # sets the floating-point precision to 42 bits
say sqrt(2) #=> 1.414213562
The Mod class represents a modular operation, similar to PARI/GP's built-in Mod(a,m)
class.
Mod(3, 4) # represents 3 mod 4
Example:
var a = Mod(13, 19)
a += 15 # Mod(9, 19)
a *= 99 # Mod(17, 19)
a /= 17 # Mod(1, 19)
say a # Mod(1, 19)
say (a == 1) # true
say (a == 20) # true
a -= 43 # Mod(15, 19)
say a**42 # Mod(11, 19)
say a**(-1) # Mod(14, 19)
say sqrt(a+1) # Mod(4, 19)
say chinese(Mod(43, 19), Mod(13, 41)) # Mod(423, 779)
The Fraction class represents a generic fraction:
var a = Fraction(3, 4)
var b = Fraction(5, 7)
say a*b #=> Fraction(15, 28)
say a+b #=> Fraction(41, 28)
The Gauss class provides support for rational Gaussian numbers and various operations on these numbers.
Gauss(3, 4) # represents 3+4i
Example:
say Gauss(3,4)**100
say Mod(Gauss(3,4), 1000001)**100 #=> Mod(Gauss(826585, 77265), 1000001)
var a = Gauss(17,19)
var b = Gauss(43,97)
say a+b #=> Gauss(60, 116)
say a-b #=> Gauss(-26, -78)
say a*b #=> Gauss(-1112, 2466)
say a/b #=> Gauss(99/433, -32/433)
The Quadratic class represents a quadratic integer of the form: a + b*sqrt(w)
.
var x = Quadratic(3, 4, 5) # represents: 3 + 4*sqrt(5)
var y = Quadratic(6, 1, 2) # represents: 6 + sqrt(2)
say x**10 #=> Quadratic(29578174649, 13203129720, 5)
say y**10 #=> Quadratic(253025888, 176008128, 2)
say x.powmod(100, 97) #=> Quadratic(83, 42, 5)
say y.powmod(100, 97) #=> Quadratic(83, 39, 2)
The Quaternion class represents a quaternion number of the form a + b*i + c*j + d*k
, where a
, b
, c
, and d
are real numbers; and i
, j
, and k
are the basic quaternions.
var a = Quaternion(1,2,3,4)
var b = Quaternion(5,6,7,8)
say a+b #=> Quaternion(6, 8, 10, 12)
say a-b #=> Quaternion(-4, -4, -4, -4)
say a*b #=> Quaternion(-60, 12, 30, 24)
say b*a #=> Quaternion(-60, 20, 14, 32)
say a/b #=> Quaternion(35/87, 4/87, 0, 8/87)
say a**5 #=> Quaternion(3916, 1112, 1668, 2224)
say a.powmod(43, 97) #=> Quaternion(61, 38, 57, 76)
say a.powmod(-5, 43) #=> Quaternion(11, 22, 33, 1)
The Polynomial class implements support for polynomials.
say Polynomial(5) # x^5
say Polynomial([1,2,3,4]) # x^3 + 2*x^2 + 3*x + 4
say Polynomial(5 => 3, 2 => 10) # 3*x^5 + 10*x^2
Also aliased as Poly()
:
var a = Poly([1,2,3])
var b = Poly([4,5,-6,7])
say a+b #=> 4*x^3 + 6*x^2 - 4*x + 10
say a-b #=> -4*x^3 - 4*x^2 + 8*x - 4
say a*b #=> 4*x^5 + 13*x^4 + 16*x^3 + 10*x^2 - 4*x + 21
say 42-a #=> -x^2 - 2*x + 39
say 42+b #=> 4*x^3 + 5*x^2 - 6*x + 49
say 42*b #=> 168*x^3 + 210*x^2 - 252*x + 294
say a/42 #=> 1/42*x^2 + 1/21*x + 1/14
say b/42 #=> 2/21*x^3 + 5/42*x^2 - 1/7*x + 1/6
# Parsing a polynomial as a string
say Poly("2*x^2 + 3*x - 5") #=> 2*x^2 + 3*x - 5
The PolynomialMod class implements support for modular polynomials.
say PolynomialMod(5, 127) # x^5 (mod 127)
say PolynomialMod([1,2,3,4], 127) # x^3 + 2*x^2 + 3*x + 4 (mod 127)
say PolynomialMod(5 => 3, 2 => 10, 127) # 3*x^5 + 10*x^2 (mod 127)
Also aliased as PolyMod()
:
var a = PolyMod([13,4,51], 43)
var b = PolyMod([5,0,-11], 43)
say a-b #=> 8*x^2 + 4*x + 19 (mod 43)
say a+b #=> 18*x^2 + 4*x + 40 (mod 43)
say a*b #=> 22*x^4 + 20*x^3 + 26*x^2 + 42*x + 41 (mod 43)
# Division and remainder
say [a.divmod(b)].join(' ; ') #=> 37 (mod 43) ; 4*x + 28 (mod 43)
# Parsing a polynomial as a string
say PolyMod("2*x^2 + 3*x - 5", 101) #=> 2*x^2 + 3*x + 96 (mod 101)
# Chinese Remainder Theorem (CRT)
say chinese(PolyMod("x^2+x+2", 3), PolyMod("x-1", 5)) #=> 10*x^2 + x + 14 (mod 15)
A numerical string can be converted into a rational number by using the Number
class:
Number("0.75") # "0.75" is parsed and stored in rational form as 3/4
Number("fff/aaa", 36) # parse a base-36 fraction as 4095/2730
The Number method as_rat
can be used for getting the rational form of a number:
say 3/4 # 0.75
say as_rat(3/4) # 3/4
say as_rat(1.234, 36) # h5/dw (which is 617/500 in base-10)
The Number.base(b)
method provides conversion from numbers into strings in a given base:
1234.base(13) # to string in base 13
1234.base(36) # to string in base 36
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're likely familiar with autovivification, the functionality responsible for dynamically creating data structures.
The Array
object has many interesting methods for making it safer and easier to work with arrays in a pure OO style.
grep
ing some elements from an array:
var new_arr = arr.grep { _ > 10 } # returns a new array containing numbers greater than 10
map
ing an array:
var new_arr = arr.map {|n| 2*n + n**2} # returns a new array with the result returned from the "map" block
sort
ing 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 }
It's a nice metaoperator borrowed from Raku, 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.
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.
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.
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.
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.
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.
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.
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.
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.
The Array.wise_op()
method takes two arbitrary nested arrays and an operator, folding each element (entrywise) with the provided operator, which is also available as a ~Wop b
:
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]]]]
Multiple arbitrary nested arrays can be combined together using the Array.combine{...}
method, added in Sidef 3.50:
var a = [[6, 6], [4, 4]]
var b = [[1, 2], [3, 4]]
var c = [[9, 5], [7, 2]]
say [a,b,c].combine{|x,y,z|
x + y + z
}
Output:
[[16, 13], [14, 10]]
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:
say ([1,2,[3,[4]]] ~S+ 42) #=> [43, 44, [45, [46]]]
say ([1,2,[3,[4]]] ~S* 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]]]
The Array operators |>>
, |Z>
and |X>
can be used in a pipeline-fashion to map the elements of an array, given a list of method names or function objects.
The map pipeline operator |>>
maps the array to a given method or function, with optional arguments:
[1,2,3] |>> (:pow, 2) |>> (:mul, 5) |> :say #=> [5, 20, 45]
[1,2,3] |>> { _**2 } |>> { _*5 } |> :say #=> [5, 20, 45]
[1,2,3] |>> ({|a,b| a**b }, 2) |>> ({|a,b| a*b }, 5) |> :say #=> [5, 20, 45]
The zip pipeline operator, |Z>
, zips the array over the callback argument list, n
-elements at a type, where n
is the number of callbacks:
[1,2,3,4,5,6] |Z> (:exp2, :exp10) |> :say #=> [2, 100, 8, 10000, 32, 1000000]
Each callback that contains arguments, must be enclosed inside an array:
[42, 100, 99, 49] |Z> ([{|a,b| a + b }, 3], :sqrt) |> :say #=> [45, 10, 102, 7]
The cross-product pipeline operator, |X>
, maps each element of the array over each element of the argument list, which is a list of callbacks. Each callback that contains arguments, must be enclosed inside an array.
[25, 36, 49] |X> (:sqrt, [{|a,b| a + b }, 3]) |> :say #=> [5, 28, 6, 39, 7, 52]
The pipeline operators can be combined freely in any order:
[25, 36, 49] |X> (:sqrt, [{|a,b| a+b }, 3]) |Z> ({_*_}, [{|a,b| a-b }, 3]) |> :say
The built-in Matrix
class (child of the Array
class) provides support for defining and working with matrices:
Matrix(
[1, 2],
[3, 4],
)
A subset of Matrix
operations are included in the following example:
var A = Matrix(
[2, -3, 1],
[1, -2, -2],
[3, -4, 1],
)
var B = Matrix(
[9, -3, -2],
[3, -1, 7],
[2, -4, -8],
)
say (A + B) # matrix addition
say (A - B) # matrix subtraction
say (A * B) # matrix multiplication
say (A / B) # matrix division
say (A + 42) # matrix-scalar addition
say (A - 42) # matrix-scalar subtraction
say (A * 42) # matrix-scalar multiplication
say (A / 42) # matrix-scalar division
say A**20 # matrix exponentation
say A**-1 # matrix inverse: A^-1
say A**-2 # (A^2)^-1
say B.det # matrix determinant
say B.solve([1,2,3]) # solve a system of linear equations
The extended for-in
loop provides built-in iteration over a 2D-array, which is useful in combination with the cross or zip metaoperators:
for a,b in ([1,2] ~X [3,4]) {
say "#{a} #{b}"
}
This is equivalent with:
[[1,2], [3,4]].cartesian {|a,b|
say "#{a} #{b}"
}
and outputs:
1 3
1 4
2 3
2 4
The same functionality is also provided by the .each_2d {|a,b,...| ... }
Array method.
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.
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']
A range defines a sequence of consecutive values, either ascending or descending. The primary advantage of a range compared to an array lies in its potential to be infinite, a capability absent in arrays. This distinction arises from the lazy evaluation characteristic inherent in ranges.
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 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 # ==//==
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)
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 lower-level comparisons, the .sort{}
method can be used:
# 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
A set is an unordered collection of objects, with no duplicates.
Set('foo', 'bar', 'baz')
All the set operators, such as intersection, difference, symmetric difference, union and concatenation, are supported.
var A = Set('foo', 'bar', 'baz', 'foo')
var B = Set('bar', 'foo', 'qux')
# Intersection
say (A & B) #=> Set("foo", "bar")
# Union
say (A | B) #=> Set("foo", "qux", "bar", "baz")
# Difference
say (A - B) #=> Set("baz")
say (B - A) #=> Set("qux")
# Symmetric difference
say (A ^ B) #=> Set("baz", "qux")
# Concatenation
say (A + B) #=> Set("baz", "bar", "qux", "foo")
The method set.delete(obj)
can be used for removing a given object from the set.
A bag (also known as a multi-set) is a unordered collection of objects, similar to a hash table, where each object has a count number, which represents the number of times it exists in the bag.
Bag('foo', 'bar', 'baz')
The Bag class supports all the set operators, such as intersection, difference, symmetric difference, union and concatenation.
var A = Bag('foo', 'bar', 'baz', 'foo')
var B = Bag('bar', 'foo', 'qux')
# Count how many times is 'foo' present in the bag A
say A.count('foo') #=> 2
# Intersection
say (A & B) #=> Bag("foo", "bar")
# Union
say (A | B) #=> Bag("qux", "bar", "baz", "foo", "foo")
# Difference
say (A - B) #=> Bag("baz", "foo")
say (B - A) #=> Bag("qux")
# Symmetric difference
say (A ^ B) #=> Bag("foo", "qux", "baz")
# Concatenation
say (A + B) #=> Bag("foo", "foo", "foo", "bar", "bar", "baz", "qux")
The methods bag.add_pair(obj, count)
and bag.update_pair(obj, count)
can be used for efficiently updating a bag in-place.
var A = Bag('foo', 'foo', 'bar')
# Add 'bar' with count=2
A.add_pair('baz', 2)
say A #=> Bag("baz", "baz", "bar", "foo", "foo")
# Update the count of 'foo'
A.update_pair('foo', 1)
say A #=> Bag("baz", "baz", "bar", "foo")
Furthermore, the method bag.delete(obj)
can be used for removing one occurrence of object obj
from the bag, while the .delete_all(obj)
can be used for removing all the occurrences of obj
from the bag.
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}"
}
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
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
}
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
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
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
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 in Sidef closely resemble those found in most programming languages, featuring the familiar if
, while
, and for
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" }
else { 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!"
}
else {
say "Positive value!"
}
}
An alternative way for testing for trueness, is by passing a Block to when
:
given (42) {
when ({.is_prime}) {
say "is prime"
}
when ({.is_even}) {
say "is even"
}
}
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"
}
}
An exception is thrown by the die
keyword, which, if not caught, it terminates the program with an appropriate exit code.
try {
die "I'm dead!" # throws an exception
}
catch { |msg|
say "msg: #{msg}" # msg: I'm dead! at test.sf line 2.
}
say "I'm alive..."
die "Now I'm dead!" # this line terminates the program
say "Or am I?" # Yes, you are!
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
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]
}
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
In Sidef, a module is the declaration of a new namespace:
module Fibonacci {
func nth(n) {
n > 1 ? nth(n-2)+nth(n-1) : n
}
}
say Fibonacci::nth(12) # prints: 144
The default namespace name is main
. Variables from other namespaces can be used inside a module by either importing them, or by specifying their full name, including the namespace:
var foo = 42
module Bar {
var baz = 99
say main::foo #=> 42
}
say Bar::baz #=> 99
Importing an identifier in the current namespace, can be done using the syntax import namespace::identifier_name
:
var foo = 42
module Bar {
import main::foo
var baz = 2*foo
}
import Bar::baz
say baz #=> 84
A class represents a set of methods and attributes. Objects are created from classes. When a class is invoked, an instance-object of that class is generated, encapsulating the supplied data from class initialization. Each call to a class produces a distinct 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
In Sidef, classes exhibit some divergence from classes in other languages. Instance variables are accessible through method calls that facilitate both retrieval and assignment of values, as depicted in the above example.
The attributes of a class can be either specified as parameters, or declared with the has
keyword.
class Example(a, b) {
has c = 3
has d = a+c
}
var obj = Example(1, 2)
say obj.a #=> 1
say obj.b #=> 2
say obj.c #=> 3
say obj.d #=> 4
Extra object-initialization setup can be done by defining a method named init
, which will be called automatically called whenever a new instance-object is created.
class Example (a, b) {
has r = 0
method init { # called automatically
r = a+b
}
method foo {
r
}
}
var obj = Example(3, 4)
say obj.foo #=> 7
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 { }
The syntax ClassName!var_name
can be used for defining, accessing or modifying a class variable.
class Example {
Example!hidden = 'secret' # global class variable
method concat (str) {
str + ' ' + Example!hidden
}
}
var x = Example()
var y = Example()
say x.concat('foo') #=> 'foo secret'
say y.concat('bar') #=> 'bar secret'
Example!hidden = 'public' # changing the class variable
say x.concat('foo') #=> 'foo public'
say y.concat('bar') #=> 'bar public'
The modification of a class variable can be localized by prefixing the declaration with the local
keyword:
local Example!hidden = 'local value'
An interesting feature is the definition of methods at runtime:
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 (21 ⊕ 42)
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”
Sidef offers basic, yet robust support for parallel computation. The Block.ffork()
method initiates a new system process that concurrently executes the content within a specified block. This method returns a new Fork
object, enabling the use of the wait
(or get
) method to await process completion and retrieve the computed value.
To harness this capability, initiate two or more processes using the forking method and store the resulting objects either in variables or within an array. The execution of each process occurs promptly after forking. Later, retrieve their values when necessary.
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.
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)
There is also a special syntax for literal names of Perl modules, creating a module-interface 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 create the module-interface object afterwards.
require('ntheory')
var nt = %S<ntheory> # function-oriented module
if (nt.is_prime(43)) {
say "43 is prime!"
}
nt.forprimes({|p| say p }, 0, 100)
Due to the fact that we can interact with Perl modules, we can also create graphical interfaces from Sidef, by using the Gtk3
library.
Perl.eval('use Gtk3 -init')
var gtk3 = %O<Gtk3>
var window = %O<Gtk3::Window>.new
var label = %O<Gtk3::Label>.new('Goodbye, World!')
window.set_title('Goodbye, World!')
window.signal_connect(destroy => { gtk3.main_quit })
window.add(label)
window.show_all
gtk3.main
Deparsing is the reverse process of parsing, which translates the AST back into code. Currently, Sidef supports deparsing into two languages with the -R lang
command-line switch:
-R perl
- Deparses the AST into valid Perl code.
-R sidef
- Deparses the AST into valid Sidef code.
Example:
$ sidef -Rperl script.sf | perl
The -Rsidef
switch (or simply -r
) is useful for verifying how the code is parsed.
Example:
$ sidef -r -E '1 + 2/3'
Outputs:
(1)->+((2)->/(3));
By using the PAR::Packer tool, we can create an executable binary from a Sidef script (script.sf
), by executing the following commands:
$ sidef -Rperl script.sf > script.pl
$ pp --execute script.pl
Currently, Sidef code that includes eval()
cannot be compiled into an executable.
Perl.eval()
can evaluate arbitrary Perl code and convert the result into a Sidef data structure which can be used later in the program:
var perl_code = <<'CODE'
sub fact {
my $p = 1;
$p *= $_ for 2..$_[0];
$p;
}
my %data = (
result => fact(10)
);
\%data; # returned data to Sidef
CODE
var data = Perl.eval(perl_code)
say data{:result} #=> 3628800
say Sys.ref(data{:result}) #=> Sidef::Types::Number::Number
Additionally, the %perl{...}
syntax can execute arbitrary Perl code and return a Sidef object. 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 block = %perl{
sub {
my ($n) = @_;
$n <= 1 ? $n : __SUB__->($n-1) + __SUB__->($n-2);
}
}
say block.ref # Sidef::Types::Block::Block
say block(28) # 28-th Fibonacci number
For more Sidef code examples, please see: https://github.com/trizen/sidef-scripts
If you have any questions related to Sidef, please ask here: