Boo — питонообразный .NET язык

    Приветствую уважаемые Хабрапользователи, хочется мне рассказать Вам об “ещё одном языке программирования” носящим названием Boo. Язык этот к удивлению малоизвестный, но при этом является очень мощным и удобным, и что самое главное, показывает прекрасный пример заимствования хороших идей в новом и интересном контексте, а именно реализацию синтаксически очень близкого к Python языка в качестве компонента .NET инфраструктуры.

    Данная заметка написана в первую очередь с позиции человека в прошлом работавшего с Питоном, поэтому дотнета тут практически не будет, специфике Boo в связке с .NET будет посвящена следующая статья

    Давайте обратимся к скудной статье на википедии, дабы узнать что же такое Boo
    Boo — объектно-ориентированный статически типизированный язык программирования для платформы .NET. Появился в 2003 году. Поддерживает синтаксис, схожий с Python.

    Любители Питона в этот момент должны начать потирать ручки, и не зря. Питон – язык очень хороший, но увы не панацея от всех проблем, так например его динамическая сущность не самым лучшим образом влияет на производительность и, увы, IronPython и Jython не многим улучшают ситуацию. Конечно, всегда можно вынести проблемные вычисления во внешние модули, например на Си, но это значит — прощай возможность запуска кода на разных платформах без рекомпиляции, да и многие ли Питонисты будут рады писать код на Си…

    Вот тут и пригодиться Boo, уже существующий проект на нём запустить скорее всего не получиться — синтаксис увы не полностью совместим с Питоном, да и внешние библиотеки придётся заменить на .NET аналоги, а вот рассмотреть его как альтернативу при написании новых приложений — замечательная идея.

    Давайте посмотрим как выглядит код на Бу, причём не будем распыляться на отдельные кусочки, а посмотрим на большинство особенностей языка в одном примере(про уток, они мне импонируют, знаете ли). Запустить его можно 3 способами, в интерпретаторах booish и booi(интерактивно и не интерактивном соответственно), или скомпилировав код и запустив его. Под линуксом сделать это проще всего установив MonoDevelop с дополнением под Boo, либо компилятором booc из командной строки.

    1. class BaseDuck:
    2.   
    3.   def Quack(times):
    4.     for time in range(times):
    5.       print "Quack!"
    6.     
    7.   virtual def Fly():
    8.     print "Off we go!"  
    9.     
    10.   def ToString():
    11.     return "Hi, I am BaseDuck"            
    12.                             
    13.   eyes = 2
    14.  
    15.   EyesProperty:
    16.     get:
    17.       return eyes
    18.     set:
    19.       raise "Sorry, but no"                            
    20.   
    21. class ChildDuck(BaseDuck):
    22.  
    23.   def ToString():
    24.     return "Hi, I am ChildDuck"    
    25.   
    26.   override def Fly():
    27.     print "I don't know how to fly"      
    28.     
    29.   
    30. testDuck = BaseDuck()
    31. print testDuck
    32.  
    33. testDuck.Quack(2)
    34. testDuck.Fly()
    35.  
    36. print testDuck.EyesProperty
    37.  
    38. try:
    39.   testDuck.EyesProperty = 3
    40. except:
    41.   pass  
    42.  
    43. print
    44.  
    45. testDuck2 = ChildDuck()    
    46. print testDuck2
    47. testDuck2.Quack(2)
    48. testDuck2.Fly()
    49.  
    50. print
    51.  
    52. ducks = ( BaseDuck(), ChildDuck(), BaseDuck())
    53.  
    54. for oneDuck in ducks:  
    55.   print oneDuck
    56.   oneDuck.Fly()
    57.   
    58. print  
    59.   
    60. duckList = [oneDuck for oneDuck in ducks]  
    61.  
    62. for oneDuck in duckList:  
    63.   print oneDuck
    64.   (oneDuck as BaseDuck).Fly()
    65.   
    * This source code was highlighted with Source Code Highlighter.


    Это, естественно, далеко не полный функционал языка, при этом совершенно не использующий функции .NET. Желающие могут ознакомиться со всеми возможностями языка изучив мануал по ссылке boo.codehaus.org/Language+Guide, мы же пройдёмся по нему, попутно сравнивая код с тем, что написали бы мы, используя стандартный Питон.

    Итак, первым делом мы определим базовый класс BaseDuck, что же броситься в глаза человеку привыкшему к Питону?

    На мой взгляд 4 вещи:
    • Во-первых, что определение класса очень похоже на подобное определение в Питоне, при этом, если бы целью кода не было показать как можно больше интересных особенностей, определение класса могло бы быть полностью совместимым синтаксически с Питоном.
    • Во-вторых, вместо функции __unicode__ использована ToString, работает она при этом полностью аналогично.
    • В-третьих, функция Fly имеет префикс virtual, но об этом позднее.
    • Ну и в-четвёртых, это определение geter'а и setter'а для свойства eyes. На этом пожалуй остановимся подробнее.

    В Питоне нет понятия модификаторов уровня доступа, все атрибуты объектов имеют уровень доступа public. (Эмуляция приватных полей отчасти возможна через конструкцию __, но и эта конструкция лишь меняет порядок доступа к свойству). В том случае если необходимо определить getter и setter на помощь приходят либо декораторы свойства @property, а в 2.6 ещё и @.setter, либо get_ и set_ функции. Boo же, являясь полноценным наследником .NET несёт с собой весь набор модификаторов доступа, при этом переменная eyes по умолчанию получает уровень доступа protected и из вне класса использоваться уже не может. А вот EyesProperty по умолчанию является public, и позволяет установить значения переменной eyes. В этом конкретном случае, мы установить значение не дадим, а выкинем исключение(в этом нет особого смысла, просто утки с 2 глазами мне нравятся больше всего).

    Внимательный хабрачеловек может внезапно задаться вопросом, почему нигде нет объявлений типа переменных, ведь Boo статически типизированный язык? И именно это является одной из приятнейших особенностей языка, везде где возможно компилятор будет определять типы переменных за нас. При этом никто не мешает написать нам к примеру

    eyes as int = 2

    тем самым чётко указать что за тип у переменной.

    Следующим шагом мы определяем класс утки ребёнка, её базовым классом является ранее созданный нами класс базовой утки, а это значит что все свойства базовой утки будут доступны и у утки ребёнка.
    Тут самое время вернуться к определённой в базовой утке функции Fly, как видно из кода у утки ребёнка функция Fly определена как override.

    Мы могли бы опустить определение виртуальной функции и её перекрывающей, и тем самым получить вновь код совершенно одинаковый с Питон реализацией, но при этом могли бы потенциально наступить на грабли, связанные со статической типизацией. Пара virtualoverride гарантирует нам что у утки дитя будет вызвана именно её функция, а не функция родительского класса(Если это не ясно, советую спросить у гугла про Virtual Functions). Мы можем стереть эти определения и посмотреть как измениться вывод программы — в строке 55 при этом будет трижды вызван конструктор базовой утки, несмотря на то что второй элемент создан как объект другого класса.

    Далее в коде мы создаём объект типа BaseDuck(строка 30), смотрим как отрабатывает её строковое представление(строка 31), вызываем функции(попутно замечая что Boo поддерживает генераторы), а также считываем и пытаемся установить значения свойства eyes. Поскольку установить его мы не можем, то демонстрируем тихое сокрытие исключения в блоках tryexcept(36-41).

    Наигравшись с первой уткой, создаём утку дитя и видим как определённая в её классе функция заменяет родительскую функцию. ( Это код будет работать аналогично и без virtualoverride директив, потому что тип переменных задан явно )

    А вот строка 52 может показаться непонятной. Но тут всё просто, мы создаём массив с элементами типа BaseDuck и инициализируем его 3 утками, которые на лету и создаём. При этом мы как ни странно можем написать и

    ducks = ( BaseDuck(), ChildDuck(), BaseDuck())

    Boo при этом создаст массив объектов типа object, но проверит что все последующие вызовы работаю как надо, и даже не потребует сделать приведение объектов при использовании одного из элементов массива, если мы захотим вызвать какое-то свойство BaseDuck.

    Ну и напоследок создадим список объектов, используя прекрасный питоновский синтаксис и вновь проитерируем по нему аналогично массиву, но тут нам уже придётся указать тип объекта, потому что список увы ничего не знает о том, что помещено в него.

    Как видно, язык синтаксически очень близок к Питону, а значит писать на нём быстро и приятно.

    Плюсы и минусы

    Не претендуя на объективность, всё-же опишу плюсы и минусы Boo, которые были найдены в процессе работы.

    Плюсы
    • Быстрая компиляция, сказывается простота языка
    • Синтаксис очень близкий к Питону
    • Высокая скорость выполнения и низкие затраты памяти по сравнению с Питоном, а также возможность превратить сборку mono в нативное приложение. (mono --aot file.exe для тех, кто вдруг не знает)
    • Возможность использовать огромный codebase под .NET и Class Library
    • Возможность познакомиться с миром .NET не испытывая отвращения от перехода с любимого языка на что-то другое
    Минусы
    • Отсутствие толковой документации. Начинать изучать язык без имеющихся знаний по Питону и .NET разработке совершенно бессмыслено. При чём если питонообразный синтаксис ещё можно разобрать по примерам, то вот не имея представления о .NET специфике что-то серьезной сделать очень трудно. В мануале, половина разделов отсылает к изучению testcase'ов, вместо того чтобы внятно что-то объяснить
    • Отчасти фиксированная структура кода, нельзя сделать модуль содержащий код между определениями нескольких классов

    Ссылки
    Официальный сайт
    Поделиться публикацией
    Комментарии 29
      +4
      Вы забыли упомянуть AST-макросы в плюсах. Они чертовски хороши в Boo, почти как в Nemerle и Lisp :)
        0
        Очень вероятно что я недооцениваю этот плюс просто, вот Вы можете привести пример какой то ситуации где макросы сильно помогают в Boo?
          +2
          Решение классической проблемы с INotifyPropertyChanged
          http://tore.vestues.no/2008/12/18/boo-astattributes-explained/
          [NotifyPropertyChanged]
          class BooCustomer:
              [Property(LastName)]
              lastName as String
              [Property(FirstName)]
              firstName as String
          


            0
            Решается через АОР.
              0
              А я бы сказал наоборот — AOP решается через AST-макросы :)
                +1
                Можно и так. Разница лишь в том, что для AST-макросов придется обучать разработчиков Воо и использовать SharpDevelop. Для АОР, достаточно установить PostSharp.
                  +1
                  Обучать разработчиков надо для всего. Laos они тоже не сразу воткнут, я уверен :) А для Boo не обязательно ставить SD — есть BooLangStudio для > VS2005: синтаксис, все дела.

                  А, и еще — Boo компилится пару секунд максимум. Laos и PostSharp — аццки долго. Boo компилится из командной строки; Laos — надо настраивать envirnonment, прописывать таски.

                  Как обычно — на вкус и цвет фломастеры разные, есть плюсы, есть минусы.
                    +1
                    А вы пробовали использовать BooLangStudio для метапрограммирования? Вас не беспокоит то, что BLS безнадежна отстала от выпусков самого Воо и что львиная доля нового синтаксиса, который представлен в документации Воо попросту не будет компилироваться? ИМХО, BooLangStudio пользоваться нереально — нужно брать SharpDevelop 3.1. Или компилить с командной строки.

                    Что до лаоса, он компилился аццки медленно в версии 1.0. Сейчас получше, но спорить не буду — действительно замедляет работу. Наверное мы уже привыкли :)
                      0
                      Не драматизируйте :) Для тех целей, что есть у меня, BooLangStudio хватает с головой (это DSL + ессно, немного магии метапрограммирования). Насчет не компилируется — всегда можно заменить booc на более свежую версию. Я даже пересобирал его с правленных исходников, потому что пришлось исправлять кое-какие баги в компиляторе.

                      Laos — хороший инструмент среди других; просто иногда есть выбор, и это хорошо (=.

            0
            Например, в Boo нет ключевого слова using как в CSharp — эта возможность реализована при помощи макроса
              +1
              Помогают там где сложность метапрограммирования становится меньше, чем сложность писания своего АОР-weaver'а. Например если нужно заменить все поля в классе на свойства, которые проверяют на self-assignment, реализуют интерфейсы INotifyPropertyChanged и INotifyPropertyChanging для цепочных зависимостей, да еще каждый порождает свой event изменения на который тоже можно подписаться.

              Вообщем, AST-макросы полезны для серьезной кодогенерации этапа компилирования.
            +1
            еще есть Cobra, диалект Python с массой вкусного, тоже для .NET
            все никак не доходят руки написать про него статью
              0
              Угу, тоже очень интересный язык, но у него синтаксис всё-же местами подальше от Питона.
                0
                По cobra еще меньше документации, чем по C# :( А судя по той, что доступна, язык еще даже не альфа
                  0
                  >> чем по C#
                  чем по Boo, конечно.
                0
                Под windows можно использовать SharpDevelop который поддерживает создание проектов под Boo.
                  0
                  MonoDevelop и под Windows прекрано работает. Он, имхо, удобнее.
                    +1
                    не ради холивара, все равно я пишу в MVS, но я не поленился :) поставил MonoDevelop, особых удобств по сравнению с SD не заметил.
                  –9
                  .NET не нужен!
                    0
                    Вот и не пользуйтесь
                      0
                      это тролль из числа июлят, выполз и сдох, болезный
                    +1
                    Я бы с удовольствием почитал про их макросы и отличия от Nemerle.
                      0
                      Простите, что «приколупался» к статье, забыл упомянуть еще один плюс Boo — модульный компилятор: т.е. вы набираете из модулей compilation pipeline (конвейер компиляции, можно подключить и свои модули), а затем запускаете, и получаете результат. Собственные модули во время компиляции имеют доступ к AST программы и могут его трансформировать на благо Родины.

                      Это очень удобно использовать для реализации собственный DSLей в .net, благо синтаксис позволяет.
                      Достаточно подробно об этом пишет вот этот замечательный дядька и соавтор NHibernate — Oren Eini (Ayende): http://ayende.com/blog/
                        0
                        Спасибо за дополнения.
                          0
                          Да, книжка неплохая кстате (я про Domain-Specific Languages with Boo), хотя стиль уж больно неформальный.
                          0
                          Очень интересно, заинтересовало, надо поближе познакомиться с Boo.Жду новх статей по нему :)
                            0
                            Спасибо, статьи будут.
                            0
                            Скажите, почему вы не сделали виртуальной функцию ToString?
                              0
                              Я, боюсь не смогу Вам ответить полностью корректно, но ToString особая функция, присущая потенциально каждому классу, либо Boo обрабатывает её вне контекста модификаторов прав доступа (делать ToString приватной функцией как минимум странно), либо по умочанию назначает свойства virtual и override

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое