programming languages
High level takeaways after completing the 3-Part Coursera class Programming Languages with Dan Grossman.
Your GitHub repo for this class (private) is here.
SML (Standard ML) Part A
- You setup vim to have an IDE for this. See notes in the VIM section below.
- ML is a statically typed language with magical type inference that works really well. It automatically determines the types and is very intuitive and helpful.
- Learned how to use recursion everywhere instead of loops, particularly with
hd
,tl
andcons
. - Local variable binding with
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 list- There is an option type that is
NONE
orSOME v
- This language doesn’t encourage mutation, which is a feature. Otherwise, you can use a reference which is like a pointer to mutate a variable.
- pattern matching with a case expression: This is one of the coolest things that I learned, and something similar is coming to Python v 3.10.
- 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 => ..."MyLastName") => ... | (first, | (first, last) => ... | name => ... | _ => ...
- Tail recursion with accumulators. Ex- factorial
- The
fn
keyword is used to define anonymous functions. - ML uses
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 “pair” that is the (function, environment when the function was defined). This pair is called the
closure
. The call stack has a snapshot of what the environment looked like at the time the function was defined. fold
is like reduce.- ML supports function composition like this with the keyword
o
:f1 o f2 o f3
- best to do a val binding to avoid unnecessary wrapping:
val newfunc = f1 o f2
- with
o
you apply functions from right to left sof1 o f2 x
is the same asf1(f2(x))
there is an alternative that is left to right called thepipeline operator
.
- best to do a val binding to avoid unnecessary wrapping:
- Currying and partial application
- 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.
- Universal way to make a func curryable:
- ML supports mutual recursion just like
let-rec
in racket.
Racket (Part 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 has dynamic typing, unlike SML.
- Thunks: Wrap a function in a zero argument function to delay evaluation. Applications:
- 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:
(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.
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”.
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.
Ruby (Part 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:
attr_reader :y, :z # defines getters
attr_accessor :x # defines getters and setters
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 Class
def foo
...
end
end
class Class
def bar
...
end
end
Blocks
They also have a very convenient lambda like thing called Blocks
:
= 0
sum [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:
= [4,6,8].inject(0) { |acc,elt| acc + elt } sum
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:
def foo x
if x
yield
else
yield
yield
end
end
true) { puts "hi" }
foo (false) { puts "hi" } foo (
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
:
= a.map {|x| {|y| x >= y} } # wrong, a syntax error
c = a.map {|x| lambda {|y| x >= y} } # this will work c
Subclassing
super
calls the same method in the parent class. You dont have to do super.method_name(), justsuper
.- Instance variables are preceeded with
@
Child classes are defined like this:
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.
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
" 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