NAME
Sq - A Language hosted in Perl
SYNOPSIS
What is a programming language? The foundation of every programming language are the data-structures the language provides you by default. The default data-structures and their possibilites shape how you will build a solution.
Sq is a module that provides certain data-structures. Those data-structures are meant as a replacement for the built-in Perl data-structures. But replacing those data-structures will change the way how you code.
Sq currently provides a lazy sequence Seq, extension for Array, Hash,
a Queue, List and a Heap data-structure.
It uses an Option and Result type for representing the absence of values
or representing Ok/Err values.
It is planned to implement Discriminated Unions.
Instead of classes, typing is done with an Structural Typing approach. It is
available under Sq::Type but not fully documented yet. This can be used as
an argument validator or even as a testing tool.
Under Sq::Parser there is a Combinator based approach to parsing. It
is already usuable and can be used for certain things but sill lacks some
features like useful error-messages.
Sq don't invent new syntax, it just uses Perl as much as possible. It
is also implemented in pure Perl so far.
The idea of Sq is to combine procedural and functional programming and
stick to a design that splits Data from Code. Because it leads to better
software, is easier to develop and has more reusable code.
The whole point is that it offers all basic operations you usually do in Perl
like reading files, directories, parsing in general, parsing arguments, testing
and a lot of other stuff in it's own System that uses the provided data-structures
like Seq, Array, Option and Result so you can use it as a foundation
to develop more abstract things without re-implementing the basics again and again.
HISTORY
I started Sq first by just being a lazy Sequence and named it Seq.
But the Seq namespace in Perl was already taken, deleted, and the old
maintainer doesn't react to eMails anymore.
So I decided to just name it Sq instead. At the same time I already had
the idea to bring more stuff to Perl, like Records, Pattern Matching
and Discriminated Unions, also some other approach to type-checking.
So instead of releasing a dozens of seperate modules I thought about
making one module that just combines all this ideas together that also can
depend on each other. I wanted a short name, so i just removed the e
from Seq and just named it Sq. You still can pronounce it Seq.
There is no meaning/abreviation behind Sq. But when you can think of one
that makes sense or you like then feel free to contribute.
Implemented so Far
Most stuff at the moment is just a place-holder, maybe some will never be implemented, but some stuff is already usable and tested. So if you really want to look around of what is usable you should look at the tests at the moment. I anyway think that code is the best way to see and understand code.
But the API itself is not fixed, means some stuff is very likely to change.
I wouldn't recommend this module at the moment to build something critical unless you are fine that you maybe need sometimes small-fixes to make code working again.
- Sq::Core::Option
- Sq::Core::Result (Partially implemented)
- Sq::Collections::Seq
- Sq::Collections::Hash
- Sq::Collections::Array
- Sq::Collections::Queue
- Sq::Collections::List
- Sq::Collections::Heap
- Sq::Type
- Sq::Parser
- Sq::Signature
- Sq::Dump (used for
->dumpand->dumpwmethods on types) - Sq::Equality (implements
equalfunction and adds methods) - Sq::Test (minimal Test-Suite that is already used to test Sq itself)
Seq Module
As I started everything with the Seq module, here are some example
how to use Seq. Keep in mind that Seq is a lazy data-structure, so nothing
is computed until you start querying for data. And only then only as much
is computed as needed.
But a sequence is not just an iterator. An iterator usually ends at some point, then either a new iterator must be created or the iterator must be resetted.
A Seq is more like an immutable-iterator. So it defines a computation that
you can execute and iterate as often as you wish. In some sense I think
that this design is more what someone expects using such a module.
use Sq;
# Fibonacci Generator
my $fib =
Seq->concat(
Seq->new(1,1),
Seq->unfold([1,1], sub($state) {
my $next = $state->[0] + $state->[1];
return $next, [$state->[1],$next];
})
);
# prints: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
$fib->take(20)->iter(sub($x) {
say $x;
});
# you can use the same $fib again, now prints: 1 1 2 3 5
$fib->take(5)->iter(sub($x) {
say $x;
});
# Represents all possible combinations
# [[clubs => 7], [clubs => 8], [clubs => 9], ...]
my $cards =
Seq::cartesian(
Seq->new(qw/clubs spades hearts diamond/),
Seq->new(qw/7 8 9 10 J Q K A/)
);
use Path::Tiny qw(path);
# get the maximum id from test-files so far
my $maximum_id =
Seq
->new( path('t')->children )
->map( sub($x) { $x->basename })
->choose( sub($x) { $x =~ m/\A(\d+) .* \.t\z/xms ? $1 : undef })
->max->or(0); # or 0 if the sequence is empty
Parser
Here is an example of the Parser to parse a number with suffix.
use Sq;
use Sq::Parser;
my $num = assign {
my $to_num = sub($num,$suffix) {
return $num if $suffix eq 'b';
return $num * 1024 if $suffix eq 'kb';
return $num * 1024 * 1024 if $suffix eq 'mb';
return $num * 1024 * 1024 * 1024 if $suffix eq 'gb';
};
p_many(
p_maybe(p_match(qr/\s* , \s*/x)), # optional ,
p_map(
$to_num,
p_many (p_strc(0 .. 9)), # digits
p_match(qr/\s*/), # whitespace
p_strc (qw/b kb mb gb/), # suffix
)
);
};
# Tests
is(p_run($num, "1 b, 1kb"), Some([1, 1024]), '1 b & 1kb');
is(p_run($num, "1 kb, 1gb"), Some([1024,1073741824]), '1 kb & 1gb');
is(p_run($num, "1 mb"), Some([1048576]), '1 mb');
is(p_run($num, "1 gb"), Some([1073741824]), '1 gb');
this is an exhausted example. Sq::Parser does not try to replace Regexes. Quite
the opposite. It allows creating Parser with regexes in mind and for good
performance you should try to cramp as much as possible into Perl's regeyes. So
here is the above parser re-written using Perl Regexes.
use Sq;
use Sq::Parser;
my $num = assign {
my $to_num = sub($num,$suffix) {
return $num if fc $suffix eq fc 'b';
return $num * 1024 if fc $suffix eq fc 'kb';
return $num * 1024 * 1024 if fc $suffix eq fc 'mb';
return $num * 1024 * 1024 * 1024 if fc $suffix eq fc 'gb';
};
p_many(
p_matchf(qr/\s* ,? \s* (\d+) \s* (b|kb|mb|gb)/xi, $to_num),
);
};
is(p_run($num, "1 b, 1kb"), Some([1, 1024]), '1 b & 1kb');
is(p_run($num, "1 kb, 1gb"), Some([1024,1073741824]), '1 kb & 1gb');
is(p_run($num, "1 Mb"), Some([1048576]), '1 mb');
is(p_run($num, "1 gb"), Some([1073741824]), '1 gb');
Data over Classes
use Sq;
my $album = sq {
artist => 'Michael Jackson',
title => 'Thriller',
tracks => [
{title => "Wanna Be Startin’ Somethin", duration => 363},
{title => "Baby Be Mine", duration => 260},
{title => "The Girl Is Mine", duration => 242},
{title => "Thriller", duration => 357},
{title => "Beat It", duration => 258},
{title => "Billie Jean", duration => 294},
{title => "Human Nature", duration => 246},
{title => "P.Y.T.", duration => 239},
{title => "The Lady in My Life", duration => 300},
],
};
my $length = $album->length; # 3 - hash has 3 keys
my $tracks = $album->{tracks}->length; # 9 - nine tracks
# 2559 - shortest version
my $album_runtime = $album->get('tracks')->map(call 'sum_by', key 'duration')->or(0);
# 2559 - expanded the "call" function
my $album_runtime = $album->get('tracks')->map(sub ($tracks) {
$tracks->sum_by(key 'duration');
})->or(0);
# 2559 - expanded the "key" function
my $album_runtime = $album->get('tracks')->map(sub ($tracks) {
$tracks->sum_by(sub($hash) {
$hash->{duration}
});
})->or(0);
# 2559 - Pure Perl version
my $album_runtime = assign {
my $sum = 0;
my $tracks = $album->{tracks};
if ( defined $tracks ) {
for my $track ( @$tracks ) {
$sum += $track->{duration};
}
}
return $sum;
};
Default Equality
Loading Sq automatically loads an equal function that can recursively check
a data-structure to be equal or not. By default it supports checking of Array,
Hash, Seq, Option and Result and sure also comparing numbers and strings.
By default this function is also installed as a method/function into the above
packages, so you also can call equal as a method on those types.
# Sq enhanced data-structure
my $album1 = sq {
Artist => 'Queen',
Title => 'Greatest Hits',
Tracks => Seq->new(
{ Title => 'We will Rock You' },
{ Title => 'Radio Gaga' },
{ Title => 'Who Wants To Life Forever' },
{ Title => "You Don't Fool Me" },
),
Tags => Some(qw/80/),
};
# pure perl data-structure
my $album2 = {
Artist => 'Queen',
Title => 'Greatest Hits',
Tracks => Seq->new(
{ Title => 'We will Rock You' },
{ Title => 'Radio Gaga' },
{ Title => 'Who Wants To Life Forever' },
{ Title => "You Don't Fool Me" },
),
Tags => Some(qw/80/),
};
my $bool = equal($album1, $album2); # 1
my $bool = $album1->equal($album2); # 1
Typing
use Sq;
use Sq::Type;
# Describes an address
my $address = t_hash(t_keys(
street => t_str,
city => t_str,
state => t_str,
zip => t_match(qr/\A\d+\z/),
));
# A user containing an address
my $user = t_hash(t_keys(
id => t_str,
first => t_str,
last => t_str,
address => $address,
));
my $user1 = {
id => 1,
first => "David",
last => "Raab",
address => {
street => 'Wonder Street',
city => 'Wonder City',
state => 'Wonder State',
zip => '12345',
},
};
my $user2 = {
id => 1,
frist => "David", # Typo
last => "Raab",
address => {
street => 'Wonder Street',
city => 'Wonder City',
state => 'Wonder State',
zip => '12345',
},
};
# Tests
is(t_run($user, $user1), Ok(1), '$user1 is a user');
is(t_run($user, $user2), Err("first does not exists on hash"), '$user2 has a typo');
# describes an album
my $is_album = assign {
# checks for format and if min:seconds are not >= 60
my $duration = t_matchf(qr/\A(\d\d):(\d\d)\z/, sub($min,$sec) {
return if $min >= 60;
return if $sec >= 60;
return 1;
});
return
t_hash(
t_with_keys(qw/artist title tracks/),
t_keys(
artist => t_str(t_min 1), # string must have at least 1 char
title => t_str(t_min 1),
tracks => t_array(
t_min(1), # Array must have at least 1 entry
t_of(t_hash( # All entries must be hashes
t_with_keys(qw/name duration/),
t_keys(
name => t_str,
duration => $duration))))));
};
my $result = t_run ($is_album, $album); # Returns Result
my $bool = t_valid($is_album, $album); # Returns boolean
t_assert($is_album, $album); # Throws exception when not valid
Signatures
The Type system can be used to add type-checking to any function. But the idea is that this kind of type-checking is only added in developing / testing. For code running in production the type-check is removed. It works like the Memoize module by replacing a function with type-checking.
So in production you don't pay the price of type-checking in every function. You just enable it when you need to find errors/bugs or during normal development to find quickly type-errors.
use Sq;
use Sq::Sig; # this adds type-checking to all kind of functions in Sq.
# throws an exception when Sq::Sig is loaded complaining that the array is not
# even-sized. Otherwise without Sq::Sig it gives some warnings but continues.
my $hash = sq([1,2,3])->as_hash;
You can add type-checking to any function.
use Sq;
use Sq::Type;
use Sq::Signature;
sub whatever($int, $str, $array_of_nums) {
# ...
return $hash;
}
# this adds type-checking to the function. Usually you put those signatures
# in its own file that can be loaded at will. This also correctly checks
# the return value of a function. So when you refactor/change code you get
# errors when you return the wrong things.
sig('main::whatever', t_int, t_str, t_array(t_of t_num), t_hash);
whatever("foo", "foo", [1,2,3]); # fails
whatever( 123, "foo", ["foo"]); # fails
whatever( 123, [], [1,2,3]); # fails
whatever(123.3, 123, []); # fails
whatever( 123, 123, []); # ok - because "123" is also a valid string
whatever( 123, "foo", []); # ok
whatever( 123, "foo", [1,2,3]); # ok
EXPORT
It exports the following functions by default: sq, equal, call, key, id, fst, snd, assign, is_str, is_num, Some, None, Ok, Err.
SUPPORT
Development project is on Github Perl-Sq
You can find documentation for this module with the perldoc command.
perldoc Sq
You can also look for information at my Blog on Perl Sq
AUTHOR
David Raab, davidraab83 at gmail.com
LICENSE AND COPYRIGHT
This software is Copyright (c) by David Raab.
This is free software, licensed under:
The MIT (X11) License