2015/10/26


Perl には x 演算子があり、x 3 といった具合に繰り返し回数を付ける事で連続した文字列を簡単に作り出すことが出来る。

say 1 x 3; # 111
say "foo" x 3; # foofoofoo

これは Perl5 でもお馴染み。しかし Perl6 の Range は凄い。

say 1 xx 3; # (1 1 1)
say "foo" xx 3; # (foo foo foo)

連続した配列要素が作り出せる。それどころか無限リストが作り出せる。

say 1 xx *; # (...)
say "foo" xx *; # (...)

もちろん無限数列も作れる。

(1 .. *)[^10].perl.say; # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

fibonacci 数列もこんなに簡単。

(1, 1, *+* ...^ *)[^100].perl.say;

上記の様に無限リストをスライスするには [^10][40..42] の様に指定する。要素は数字や文字列に限った話ではない。

my $a = <a b c>;
my $b = $a xx *;
say $b[^5]; # ((a b c) (a b c) (a b c) (a b c) (a b c))

配列の繰り返しも可能だし、| を使えばダイナミックバインディングも使える。

my $a = <a b c>;
my $b = |$a xx *;
say $b[^10]; # (a b c a b c a b c a)

応用すれば FizzBuzz だって。

my $r = 0;
my $a = sub {
  $r++;
  given 0 {
  when $r % 15 { "FizzBuzz" }
  when $r % 3 { "Fizz" }
  when $r % 5 { "Buzz" }
  default { $r };
  }
};
my $b = |$a() xx *;
say $b[^20];

この |、実は凄くて

multi sub foo(Str $a) {
  say "foo1";
}

multi sub foo(Str $a, Str $b) {
  say "foo2";
}

my $a = <a>;
foo |$a; # foo1

$a = <a b>;
foo |$a; # foo2

メソッドのダイナミック呼び出しも出来る。Perl6 凄い。


2015/10/19


Perl6 には Grammar という機能があるのですが、これがまた凄いんです。スキャナとトークナイザと処理系が引っ付いている様な物がデフォルトで提供されているんです。

通常はこれらが別の機能として提供されており、プログラミング言語を実装する過程でデータの受け渡しがシームレスではなく、実装を変えたりするのが非常に面倒だったりします。しかしこれが Perl6 という一つの処理系の中で提供されてしまっている為、本来であれば数百ステップくらい掛かってしまう俺言語のコードが50ステップ程度で書けてしまいます。

use v6;

grammar SyoboiScript::Grammar {
    token num { <[0..9]>+ }
    token ident { <[a..z]>+ }
    token op { '+' || '-' || '*' || '/' }
    token exp { <ident> || <num> }
    token expr { <exp> | <exp> \s* <op> \s* <exp> }
    token let { <ident> \s* '=' \s* <expr> }
    token call { <ident> '(' \s* <expr> \s* ')' }
    token stmt { <call> || <let> }
    rule TOP { ^ ( <stmt> || '#' .* || "\n" )* $ }
}

class SyoboiScript::Action {
    my Hash $func = {
        print => sub ($arg) { 
            say $arg; 
        } 
    };
    my Hash $vars;

    method num($/) { make EVAL $/.Str }
    method ident($/){
        make $vars{$/.Str};
    }
    method call($/){
        make $func{$<ident>.Str}(self.expr($<expr>));
    }
    method let($/){
        my $v = self.expr($<expr>);
        $vars{$<ident>.Str= $v;
        make $v;
    }
    method expr($/){
        given $<op> {
            when '+' { make [+]($<exp>>>.ast) }
            when '-' { make [-]($<exp>>>.ast) }
            when '*' { make [*]($<exp>>>.ast) }
            when '/' { make [/]($<exp>>>.ast) }
            default  { make $<exp>>>.ast[0] }
        }
    }
 
    method exp($/) {
        make $/.values[0].ast;
    }
 
    method TOP($/) { make $<exp>.ast; }
}

sub MAIN(Bool :$dump = False, Str :$file = 'syoboi.ss') {
    my $act = SyoboiScript::Action.new;
    SyoboiScript::Grammar.parse(slurp($file), actions => $act);
}

たったこれだけですが、以下の syoboi.ss というスクリプトファイルを読み込み、画面に3と表示するプログラミング言語が実装出来ました。

= 1
= a + 2
print(a)

Perl6 凄い!


2015/10/14


Lepton という数式パーサを見つけたので遊んでみた。

Simtk.org: Lepton Mathematical Expression Parser: Overview

Purpose/Synopsis: A small C++ library for parsing, evaluating, differentiating, and analyzing mathem...

https://simtk.org/home/lepton

一般的な数式パーサは expression を token に分解するのみだが、この lepton はもう1歩踏み込んだ処理が行える。

通常の数式は以下の様にパースし実行出来る。

#include <iostream>
#include <string>
#include <map>
#include <Lepton.h>

int
main(int argc, char* argv[]) {
  std::map<std::string, double> variables;
  variables["x"] = 2.0;
  variables["y"] = 3.0;
  std::cout << Lepton::Parser::parse("x^y").evaluate(variables) << std::endl; 
  // 8
  return 0;
}

答えは8となる。もちろん定数も扱える。定数は optimize により実行前に展開される。例えば 2*3*xoptimize により 6*x にコンパイルされる。

#include <iostream>
#include <string>
#include <map>
#include <cmath>
#include <algorithm>
#include <Lepton.h>

#define PI 3.14

int
main(int argc, char* argv[]) {
  std::map<std::string, double> constants;
  constants["pi"] = PI;

  Lepton::ParsedExpression exp = Lepton::Parser::parse("2*pi*x").optimize(constants);

  std::cout << exp << std::endl;
  // 6.28319*(x) ;

  std::map<std::string, double> variables;
  variables["x"] = 2;
  std::cout << exp.evaluate(variables) << std::endl;
  // 12.56
  return 0;
}

また変数の参照も扱える。

#include <iostream>
#include <string>
#include <map>
#include <cmath>
#include <algorithm>
#include <Lepton.h>

int
main(int argc, char* argv[]) {
  Lepton::CompiledExpression expression =
    Lepton::Parser::parse("x+y^2").createCompiledExpression();
  double& x = expression.getVariableReference("x");
  double& y = expression.getVariableReference("y");
  x = 1;
  y = 2;
  std::cout << expression.evaluate() << std::endl;
  // 5
  return 0;
}

尚、実行時に変数が解決されていないと例外が発生する様になっている。

さらに関数が定義出来るので sin を定義してみた。

#include <iostream>
#include <string>
#include <map>
#include <cmath>
#include <algorithm>
#include <Lepton.h>

class SinFunc : public Lepton::CustomFunction {
  int getNumArguments() const {
    return 1;
  }
  Lepton::CustomFunction* clone() const {
    return new SinFunc();
  }
  double evaluate(const double* arguments) const {
    return std::sin(arguments[0]);
  }
  double evaluateDerivative(const double* arguments, const int*derivOrder) const {
    return evaluate(arguments);
  }
};

int
main(int argc, char* argv[]) {
  std::map<std::string, Lepton::CustomFunction*> functions;
  SinFunc sin_func;
  functions["sin"] = &sin_func;
  Lepton::ParsedExpression exp = Lepton::Parser::parse("sin(Θ)", functions);
  std::map<std::string, double> variables;
  variables["Θ"] = 3.14 / 2;
  std::cout << exp.evaluate(variables) << std::endl;
  // 1
  return 0;
}

とても便利そうです。これの文字列が扱える物があれば FizzBuzz とか出来て便利(ではないが)そうと思ってしまい、我ながら駄目な人間だなーと思った。

ライセンスはMITです。