image
Источник


Ральф Джонсон, один из членов "Банды четырёх", однажды показал, как синтаксис языка Smalltalk-80 можно уместить на почтовой открытке. Сейчас, спустя почти 30 лет после появления первой версии Smalltalk, самым быстроразвивающимся диалектом Smalltalk является Pharo, почтовую открытку которого далее и разберём.


Объявление метода


exampleWithNumber: x

Методы объявляются в виде имяМетода: имяПараметра и являются членами класса. Метод с несколькими параметрами объявляется так


rangeFrom: start to: end

Имя метода здесь rangeFrom:to:, а имена параметров — start и end.


Декларация pragma


<syntaxOn: #postcard>

Pragma используется для аннотации метода. Эта аннотация может использоваться компилятором или другими методами как метаданные.


Комментарии


"A ""complete"" Pharo syntax"

Комментарии обозначаются кавычками. Кавычки внутри комментария обозначаются двойными кавычками.


Объявление локальной переменной


| y |

Локальные переменные используются в вычислениях внутри метода. Объявления типа переменной не требуется, поскольку Smalltalk — динамически типизированный язык.


Несколько переменных объявляются одним списком


| y x totalSum |

Объекты и сообщения


true & false not & (nil isNil)

Всё в Smalltalk является объектами, а объекты могут принимать сообщения. Порядок выполнения (то есть — отправки сообщений) — слева направо, но сообщения без параметров отправляются в первую очередь в соответствии с правилами приоритета, так что порядок вычисления здесь будет


(true & (false not)) & (nil isNil)

Правил приоритета в Smalltalk всего четыре: первыми отправляются сообщения в скобках, затем — унарные (без дополнительных параметров помимо самого объекта-получателя, например false not), затем — бинарные (с одним дополнительным параметром, например 1 + 2), затем — сообщения с несколькими параметрами (например 15 between: 1 and: 2). Приоритетность выполнения обозначается простой схемой


скобки > унарные > бинарные > сообщения с несколькими параметрами


Эти правила действуют и для математических операций, так что результатом выполнения выражения


1 + 15 / 4   " = (1 + 15) / 4 "

будет 4. Кстати nil также является объектом и может принимать и отвечать на сообщения.


Условное выполнение и блоки кода


true & false not & (nil isNil) 
  ifFalse: [ self perform: #add: with: x ].

Условное выполнение реализуется отправкой сообщений ifTrue, ifFalse объекту-логическому значению. Аргументом этого сообщения является блок кода, обозначаемый квадратными скобками, который выполняется, если выполняется заданное условие.


Блоки Smalltalk также используются как анонимные функции:


sum := [ :x :y | x+y ].    " Блок x,y -> x+y "

sum value: 10 value: 25.   " Вычисление блока, результат - 35"

Отправка сообщений самому себе


self perform: #add: with: x

Ключевое слово self используется как ссылка на содержащий метод объект при отправке сообщений самому объекту. Здесь мы отправляем сообщение perform:with: с аргументами #add и x. Знаком # обозначается строка-литерал, которая здесь используется как идентификатор метода.


Присвоение переменной


y := thisContext stack size + super size.

Присвоение переменной обозначается оператором :=. Ключевое слово super используется для обращения к объекту суперкласса.


Все объекты Smalltalk наследуют либо от класса Object, либо от своего суперкласса, который, в свою очередь, наследует от своего суперкласса или от класса Object.


Статический массив


byteArray := #[2 2r100 8r20 16rFF].

byteArray — переменная экземпляра класса, объявленная при объявлении класса. Массив byteArray состоит из целых чисел, записанных в разных системах счисления в виде


<основание>r<число> 

Размер статических массивов фиксирован и задаётся во время компиляции. Индексация массивов начинается с 1


byteArray at: 2   " = 2r100 "

С самого начала Smalltalk был не только языком, но и интегрированной средой разработки со своей виртуальной машиной: классы и методы Smalltalk не хранятся в отдельных текстовых файлах, а сразу сохраняются в образе виртуальной машины и объявляются через интерфейс среды разработки. Например, класс Counter объявляется в разделе классов как


Object subclass: #Counter
       instanceVariableNames: ’count initialValue’
       classVariableNames: ’’
       package: ’MyCounter’  

а его методы объявляются в разделе методов класса Counter.


Динамический массив


{ -42 . #($a #a #'I''m' 'a' 1.0 1.23e2 3.14s2 1) }

Динамический массив создается во время выполнения программы.
Массивы могут содержать данные разных типов: первый элемент массива — число -42, второй элемент массива — массив с элементами разных типов:


  • $a — символ "a"
  • #a #'I''m' — строки-литералы "a" и "I'm"
  • 'a' — строка "a"
  • 1.0 1.23e2 — числа с плавающей точкой
  • 3.14s2 — десятичная дробь с масштабом 2

Циклы


{ -42 . #($a #a #'I''m' 'a' 1.0 1.23e2 3.14s2 1) }
  do: [ :each | 
    | var |
    var := Transcript 
      show: each class name;
      show: each printString ].

Циклы в Smalltalk реализуются отправкой сообщения массиву с блоком, который будет выполняться на каждом элементе этого массива, что очень похоже на функциональный подход. На каждой итерации элемент массива передаётся как аргумент блоку, производящему над ним какие-то вычисления. В блоке в примере объявляется локальная переменная var, которой присвается результат отправки последнего сообщения show глобальному объекту Transcript.


Интересной фишкой Smalltalk является возможность каскадирования сообщений: выражение


Transcript show: 'A'; show: 'B'.

последовательно выводит строки A и B в окно консоли Transcript. Это эквивалентно коду


Transcript show: 'A'.
Transcript show: 'B'. 

но позволяет избежать повторения имени объекта-получателя сообщений Transcript. Результатом каскадирования является ответ объекта на последнее сообщение.


Возврат значения из метода


^ x < y

Возврат значения обозначается символом ^. В данном случае возвращается логическое значение — результат сравнения x < y.

Комментарии (18)


  1. vlad9486
    03.04.2019 11:52

    Объявления типа переменной не требуется, поскольку Smalltalk — динамически типизированный язык.
    Но ведь в статически типизированных тоже не требуется. Даже больше того. В некоторых языках можно объявлять переменную не назначая ей значение, получится неинициализированная переменная (в си так можно). Так вот в динамически типизированных объявление переменной без типа — аналог объявления переменной без значения в статически типизированных, что не очень хорошо. В статически типизированных такой проблемы нет, если не указать тип, то он все равно будет назначен статически.


    1. ARad
      03.04.2019 13:19
      +1

      В смолтолке вообще нет синтаксиса для объявления у переменной типа. Автор видать это хотел сказать.


      1. chaetal
        03.04.2019 21:48

        Именно это автор и сказал.


    1. chaetal
      03.04.2019 21:46

      Фраза, к которой вы зачем-то попытались придраться, абсолютно верная: динамическая типизация => объявлять тип переменных не нужно.
      Наличие вывода типов (вы, возможно это имели ввиду, но забыли уточнить?) в некоторых статически типизированных языках (что делает необязательным явно указывать типы) никоим образом не делает исходное утверждение неверным. Вам бы логику подучить? ;)

      «Даже больше того». Дальнейший текст не имеет вообще никакого смысла: несмотря на то, что тип переменной в Smalltalk не указывается, она всегда инициализируется конкретным значением, и проблемы неинициализированных переменных здесь просто нет.
      «Так вот в динамически типизированных объявление переменной без типа» — ничего общего с объявление переменной без значения в статически типизированных не имеет.


      1. vlad9486
        03.04.2019 22:16

        Если есть значение, значит и тип точно есть. Динамическая типизация означает что тип может меняться во время выполнения, но не то что значение может быть вообще без типа.

        несмотря на то, что тип переменной в Smalltalk не указывается, она всегда инициализируется конкретным значением
        Значит и тип всегда при инициализации есть конкретный. Значит там такой же вывод типов. То что вывод типов сначала появился в динамически типизированных языках не значит что не указывать типы можно по причине динамической типизации.
        Вывод типов => объявлять тип переменных не нужно. Динамическая или статическая типизация — не важно.


        1. chaetal
          03.04.2019 23:07

          Теперь я понял цель исходного комментария — вы хотите разобраться с типами? ;)

          Если есть значение, значит и тип точно есть. Динамическая типизация означает что тип может меняться во время выполнения, но не то что значение может быть вообще без типа.

          Рад, что вы это понимаете (с некоторыми оговорками — см. ниже).
          Динамическая типизация в Smalltalk — строгая типизация. Кстати, статическая типизация не обязательно влечет за собой строгую (в качестве примера можете взять упомянутый вами же C).

          Значит и тип всегда при инициализации есть конкретный.

          В каждый момент времени в переменной лежит конкретное значение — ему можно приписать некий «тип». Только надо бы понять, что такое «тип». Оставим это (пока) на самостоятельное изучение?

          Значит там такой же вывод типов.

          Вывод типов => объявлять тип переменных не нужно. Динамическая или статическая типизация — не важно.

          Никакого вывода типов в языках с динамической типизацией нет. Вывод типов — «фишка» языков со статической типизацией, позволяющая системе на основе анализа кода понять тип переменной на этапе компиляции. Просто чтобы пользователю не приходилось заниматься этой утомительной работой.

          В динамических языках проверка типа осуществляется в момент выполнения. В том же Smalltalk-е, в частности, это означает, что объект, получив сообщение, либо найдет в классе, экземпляром которого данный объект являтся, (или в классе выше по иерархии) соответсвующий метод, либо выкинет исключение пошлет себе специальное сообщение #doesNotUnderstand.

          На самом деле, надо понимать, что (по крайней мере) в исходном понимания ООП типы не предусмотрены, и мы с определенной долей условности «проецируем» позднее связывание на понятие типизации. А на самом деле, в Smalltalk таки
          значение может быть вообще без типа
          И даже не «может быть», а всегда без типа! Но при этом, Smalltalk — язык со строгой динамической типизацией. :)


          1. vlad9486
            03.04.2019 23:13

            Да, я это понимаю. Почти со всем согласен кроме

            И даже не «может быть», а всегда без типа!
            Тогда типизация бы называлась «никакая». Но она называется «строгая статическая». Там всегда тип есть. Просто нету соответствия между типом и конкретным местом кода.


            1. chaetal
              03.04.2019 23:25

              Тогда типизация бы называлась «никакая».
              Типизация была бы «никакая», если бы объект можно было заставить выполнить недопустимую операцию.
              Но она называется «строгая статическая».
              Она называется «строгая динамическая» — потому что заставить выполнить «недопустимую операцию» невозможно.
              Там всегда тип есть.
              Типа нет! Я выше объяснил, почему. Не верите — загрузите Pharo (или любую другую реализацию Smalltalk) и убедитесь лично ;) …ну, или найдите его там


              1. vlad9486
                04.04.2019 11:40

                То была описка (статическая динамическая). А вы объяснили что

                В динамических языках проверка типа осуществляется в момент выполнения.
                Это я и так знал. Странно что осуществляется проверка того чего нет.


                1. chaetal
                  04.04.2019 13:26

                  > Странно что осуществляется проверка того чего нет.
                  Чтобы не было странно надо (а) понять, что такое тип и (б) взять слова «проверка типа» в кавычки: в том же Smalltalk-е проверки типов как таковой нет; она просто не нужна, так как (строгая динамическая) «типизация» заложена в основополагающий для ООП принцип посылки сообщений и в Smalltalk-е реализована вполне себе надежным способом.


                  1. vlad9486
                    04.04.2019 13:33

                    Тип — это умозрительная вещь. Не обязательно в коде компилятора (интерпретатора) должны быть явно структуры данных отвечающие за тип что-бы считать что компилятор (интерпретатор) работает с типом. Обычно в машинном коде не остается и следов типов. А в действиях интерпретатора может не быть явных проверок если он сможет доказать что они не нужны. Типы данных появляются «на бумаге», когда мы (люди) анализируем язык.
                    Вот так сложилось что понятие «тип» используют при рассуждениях о языках программирования, даже если интерпретатор не использует.


                    1. chaetal
                      04.04.2019 14:31

                      Тип — это умозрительная вещь.
                      Когда компилятор статически типизированного языка отказывается компилировать вашу программу из-за несовместимости типов или программа на динамически типизированном языке выкидывает исключение по той же причине —  в чей ум она обращается? :)

                      Если типы есть, они вполне материальны — они присутствуют на различных этапах жизни программы: где-то только в момент компиляции, где-то и в рантайме…

                      Однако, похоже, мою мысль про отсутствие типов в Smalltalk-е при наличии «типизации» надо пояснить. В качестве контр-примера возьмем Java. Там типом объекта (примитивные типы не рассматриваем) является его класс. Часть контроля типов выполняется в момент компиляции (статически). Но, поскольку типы в Java «кривые» (если сравнивать с наследниками ML), приходится допускать возможность «обходить» контроль типов и система вынуждена часть проверок выполнять в run-time. И это именно проверка типа: если я хочу послать некоторому объекту сообщение X(), система сначала должна убедиться, что я «имею право это сделать» (метод реализован в классе объекта или в родительском классе), если нет — имеем исключение. То есть, типы есть, они материальны, более того, они присутствуют даже в рантайм.

                      В Smalltalk-е типов нет, даже класс объекта не задает его тип: я могу послать абсолютно любое сообщение любому объекту. Звучит как полное отсуствие типизации? А вот нет! Механизм связывания сообщений с методами обеспечивает что-то вроде «проверки типов» (при фактическом отсуствии типов на всех этапах и в каком бы то ни было виде — даже в «умозрительном»): система обращается к классу объекта, просит найти соответствующий сообщению метод; если не находит, делает тоже с родительским классом… пока не упирается в корень иерархии; если это произошло, объекту посылается сообщение #doesNotUnderstand (которое, как минимум, реализовано в классе Object, и, следовательно, не приведет к крушению системы), обеспечивающее нужную обработку данной ситуации. То есть, типов нет, проверки типов нет, а поведение полностью аналогичное строгой динамической типизации есть!


                      1. vlad9486
                        04.04.2019 14:38
                        +2

                        Да, Smalltalk — необычный язык и говорить о его типах нужно осторожнее. Я понял вас, так тоже можно считать.


            1. OLDRihard
              04.04.2019 11:44

              Я сделаю проще. Вот: habr.com/ru/post/161205


              1. vlad9486
                04.04.2019 13:09

                Я это читал еще давным давно. Просто у меня странное (может даже иррациональное) желание сказать что тип можно не указывать не потому что динамическая типизация. Хотя да, из-за динамической типизации теряется связь между местом в коде и типом, значит даже при желании тип не получится явно указывать. Я просто мало программировал на таких языках, только на питоне чуть-чуть и то для работы, а не по собственному желанию.


                1. chaetal
                  04.04.2019 13:37

                  В общем случае тип можно не указывать по разным причинам:
                  — за вас это может сделать кто-то другой (вывод типов)
                  — они будут определены и проверены автоматически в момент выполнения программы
                  — их просто нет
                  (Последнее не означает автоматически, что нет типизации. Но и наличие типов не гарантирует сильную/строгую типизацию.)


  1. chaetal
    04.04.2019 13:36

    <перемещено в правильное место>


  1. potan
    05.04.2019 09:18

    На чем бы уместить синтаксис Lisp…