programming languages – Hamel’s Blog - Hamel Husain
Хамел Хусейн делится главными выводами после прохождения трёхчастного курса 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.
High level takeaways after completing the 3-Part Coursera class Programming Languages with Dan Grossman.
Главные выводы после прохождения трёхчастного курса Coursera «Programming Languages» с Дэном Гроссманом.
Your GitHub repo for this class (private) is here.
Ваш (приватный) GitHub-репозиторий для этого курса находится здесь.
SML (Standard ML) Part A
SML (Standard ML), часть A
hd, tl and cons.let is very important (which also allows you to bind local/private functions as well)cons allows you to append to the beginning of a listNONE or SOME v- You can have nested patterns
- You can pattern match against function arguments which allow for really nice syntax for achieving multiple dispatch type of functionality.. (not sure about python)
- You can pattern match against types as well as data structures.
- You can have constants in there as well.
case name NameType name => ... | (first, "MyLastName") => ... | (first, last) => ... | name => ... | _ => ...fn keyword is used to define anonymous functions.lexical scope which means function is evaluated in the environment where the function was defined. dynamic scope, which is usually not desired, is the alternative where the function is evaluated in the in the environment it is called.closure. The call stack has a snapshot of what the environment looked like at the time the function was defined.fold is like reduce.o: f1 o f2 o f3 - best to do a val binding to avoid unnecessary wrapping:
val newfunc = f1 o f2 - with
oyou apply functions from right to left sof1 o f2 xis the same asf1(f2(x))there is an alternative that is left to right called thepipeline operator.
- Universal way to make a func curryable:
ml fun myfunc x let fun f2 (z) = z fun f1 (y) = f2(y) begin f1 end - ML has first class support for currying so you don’t have to do the above hack.
let-rec in racket.Вы настроили 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 (Part B)
Racket (часть B)
Racket is related to Lisp and Scheme. Everything is a function. Parenthesis for everything. The position of parenthesis changes the meaning of the code.
Racket родственен Lisp и Scheme. Всё является функцией. Скобки для всего. Положение скобок меняет смысл кода.
- Streams: the function will return a tuple of (value, func), and when you call func it will return (value, func) so you get one value at a time. This is not specific to Racket.
- Lazy evaluation: You can use thunks to delay execution like a promise to a later time. This is an example of lazy evalution that doesn’t actually evaluate anything until being forced to:
В 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))))
(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 allows you use macros that will evaluate before the code is run and that will “expand” into valid racket syntax.
Racket позволяет использовать макросы, которые вычисляются до запуска кода и «разворачиваются» в валидный синтаксис racket.
You implemented your own small programming language. This used recursive calls to evluate expressions with the base case being the values (Integer, strings, etc). - Interperter: write a program in another language A that takes programs in B and produces answers directly. A better term would be “evaluator”. - Compiler: write program in another language A that takes programs in B and produces an equivalent program in langauage C. A better term here would be “translator”.
Вы реализовали собственный небольшой язык программирования. Здесь использовались рекурсивные вызовы для вычисления выражений, где базовым случаем являются значения (целые числа, строки и т. д.). - Интерпретатор: написать на другом языке A программу, которая принимает программы на B и сразу выдаёт ответы. Более точным термином было бы «вычислитель» (evaluator). - Компилятор: написать на другом языке A программу, которая принимает программы на B и выдаёт эквивалентную программу на языке C. Более точным термином здесь было бы «транслятор» (translator).
Closures: for lexical scope, the interpreter has a stack of tuples. The tuples are (1) the function to be called (2) the environment, which contains the value of all variables at the time the function was defined. You also have to track the arguments for the function seperately, so you can evaluate the arguments in the environment the function was run in.
Замыкания: для лексической области видимости интерпретатор имеет стек кортежей. Кортежи — это (1) вызываемая функция (2) окружение, которое содержит значения всех переменных на момент определения функции. Также нужно отслеживать аргументы функции отдельно, чтобы можно было вычислять их в окружении, в котором функция была запущена.
Ruby (Part C)
Ruby (часть C)
I didn’t spend too much time some concepts I was mostly familiar with this.
Я не потратил слишком много времени, с некоторыми концепциями я был в основном знаком.
Ruby is OOP, dynamically typed.
Ruby is pure OOP, even top level functions and variables are part of the built-in Object class.
They have fastcore like shortcuts for getters and setters:
Ruby — объектно-ориентированный язык с динамической типизацией. Ruby — это чистый ООП, даже функции и переменные верхнего уровня являются частью встроенного класса Object. В нём есть короткие способы вроде fastcore для геттеров и сеттеров:
attr_reader :y, :z # defines getters attr_accessor :x # defines getters and setters
attr_reader :y, :z # определяет геттеры attr_accessor :x # определяет геттеры и сеттеры
newlines are important. The syntax can change without them.
Переводы строк важны. Без них синтаксис может измениться.
Dynamic class definitions. The following code will result in Class with the methods foo and bar! The second one doesn’t override the first one!
Динамические определения классов. Следующий код приведёт к классу Class с методами foo и bar! Второй не переопределяет первый!
class Class def foo ... end end class Class def bar ... end end
class Class def foo ... end end class Class def bar ... end end
Blocks
Блоки (Blocks)
They also have a very convenient lambda like thing called Blocks:
В нём также есть очень удобная штука, похожая на лямбды, под названием Blocks:
sum = 0 [4,6,8].each { |x| sum += x puts sum }
sum = 0 [4,6,8].each { |x| sum += x puts sum }
You can use Blocks to make accumulators too, and even use inject to initialize the accumulator:
Можно использовать Blocks и для создания аккумуляторов, и даже использовать inject для инициализации аккумулятора:
sum = [4,6,8].inject(0) { |acc,elt| acc + elt }
sum = [4,6,8].inject(0) { |acc,elt| acc + elt }
To use blocks in a method, you will have to look that up in the docs. This involves the yield keyword. For example, this code will print “hi” 3 times:
Чтобы использовать блоки внутри метода, придётся поискать это в документации. Это связано с ключевым словом yield. Например, этот код напечатает «hi» 3 раза:
def foo x if x yield else yield yield end end foo (true) { puts "hi" } foo (false) { puts "hi" }
def foo x if x yield else yield yield end end foo (true) { puts "hi" } foo (false) { puts "hi" }
Blocks are not first class functions even though they kind of look like lambdas. Lets say you wanted to map over an array but wanted to return an array of functions instead of values. The way to do this is to use the keyword lambda:
Blocks не являются первоклассными функциями, хотя и выглядят немного как лямбды. Допустим, вы хотите применить map к массиву, но хотите вернуть массив функций вместо значений. Способ сделать это — использовать ключевое слово lambda:
c = a.map {|x| {|y| x >= y} } # wrong, a syntax error c = a.map {|x| lambda {|y| x >= y} } # this will work
c = a.map {|x| {|y| x >= y} } # неверно, синтаксическая ошибка c = a.map {|x| lambda {|y| x >= y} } # это сработает
Subclassing
Наследование (Subclassing)
super calls the same method in the parent class. You dont have to do super.method_name(), just super.@super вызывает тот же метод в родительском классе. Не нужно писать super.method_name(), достаточно просто super. Переменные экземпляра предваряются символом @
Child classes are defined like this:
Дочерние классы определяются так:
class Child < Parent ... end
class Child < Parent ... end
Typing
Типизация
They discussed the various ways different type systems are constructed. The interface idiom, that is familar to you from Golang (but not specific to Golang) was introduced.
Обсуждались различные способы построения систем типов. Была представлена идиома интерфейсов, знакомая вам по Golang (но не специфичная для Golang).
VIM
VIM
For the Standard ML programming language I decided to force myself to use vim. I added the following things to my .vimrc to make it manageable. Note the plugin jez/vim-better-sml
Для языка программирования Standard ML я решил заставить себя использовать vim. Я добавил следующие вещи в свой .vimrc, чтобы сделать работу управляемой. Обратите внимание на плагин jez/vim-better-sml
" from https://github.com/jez/vim-as-an-ide set nocompatible inoremap <C-e> <C-o>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
" from https://github.com/jez/vim-as-an-ide set nocompatible inoremap