Недавно я наткнулся на один любопытный проект. Он называется Berp и представляет собой транслятор скриптов на языке Python в программы на Haskell. Со стороны пользователя Berp выглядит как интерпретатор и компилятор Python, поскольку вся трансляция происходит «в бэкенде».
***
Проект достаточно молод. Чтобы попробовать Berp, нужно самостоятельно собрать его из исходников. Сами исходники лежат на гитхабе . Сборка происходит примерно таким образом:
cabal install haskell-src-exts language-python parseargs
cabal install MonadCatchIO-mtl
cd path / to / unzipped / bjpop-berp-63eb4a0 / libs
cabal configure
cabal install
cd .. / compiler
cabal configure
cabal build
cd dist / build / berp
Не могу не отметить, что компилировать программы на Haskell — одно удовольствие. Никакой возни с настройкой программы под свою систему, поиском библиотек или различными версиями компиляторов. Кто пробовал собирать в Visual Studio программы, написанные под GCC, тот понимает, о чем речь.
Итак, в результате описанных выше шагов, мы получили компилятор berp.exe (я тестировал Berp под Windows). Теперь попробуем скомпилировать с его помощью простенький скрипт:
print ( «Hello!» )
Говорим:
test.exe
Должны увидеть:
Размер полученного исполняемого файла составил 3 Мб. Многовато, конечно, но жить можно. В экзешнике был замечен традиционный для Haskell «мусор», который можно вычистить с помощью утилиты strip. (Подробнее о тюнинге программ на Haskell можно прочитать в заметке, посвященной wxHaskell .) В таблице импорта никаких лишних библиотек не обнаружилось.
Помимо самой программы, Berp также создал два файла следующего содержания. Файл Main.hs:
import Prelude ( )
import Berp . Base ( run )
import Berp _ test ( init )
main = run init
Файл Berp_test.hs:
import Prelude ( )
import Berp . Base
init globals
= do _ t _ 0 <- readGlobal globals ( 177724922 , «_s_print» )
_ t _ 0 @@ [ string «Hello!» ]
Насколько я могу судить, использовать этот код в своих программах на Haskell будет затруднительно.
***
Здорово, конечно, что такой простенький пример успешно собрался, но ведь нас с вами обычно интересуют программы посложнее, не так ли? Сначала я хотел написать скрипт, выводящий квадратные корни чисел от 1 до 100, но выяснилось, что питоновский модуль math написан на Си.
Таким образом, не ясно, как его использовать в Berp. Мы даже не можем воспользоваться модулями PyPy, хотя, как я понимаю, все они написаны без использования Си. Дело в том, что PyPy — это реализация Python 2.7, а Berp понимает только Python 3. В некоторых отношениях различия между этими языками довольно существенны.
Тогда я сваял следующий пример. Файл mymodule.py:
def getHello ( ) :
return ( «Hello from mymodule!» )
Файл test.py:
from mymodule import getHello
print ( getHello ( ) )
Этот пример успешно компилируется, но при запуске выдает ошибку:
Если кому интересно, файл Berp_mymodule.hs:
import Prelude ( )
import Berp . Base
init globals
= do _ t _ 0 <- def 0 none
( [ ] -> ret ( string «Hello from mymodule!» ) )
writeGlobal globals ( 9072933 , «_s_getHello» ) _ t _ 0
Файл Berp_test.hs:
import Prelude ( )
import Berp . Base
import qualified Berp _ mymodule ( init )
init globals
= do _ t _ 0 <- importModule «mymodule» Berp _ mymodule . init
_ t _ 1 <- _ t _ 0 . ( 9072933 , «_s_getHello» )
writeGlobal globals ( 9072933 , «_s_getHello» ) _ t _ 1
_ t _ 2 <- readGlobal globals ( 177724922 , «_s_print» )
_ t _ 3 <- readGlobal globals ( 9072933 , «_s_getHello» )
_ t _ 4 <- _ t _ 3 @@ [ ]
_ t _ 2 @@ [ _ t _ 4 ]
Main.hs остался таким же, как и в предыдущем примере.
***
Ок, с импортом вышел небольшой косяк. Но как Berp работает с остальными конструкциями языка? Чтобы выяснить это, нужно попробовать собрать какую-нибудь реальную и при этом довольно сложную программу. На ум ничего хорошего не пришло, так что я тупо переписал (в который раз) программу, решающую задачу о кодировании цифр . Отдельно пришлось повозиться с допиливанием скрипта под Python 3 . В итоге получилось следующее:
# (c) Alexandr A Alexeev 2011 | http://remontka.com/
from functools import reduce
def nub ( lst ) :
seen = set ( )
rslt = list ( )
for itm in lst:
if itm not in seen:
seen. add ( itm )
rslt. append ( itm )
return rslt
def nub2 ( lst ) : # lists are not hashable, but tuples are
tmp = nub ( list ( map ( lambda x: tuple ( x ) , lst ) ) )
return list ( map ( lambda x: list ( tmp ) , tmp ) )
def signalsNumber ( sigSet ) :
return len ( sigSet )
def bitsNumber ( sigSet ) :
return len ( sigSet [ 0 ] )
def allSignals ( bits ) :
if bits == 0 :
return [ [ ] ]
rest = allSignals ( bits- 1 )
return list ( map ( lambda x: [ False ] + x , rest ) ) +
list ( map ( lambda x: [ True ] + x , rest ) )
def allDefects ( bits ) :
return allSignals ( bits )
def isCriticalDefect ( defect , sigSet ) :
defectedSigSet = nub2 ( list ( map ( lambda s:
list ( map ( lambda x: x [ 0 ] & x [ 1 ] , zip ( s , defect ) ) ) , sigSet ) ) )
return signalsNumber ( sigSet ) != signalsNumber ( defectedSigSet )
def allNoncriticalDefects ( sigSet ) :
defects = allDefects ( bitsNumber ( sigSet ) )
return list ( filter ( lambda x: not ( isCriticalDefect ( x , sigSet ) ) ,
defects ) )
def solveFirstTask ( signSet ) :
defects = allNoncriticalDefects ( signSet )
temp = list ( map ( lambda t:
( reduce ( lambda x , b: x if b else x + 1 , t , 0 ) , [ t ] ) , defects ) )
return reduce ( lambda a , b:
( ( a [ 0 ] , a [ 1 ] + b [ 1 ] ) if b [ 0 ] == a [ 0 ] else b ) if b [ 0 ] >= a [ 0 ]
else a , temp )
def solveSecondTask ( signSets ) :
solutions = zip ( list ( map ( lambda x: solveFirstTask ( x ) , signSets ) ) ,
list ( map ( lambda x: [ x ] , signSets ) ) )
return reduce ( lambda x , y:
( ( x [ 0 ] , x [ 1 ] + y [ 1 ] ) if x [ 0 ] [ 0 ] == y [ 0 ] [ 0 ] else x )
if x [ 0 ] [ 0 ] >= y [ 0 ] [ 0 ] else y , solutions )
def intArrToSignalSet ( mtx ) :
return list ( map ( lambda lst: list ( map ( lambda itm: itm != 0 , lst ) ) ,
mtx ) )
def sevenPosSignalSet ( ) :
return intArrToSignalSet ( [
[ 1 , 1 , 1 , 0 , 1 , 1 , 1 ] , # 0
[ 0 , 0 , 1 , 0 , 0 , 1 , 0 ] , # 1
[ 1 , 0 , 1 , 1 , 1 , 0 , 1 ] , # 2
[ 1 , 0 , 1 , 1 , 0 , 1 , 1 ] , # 3
[ 0 , 1 , 1 , 1 , 0 , 1 , 0 ] , # 4
[ 1 , 1 , 0 , 1 , 0 , 1 , 1 ] , # 5
[ 1 , 1 , 0 , 1 , 1 , 1 , 1 ] , # 6
[ 1 , 0 , 1 , 0 , 0 , 1 , 0 ] , # 7
[ 1 , 1 , 1 , 1 , 1 , 1 , 1 ] , # 8
[ 1 , 1 , 1 , 1 , 0 , 1 , 1 ] , # 9
] )
def ninePosSignalSet ( ) :
return intArrToSignalSet ( [
[ 1 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 1 ] , # 0
[ 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 ] , # 1
[ 1 , 0 , 0 , 1 , 0 , 0 , 1 , 0 , 1 ] , # 2
[ 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 0 ] , # 3
[ 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 0 ] , # 4
[ 1 , 1 , 0 , 0 , 1 , 0 , 0 , 1 , 1 ] , # 5
[ 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 1 ] , # 6
[ 1 , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 ] , # 7
[ 1 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , 1 ] , # 8
[ 1 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 ] , # 9
] )
def unpackIntArrays ( mtx ) :
return reduce ( lambda x , y:
[ a + [ b ] for a in x for b in y ] , mtx , [ [ ] ] )
def multipleNinePosSignalSets ( ) :
unpacked = unpackIntArrays ( [
[ [ 1 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 1 ] ] ,
[ [ 0 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 ] , [ 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 0 ] , [ 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 ] ] ,
[ [ 1 , 0 , 0 , 1 , 0 , 0 , 1 , 0 , 1 ] , [ 1 , 0 , 0 , 1 , 1 , 1 , 0 , 0 , 1 ] ] ,
[ [ 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 0 ] , [ 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1 ] , [ 1 , 0 , 1 , 0 , 1 , 0 , 0 , 1 , 1 ] ] ,
[ [ 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 0 ] , [ 0 , 0 , 1 , 1 , 1 , 0 , 0 , 1 , 0 ] ] ,
[ [ 1 , 1 , 0 , 0 , 1 , 0 , 0 , 1 , 1 ] , [ 1 , 1 , 0 , 0 , 1 , 0 , 1 , 0 , 0 ] ] ,
[ [ 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 1 ] , [ 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 1 ] ] ,
[ [ 1 , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 ] , [ 1 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 0 ] , [ 1 , 0 , 0 , 1 , 0 , 0 , 1 , 0 , 0 ] ] ,
[ [ 1 , 1 , 0 , 1 , 1 , 1 , 0 , 1 , 1 ] ] ,
[ [ 1 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 ] , [ 1 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 ] ]
] )
return list ( map ( lambda x: intArrToSignalSet ( x ) , unpacked ) )
Следует отметить, что писать в функциональном стиле на Python оказалось намного проще и приятнее, чем на Perl . Кроме того, приведенный скрипт довольно экономно использует память («палка» на графике, как и в случае с Haskell) и работает вполне быстро (хотя и во много раз медленнее аналогичной программы на Haskell).
К сожалению, мне не удалось найти в стандартной библиотеке аналогов функций nub и zipWith. Также для меня остается загадкой, за каким хреном в Python 3 перенесли функцию reduce в отдельный пакет. А еще в третьем питоне стало сложнее использовать функции map и filter, потому что вместо списков они теперь возвращают объекты.
Однако вернемся к Berp. Собрать приведенную выше программу он не в состоянии. Вопреки моим надеждам, встроенной функции reduce в нем не оказалось, а на попытку ее импорта из functools мы получаем:
В итоге reduce пришлось дописать:
if x is None :
x = lst [ 0 ]
lst = lst [ 1 : ]
for i in lst:
x = f ( x , i )
return x
Далее вылез такой косяк:
Пытаемся пофиксить и его:
if c:
return a
else :
return b
И тут наступает epic fail:
Кроме того, выяснилось, что Berp не поддерживает конструкцию «not in»:
{span_filename = «lamp.py», span_row = 9, span_start_column
= 12, span_end_column = 17}}
В общем, ужас!
***
На момент написания этих строк пользоваться Berp было невозможно. Поддержка синтаксиса Python 3 реализована в нем лишь частично. Также не совсем понятно, откуда следует брать стандартные библиотеки. Как я уже отметил, библиотеки PyPy в данном случае не годятся.
Тем не менее, проект довольно интересен. Я искренне надеюсь, что автор ( Bernie Pope ) его не забросит. Особенно мне понравилась идея трансляции чего бы то ни было именно в Haskell, а не традиционные Си и C++. Ведь в этом случае мы получаем не только более хорошую переносимость, но и множество фирменных фишек Хаскеля .
Надо будет посмотреть на Berp еще разок где-нибудь через год.