newsmode
search
Меню
arrow_back Назад

programming languages – Hamel’s Blog - Hamel Husain

auto_awesomeКраткое саммари

Хамел Хусейн делится главными выводами после прохождения трёхчастного курса Coursera «Programming Languages» Дэна Гроссмана; код его (приватного) репозитория лежит на GitHub. В части A изучается Standard ML (SML) — статически типизированный язык с мощным выводом типов, рекурсией вместо циклов, сопоставлением с образцом через case, замыканиями, хвостовой рекурсией, каррированием и композицией функций. Часть B посвящена Racket (родственнику Lisp и Scheme) с динамической типизацией, thunk'ами, ленивыми вычислениями, макросами и написанием собственного маленького интерпретатора. В части C рассматривается Ruby — чисто объектно-ориентированный язык с динамической типизацией, блоками, yield, лямбдами и наследованием. Отдельно автор описывает настройку Vim как IDE для SML с плагином jez/vim-better-sml и конфигурацией .vimrc.

Главные выводы после прохождения трёхчастного курса Coursera «Programming Languages» с Дэном Гроссманом.

Ваш (приватный) GitHub-репозиторий для этого курса находится здесь.

SML (Standard ML), часть A

Вы настроили vim как IDE для этого. См. заметки в разделе VIM ниже. ML — статически типизированный язык с волшебным выводом типов, который работает действительно хорошо. Он автоматически определяет типы и очень интуитивен и полезен. Научились использовать рекурсию повсюду вместо циклов, в частности с hd, tl и cons. Очень важно локальное связывание переменных через let (которое также позволяет связывать локальные/приватные функции). cons позволяет добавлять элемент в начало списка. Есть тип option, который равен NONE или SOME v. Этот язык не поощряет мутацию, и это его достоинство. В противном случае можно использовать ссылку, которая подобна указателю, для мутации переменной. Сопоставление с образцом через выражение case: это одна из самых классных вещей, которые я узнал, и нечто похожее появляется в Python 3.10. Можно использовать вложенные образцы. Можно сопоставлять с образцом аргументы функций, что даёт очень приятный синтаксис для реализации функциональности вроде множественной диспетчеризации.. (насчёт python не уверен). Можно сопоставлять с образцом как типы, так и структуры данных. Можно использовать там и константы. case name NameType name => ... | (first, "MyLastName") => ... | (first, last) => ... | name => ... | _ => ... Хвостовая рекурсия с аккумуляторами. Пример — факториал. Ключевое слово fn используется для определения анонимных функций. ML использует лексическую область видимости, что означает, что функция вычисляется в окружении, где она была определена. динамическая область видимости, которая обычно нежелательна, — это альтернатива, при которой функция вычисляется в окружении, где она вызывается. Замыкание — в стеке вызовов есть «пара», представляющая собой (функцию, окружение на момент определения функции). Эта пара называется замыканием. Стек вызовов содержит снимок того, как выглядело окружение в момент определения функции. fold похож на reduce. ML поддерживает композицию функций так, с ключевым словом o: f1 o f2 o f3. Лучше сделать val-связывание, чтобы избежать ненужной обёртки: val newfunc = f1 o f2. С o функции применяются справа налево, так что f1 o f2 x — это то же самое, что f1(f2(x)). Есть альтернатива, применяющая слева направо, называемая оператором конвейера (pipeline operator). Каррирование и частичное применение. Универсальный способ сделать функцию каррируемой: ml fun myfunc x let fun f2 (z) = z fun f1 (y) = f2(y) begin f1 end. ML имеет первоклассную поддержку каррирования, так что прибегать к описанному выше трюку не нужно. ML поддерживает взаимную рекурсию так же, как let-rec в racket.

Racket (часть B)

Racket родственен Lisp и Scheme. Всё является функцией. Скобки для всего. Положение скобок меняет смысл кода.

В Racket динамическая типизация, в отличие от SML. Thunk'и: оборачивают функцию в функцию без аргументов, чтобы отложить вычисление. Применения: Потоки (Streams): функция возвращает кортеж (значение, функция), и когда вы вызываете функцию, она возвращает (значение, функция), так что вы получаете по одному значению за раз. Это не специфично для Racket. Ленивые вычисления: можно использовать thunk'и, чтобы отложить выполнение, как обещание, на более поздний момент. Вот пример ленивого вычисления, которое фактически ничего не вычисляет, пока его к этому не принудят:

(define (my-delay f) (mcons #f f)) (define (my-force th) (if (mcar th) (mcdr th) (begin (set-mcar! th #t) (set-mcdr! th ((mcdr th))) (mcdr th))))

Racket позволяет использовать макросы, которые вычисляются до запуска кода и «разворачиваются» в валидный синтаксис racket.

Вы реализовали собственный небольшой язык программирования. Здесь использовались рекурсивные вызовы для вычисления выражений, где базовым случаем являются значения (целые числа, строки и т. д.). - Интерпретатор: написать на другом языке A программу, которая принимает программы на B и сразу выдаёт ответы. Более точным термином было бы «вычислитель» (evaluator). - Компилятор: написать на другом языке A программу, которая принимает программы на B и выдаёт эквивалентную программу на языке C. Более точным термином здесь было бы «транслятор» (translator).

Замыкания: для лексической области видимости интерпретатор имеет стек кортежей. Кортежи — это (1) вызываемая функция (2) окружение, которое содержит значения всех переменных на момент определения функции. Также нужно отслеживать аргументы функции отдельно, чтобы можно было вычислять их в окружении, в котором функция была запущена.

Ruby (часть C)

Я не потратил слишком много времени, с некоторыми концепциями я был в основном знаком.

Ruby — объектно-ориентированный язык с динамической типизацией. Ruby — это чистый ООП, даже функции и переменные верхнего уровня являются частью встроенного класса Object. В нём есть короткие способы вроде fastcore для геттеров и сеттеров:

attr_reader :y, :z # определяет геттеры attr_accessor :x # определяет геттеры и сеттеры

Переводы строк важны. Без них синтаксис может измениться.

Динамические определения классов. Следующий код приведёт к классу Class с методами foo и bar! Второй не переопределяет первый!

class Class def foo ... end end class Class def bar ... end end

Блоки (Blocks)

В нём также есть очень удобная штука, похожая на лямбды, под названием Blocks:

sum = 0 [4,6,8].each { |x| sum += x puts sum }

Можно использовать Blocks и для создания аккумуляторов, и даже использовать inject для инициализации аккумулятора:

sum = [4,6,8].inject(0) { |acc,elt| acc + elt }

Чтобы использовать блоки внутри метода, придётся поискать это в документации. Это связано с ключевым словом yield. Например, этот код напечатает «hi» 3 раза:

def foo x if x yield else yield yield end end foo (true) { puts "hi" } foo (false) { puts "hi" }

Blocks не являются первоклассными функциями, хотя и выглядят немного как лямбды. Допустим, вы хотите применить map к массиву, но хотите вернуть массив функций вместо значений. Способ сделать это — использовать ключевое слово lambda:

c = a.map {|x| {|y| x >= y} } # неверно, синтаксическая ошибка c = a.map {|x| lambda {|y| x >= y} } # это сработает

Наследование (Subclassing)

super вызывает тот же метод в родительском классе. Не нужно писать super.method_name(), достаточно просто super. Переменные экземпляра предваряются символом @

Дочерние классы определяются так:

class Child < Parent ... end

Типизация

Обсуждались различные способы построения систем типов. Была представлена идиома интерфейсов, знакомая вам по Golang (но не специфичная для Golang).


VIM

Для языка программирования Standard ML я решил заставить себя использовать vim. Я добавил следующие вещи в свой .vimrc, чтобы сделать работу управляемой. Обратите внимание на плагин jez/vim-better-sml

" from https://github.com/jez/vim-as-an-ide set nocompatible inoremap A filetype off set rtp+=~/.vim/bundle/Vundle.vim call vundle#begin() Plugin 'VundleVim/Vundle.vim' " ----- Making Vim look good ------------------------------------------ Plugin 'altercation/vim-colors-solarized' Plugin 'tomasr/molokai' Plugin 'vim-airline/vim-airline' Plugin 'vim-airline/vim-airline-themes' " ----- Vim as a programmer's text editor ----------------------------- Plugin 'scrooloose/nerdtree' Plugin 'jistr/vim-nerdtree-tabs' Plugin 'vim-syntastic/syntastic' Plugin 'xolox/vim-misc' Plugin 'xolox/vim-easytags' Plugin 'majutsushi/tagbar' Plugin 'ctrlpvim/ctrlp.vim' " ----- Working with Git ---------------------------------------------- Plugin 'airblade/vim-gitgutter' Plugin 'tpope/vim-fugitive' Plugin 'Raimondi/delimitMate' Plugin 'jez/vim-better-sml' Plugin 'christoomey/vim-tmux-navigator' Plugin 'benmills/vimux' call vundle#end() filetype plugin indent on set number set ruler set showcmd set incsearch set hlsearch set backspace=indent,eol,start syntax on set mouse=a