Автоматически добавлять сигнатуры типов к функциям верхнего уровня
Я был ленив и написал модуль Haskell (используя отличную среду EclipseFP) без предоставления подписей типа к моим функциям верхнего уровня.
EclipseFP использует HLint для автоматической маркировки каждой оскорбительной функции, и я могу исправить каждый из них с помощью четырех щелчков мыши. Эффективный, но утомительный.
Есть ли утилита, которая сканирует файл .hs и испускает измененную версию, которая добавляет сигнатуры типов к каждой функции верхнего уровня?
Пример:
./addTypeSignatures Foo.hs
будет читать файл Foo.hs
:
foo x = foo + a
и испускать
foo :: Num a => a -> a
foo x = x + 1
Бонусные очки, если инструмент автоматически редактирует Foo.hs
на месте и сохраняет резервную копию Foo.bak.hs
Ответы
Ответ 1
Существует haskell-режим для emacs, который имеет ярлык для вставки типа сигнатуры функции: C-u, C-c, C-t. Это не автоматическое, вы должны сделать это для каждой функции. Но если у вас есть только один модуль, вам, вероятно, потребуется несколько минут, чтобы пройти через него.
Ответ 2
Здесь приведен вариант выше script, который использует ": browse" вместо ": type", за один комментарий.
Одна из основных проблем с этим решением заключается в том, что ": browse" отображает полностью квалифицированные имена типов, тогда как ": type" использует импортированные (сокращенные) имена типов. Это, если в вашем модуле используются неквалифицированные импортированные типы (общий случай), вывод этого script не будет компилироваться.
Этот недостаток является фиксируемым (с использованием некоторого анализа импорта), но это отверстие кролика становится глубоким.
#!/usr/bin/env perl
use warnings;
use strict;
sub trim {
my $string = shift;
$string =~ s/^\s+|\s+$//g;
return $string;
}
my $sig=0;
my $file;
my %funcs_seen = ();
my %keywords = ();
for my $kw qw(type newtype data class) { $keywords{$kw} = 1;}
foreach $file (@ARGV)
{
if ($file =~ /\.lhs$/)
{
print STDERR "$file: .lhs is not supported. Skipping.\n";
next;
}
if ($file !~ /\.hs$/)
{
print STDERR "$file is not a .hs file. Skipping.\n";
next;
}
my $module = $file;
$module =~ s/\.hs$//;
my $browseInfo = `echo :browse | ghci $file`;
if ($browseInfo =~ /Failed, modules loaded:/)
{
print STDERR "$browseInfo\n";
print STDERR "$file is not valid Haskell source file. Skipping.\n";
next;
}
my @browseLines = split("\n", $browseInfo);
my $browseLine;
my $func = undef;
my %dict = ();
for $browseLine (@browseLines) {
chomp $browseLine;
if ($browseLine =~ /::/) {
my ($data, $type) = split ("::", $browseLine);
$func = trim($data);
$dict{$func} = $type;
print STDERR "$func :: $type\n";
} elsif ($func && $browseLine =~ /^ /) { # indent on continutation
$dict{$func} .= " " . trim($browseLine);
print STDERR "$func ... $browseLine\n";
} else {
$func = undef;
}
}
my $backup = "$file.bak";
my $new = "$module.New.hs";
-e $backup and die "Backup $backup file exists. Refusing to overwrite. Quitting";
open OLD, $file;
open NEW, ">$new";
print STDERR "Functions in $file:\n";
my $block_comment = 0;
while (<OLD>)
{
my $original_line = $_;
my $line = $_;
my $skip = 0;
$line =~ s/--.*//;
if ($line =~ /{-/) { $block_comment = 1;} # start block comment
$line =~ s/{-.*//;
if ($block_comment and $line =~ /-}/) { $block_comment=0; $skip=1} # end block comment
if ($line =~ /^ *$/) { $skip=1; } # comment/blank
if ($block_comment) { $skip = 1};
if (!$skip)
{
if (/^(('|\w)+)( +(('|\w)+))* *=/ )
{
my $object = $1;
if ((! $keywords{$object}) and !($funcs_seen{$object}))
{
$funcs_seen{$object} = 1;
print STDERR "$object\n";
my $type = $dict{$1};
unless ($sig)
{
if ($type) {
print NEW "$1 :: $type\n";
print STDERR "$1 :: $type\n";
} else {
print STDERR "no type for $1\n";
}
}
}
}
$sig = /^(('|\w)+) *::/;
}
print NEW $original_line;
}
close OLD;
close NEW;
my $ghciPostTest = `echo 1 | ghci $new`;
if ($ghciPostTest !~ /Ok, modules loaded: /)
{
print $ghciPostTest;
print STDERR "$new is not valid Haskell source file. Will not replace original (but you might find it useful)";
next;
} else {
rename ($file, $backup) or die "Could not make backup of $file -> $backup";
rename ($new, $file) or die "Could not make new file $new";
}
}
Ответ 3
Этот perl script выполняет хакерскую работу, делая некоторые предположения о структуре исходного файла. (Например: .hs
file (not .lhs
), подписи находятся в строке, непосредственно предшествующей определениям, определения - на краю левого поля и т.д.)
Он пытается обрабатывать (пропускать) комментарии, определения стиля уравнения (с повторяющимися левыми сторонами) и типы, которые генерируют многострочный вывод в ghci
.
Несомненно, многие интересные действительные случаи не обрабатываются должным образом. script не близок к соблюдению фактического синтаксиса Haskell.
Это невероятно медленно, поскольку он запускает сеанс ghci
для каждой функции, для которой требуется подпись.
Он создает резервный файл File.hs.bak
, печатает функции, которые он находит, для stderr, а также подписи для функций, отсутствующих подписи, и записывает обновленный исходный код на File.hs
. Он использует промежуточный файл File.hs.new
и имеет несколько проверок безопасности, чтобы избежать перезаписи вашего содержимого с помощью мусора.
ИСПОЛЬЗУЙТЕ СВОЙ СОБСТВЕННЫЙ РИСК.
Этот script может отформатировать ваш жесткий диск, записать ваш дом вниз, небезопасныйPerformIO и иметь другие нечистые побочные эффекты. На самом деле это, вероятно, будет.
Я чувствую себя настолько грязным.
Протестировано на Mac OS X 10.6 Snow Leopard с несколькими моими исходными файлами .hs
.
#!/usr/bin/env perl
use warnings;
use strict;
my $sig=0;
my $file;
my %funcs_seen = ();
my %keywords = ();
for my $kw qw(type newtype data class) { $keywords{$kw} = 1;}
foreach $file (@ARGV)
{
if ($file =~ /\.lhs$/)
{
print STDERR "$file: .lhs is not supported. Skipping.";
next;
}
if ($file !~ /\.hs$/)
{
print STDERR "$file is not a .hs file. Skipping.";
next;
}
my $ghciPreTest = `echo 1 | ghci $file`;
if ($ghciPreTest !~ /Ok, modules loaded: /)
{
print STDERR $ghciPreTest;
print STDERR "$file is not valid Haskell source file. Skipping.";
next;
}
my $module = $file;
$module =~ s/\.hs$//;
my $backup = "$file.bak";
my $new = "$module.New.hs";
-e $backup and die "Backup $backup file exists. Refusing to overwrite. Quitting";
open OLD, $file;
open NEW, ">$new";
print STDERR "Functions in $file:\n";
my $block_comment = 0;
while (<OLD>)
{
my $original_line = $_;
my $line = $_;
my $skip = 0;
$line =~ s/--.*//;
if ($line =~ /{-/) { $block_comment = 1;} # start block comment
$line =~ s/{-.*//;
if ($block_comment and $line =~ /-}/) { $block_comment=0; $skip=1} # end block comment
if ($line =~ /^ *$/) { $skip=1; } # comment/blank
if ($block_comment) { $skip = 1};
if (!$skip)
{
if (/^(('|\w)+)( +(('|\w)+))* *=/ )
{
my $object = $1;
if ((! $keywords{$object}) and !($funcs_seen{$object}))
{
$funcs_seen{$object} = 1;
print STDERR "$object\n";
my $dec=`echo ":t $1" | ghci $file | grep -A100 "^[^>]*$module>" | grep -v "Leaving GHCi\." | sed -e "s/^[^>]*$module> //"`;
unless ($sig)
{
print NEW $dec;
print STDERR $dec;
}
}
}
$sig = /^(('|\w)+) *::/;
}
print NEW $original_line;
}
close OLD;
close NEW;
my $ghciPostTest = `echo 1 | ghci $new`;
if ($ghciPostTest !~ /Ok, modules loaded: /)
{
print $ghciPostTest;
print STDERR "$new is not valid Haskell source file. Will not replace original (but you might find it useful)";
next;
} else {
rename ($file, $backup) or die "Could not make backup of $file -> $backup";
rename ($new, $file) or die "Could not make new file $new";
}
}
Ответ 4
Для редактора Atom можно автоматически вставить подпись типа для каждой функции с пакетом haskell-ghc-mod, который обеспечивает:
'ctrl-alt-T': 'haskell-ghc-mod:insert-type'
https://atom.io/packages/haskell-ghc-mod#keybindings