Add presentation

Signed-off-by: Ramon Rüttimann <ramon@nine.ch>
master
Ramon Rüttimann 5 years ago
parent ca87271792
commit a8d61cce72

@ -0,0 +1,18 @@
FROM fgo:latest
RUN fgo get golang.org/x/tools/cmd/present
RUN ln -s $(which fgo) /go/bin/go
RUN fgo get github.com/tommyknows/funcheck
COPY . /tmp/prettyprint
RUN ls /tmp/prettyprint/prettyprint
WORKDIR /tmp/prettyprint/prettyprint
RUN fgo install .
WORKDIR /work/presentation
ENTRYPOINT ["present", "-notes", "-http", "0.0.0.0:3999", "-orighost", "127.0.0.1"]
# RUN WITH:
# docker run --rm -d --name present -p 127.0.0.1:3999:3999 -v (pwd):/work/presentation fgo-present

@ -0,0 +1,215 @@
package assigncheck
import (
"bytes"
"go/ast"
"go/printer"
"go/token"
"golang.org/x/tools/go/analysis"
)
var Analyzer = &analysis.Analyzer{
Name: "assigncheck",
Doc: "reports reassignments",
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
// function assignments, if that function was just recently
// declared, should be allowed. Anonymous functions cannot be
// called recursively if they are not in scope yet. This means
// that to call an anonymous function, the following pattern
// is always needed:
// var x func(int) string
// x = func(int) string { ... x(int) }
// To ignore that, whenever a "var x func" is encountered, we
// save that position until the next node.
var lastFuncDecl token.Pos
// START OMIT
ast.Inspect(file, func(n ast.Node) bool {
switch as := n.(type) {
case *ast.ForStmt:
pass.Reportf(as.Pos(), "internal reassignment (for loop) in %q", renderFor(pass.Fset, as))
case *ast.RangeStmt:
pass.Reportf(as.Pos(), "internal reassignment (for loop) in %q", renderRange(pass.Fset, as))
case *ast.DeclStmt:
lastFuncDecl = functionPos(as)
return false // important to return, as we'd reset the position if not
case *ast.AssignStmt:
for _, i := range exprReassigned(as, lastFuncDecl) {
pass.Reportf(as.Pos(), "reassignment of %s", render(pass.Fset, i))
}
case *ast.IncDecStmt:
pass.Reportf(as.Pos(), "inline reassignment of %s", render(pass.Fset, as.X))
}
lastFuncDecl = token.NoPos
return true
})
// END OMIT
}
return nil, nil
}
func renderFor(fset *token.FileSet, as *ast.ForStmt) string {
s := "for "
if as.Init == nil && as.Cond == nil && as.Post == nil {
return s + "{ ... }"
}
if as.Init == nil && as.Cond != nil && as.Post == nil {
return s + render(fset, as.Cond) + " { ... }"
}
if as.Init != nil {
s += render(fset, as.Init)
}
s += "; "
if as.Cond != nil {
s += render(fset, as.Cond)
}
s += "; "
if as.Post != nil {
s += render(fset, as.Post)
}
return s + " { ... }"
}
func renderRange(fset *token.FileSet, as *ast.RangeStmt) string {
s := "for "
switch {
case as.Key == nil && as.Value == nil:
// nothing
case as.Value == nil:
s += render(fset, as.Key) + " := "
case as.Key == nil:
s += "_, " + render(fset, as.Value) + " := "
default:
s += render(fset, as.Key) + ", " + render(fset, as.Value) + " := "
}
s += "range " + render(fset, as.X) + " { ... }"
return s
}
const blankIdent = "_"
// exprReassigned returns all expressions in an assignment
// that are being reassigned. This is done by checking that the
// assignment of all identifiers is at the position of the first
// identifier. If it is not an identifier, it must be a reassignment.
// There are two exceptions to this rule:
// - Blank identifiers are ignored
// - Functions may be redeclared if the assignment position is the lastFuncPos
func exprReassigned(as *ast.AssignStmt, lastFuncPos token.Pos) (reassigned []ast.Expr) {
type pos interface {
Pos() token.Pos
}
var expectedAssignPos token.Pos
for i, expr := range as.Lhs {
ident, ok := expr.(*ast.Ident)
if !ok { // if it's not an identifier, it is always reassigned.
reassigned = append(reassigned, expr)
continue
}
// we expect all assignments to be at the same position
// as the first identifier.
if expectedAssignPos == token.NoPos {
expectedAssignPos = ident.Pos()
}
// skip blank identifiers
if ident.Name == blankIdent {
continue
}
// no object probably means that the variable has been declared
// in a separate file, making this a reassignment.
if ident.Obj == nil {
reassigned = append(reassigned, ident)
continue
}
// make sure the declaration has a Pos func and get it
declPos := ident.Obj.Decl.(pos).Pos()
// if we got a function position and the corresponding
// Rhs expression is a function literal, check that the
// positions match (=same declaration)
if lastFuncPos != token.NoPos && len(as.Rhs) > i {
if _, ok := as.Rhs[i].(*ast.FuncLit); ok {
// the function is either declared right here or on the last
// position that we got from the callee.
if declPos != lastFuncPos && declPos != ident.Pos() {
reassigned = append(reassigned, ident)
}
continue
}
}
if declPos != expectedAssignPos {
reassigned = append(reassigned, ident)
}
}
return reassigned
}
// functionPos returns the position of the function
// declaration, if the DeclStmt has a function declaration
// at all. If not, token.NoPos is returned.
// At most, one position (the position of the last function
// declaration) is returned
func functionPos(as *ast.DeclStmt) token.Pos {
decl, ok := as.Decl.(*ast.GenDecl)
if !ok {
return token.NoPos
}
var pos token.Pos
// iterate over all variable specs to fetch
// the last function declaration. Skip all declarations
// that are not function literals.
for i := range decl.Specs {
val, ok := decl.Specs[i].(*ast.ValueSpec)
if !ok {
continue
}
if val.Values != nil {
continue
}
_, ok = val.Type.(*ast.FuncType)
if !ok {
continue
}
// there may not be more than one function
// declaration mapped to a single type,
// so we just return the first one.
pos = val.Names[0].Pos()
}
return pos
}
// render returns the pretty-print of the given node
func render(fset *token.FileSet, x interface{}) string {
var buf bytes.Buffer
if err := printer.Fprint(&buf, fset, x); err != nil {
panic(err)
}
return buf.String()
}

@ -0,0 +1,37 @@
package main
import (
"fmt"
"os/exec"
)
func reassignments() {
// START OMIT
a, b := 5, 1
a, c := 5, 2
// END OMIT
fmt.Println(a, b, c)
}
func main() {
cmd := exec.Command("/go/bin/prettyprint", "./code/declprint")
out, err := cmd.CombinedOutput()
fmt.Printf("%s\n", out[:findEnd(out)])
if err != nil {
fmt.Printf("%s", err)
}
}
func findEnd(b []byte) int {
var occurrence int
for i, r := range b {
if r == '\n' {
occurrence++
}
if occurrence == 10 {
return i
}
}
return -1
}

@ -0,0 +1,14 @@
package main
func main() {
// START OMIT
f := func() {
f() // invalid: f declared but not used
}
var g func()
g = func() {
g() // valid: g has been declared
}
// END OMIT
}

@ -0,0 +1,33 @@
package main
import "fmt"
// START OMIT
func main() {
p := &presentation{
slides: make([]slide, 0, 10),
author: "Ramon",
}
p.slides = append(p.slides, newIntroSlide(p.author))
fmt.Println(p)
}
type presentation struct {
slides []slide
author string
}
func (p *presentation) String() string {
return fmt.Sprintf("Author: %v, Number of Slides: %v",
p.author, len(p.slides))
}
func newIntroSlide(author string) slide {
return slide{text: "Author: " + author}
}
// END OMIT
type slide struct {
text string
}

@ -0,0 +1,4 @@
func mapIntToInt(f func(int) int, []int) []int
func mapIntToString(f func(int) string, []int) []string
func mapStringToInt(f func(string) int, []string) []int
func mapStringToString(f func(string) string, []string) []string

@ -0,0 +1,38 @@
package main
import (
"fmt"
"os/exec"
)
// START OMIT
func quicksort(p []int) []int {
if len(p) == 0 {
return []int{}
}
lesser := filter(func(x int) bool { return p[0] > x }, p[1:]) // HLcustom
greater := filter(func(x int) bool { return p[0] <= x }, p[1:]) // HLcustom
return append(
quicksort(lesser),
prepend(p[0], quicksort(greater))..., // HLcustom
)
}
func main() {
fmt.Println(quicksort([]int{1, 8, 5, 3, 4, 9}))
funcheck()
}
// END OMIT
func funcheck() {
cmd := exec.Command("/go/bin/funcheck", "./code/quicksort/main.go")
out, err := cmd.CombinedOutput()
if err == nil {
fmt.Println("funcheck: no reported errors")
return
}
fmt.Printf("%s\n%s\n", out, err)
}

@ -0,0 +1,30 @@
package main
import (
"fmt"
"os/exec"
)
func reassignments() {
var x int
// START OMIT
for {
}
for x != 0 {
}
for i, elem := range []int{1, 2, 3} {
fmt.Println(i, elem)
}
for x := 0; x < 10; x++ {
}
// END OMIT
}
func main() {
cmd := exec.Command("/go/bin/funcheck", "./code/reassignments/for")
out, err := cmd.CombinedOutput()
fmt.Printf("%s\n%s", out, err)
}

@ -0,0 +1,25 @@
package main
import (
"fmt"
"os/exec"
)
func reassignments() {
// START OMIT
//f := func() {
// f() // undeclared name: f
//}
var f func()
f = func() {
f()
}
// END OMIT
}
func main() {
cmd := exec.Command("/go/bin/funcheck", "./code/reassignments/funcliterals")
out, err := cmd.CombinedOutput()
fmt.Printf("%s\n%s", out, err)
}

@ -0,0 +1,36 @@
package main
import (
"fmt"
"os/exec"
)
func reassignments() {
// START OMIT
x, err := do()
if err != nil {
panic(err)
}
y, err := do()
if err != nil {
panic(err)
}
_ = "hello" // blank identifiers
var a float64 = 5
// END OMIT
fmt.Println(a, x, y)
}
func do() (int, error) {
return 0, nil
}
func main() {
cmd := exec.Command("/go/bin/funcheck", "./code/reassignments/issues")
out, err := cmd.CombinedOutput()
fmt.Printf("%s\n%s", out, err)
}

@ -0,0 +1,32 @@
package main
import (
"fmt"
"os/exec"
)
func reassignments() {
// START OMIT
var a int
a = 5
var b int = 5
c := 5
a = 6
a |= 3
a += 7
a++
// END OMIT
fmt.Println(a, b, c)
}
func main() {
cmd := exec.Command("/go/bin/funcheck", "./code/reassignments/")
out, err := cmd.CombinedOutput()
fmt.Printf("%s\n%s", out, err)
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,8 @@
module github.com/tommyknows/bachelor-thesis/presentation
go 1.14
require (
github.com/tommyknows/funcheck v0.1.2
golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff
)

@ -0,0 +1,25 @@
github.com/tommyknows/funcheck v0.1.2 h1:hML/hdzmAYJUm50m9mrSB7uHcRxAK43m7VvxGY/egx8=
github.com/tommyknows/funcheck v0.1.2/go.mod h1:u/JdOI1TR4LAjf1A/eDzzVbpebsn+Bet6r84Rvpy9gM=
github.com/yuin/goldmark v1.1.27 h1:nqDD4MMMQA0lmWq03Z2/myGPYLQoXtmi0rGVs95ntbo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff h1:foic6oVZ4MKltJC6MXzuFZFswE7NCjjtc0Hxbyblawc=
golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

@ -0,0 +1,140 @@
# Functional Go
...an easier introduction to functional programming
29 Jun 2020
Summary: Presentation of my bachelor thesis functional Go
Ramon Rüttimann
me@ramonr.ch
## (Learning) Functional Programming is hard
[What I Wish I Knew When Learning Haskell](http://dev.stephendiehl.com/hask/tutorial.pdf) - 475 pages
FUP@ZHAW teaches the very basics of functional programming with Haskell
- Changed the way I think about problems
- I understand the (very) basics
- Could not write production-ready code
- Learning syntax + paradigm
.code quicksort.hs
## Making it easier
Learn functional programming without learning a new syntax
Choose a language that:
- has a familiar syntax (with familiar keywords)
- is statically typed
- supports functions as first class citizens ("multi-paradigm")
- is as simple as possible
plus: add tooling to ensure a purely functional style
## The chosen one: Go
- C-like syntax
- statically typed
- multi-paradigm
- simple
> Clear is better than clever
Go Proverbs - Rob Pike
.image gopher.png _ 240
## Issues with Go
- No list type
- but slices
- no difference in usage, but in runtime
- No list processing functions (higher-order functions)
- No polymorphism
- apart from (compiler) built-in functions
- every call translated at compile time (AST rewriting)
- `append`, `make`, `len`, `new`, `copy`, ...
.code code/map.go
## Adding new built-in functions to the compiler
fmap, foldr, foldl, prepend, filter
- documentation (Godoc)
- type checking
- for external tools
- within the compiler
- AST walking / rewriting
- instead of directly writing code, write the code as AST nodes
## Example Builtin: fmap
.code code/walk/walk.go /START OMIT/,/END OMIT/
## Tooling: funcheck
Goal: add tooling to ensure a purely functional style
-> Write a code analysis tool that reports non-functional constructs
Definition of "purely functional":
> [...] Purely functional programming may also be defined by forbidding changing state and mutable data.
> Purely functional programming consists in ensuring that functions [...] will only depend on their arguments, regardless of any global or local state.
Purely functional programming - Wikipedia
## One rule to rule them all
Do not allow reassigments
- Disallow the `=` operator
- Only allow using `:=` (short variable declarations)
.play -numbers code/reassignments/main.go /START OMIT/,/END OMIT/
## ...with exceptions
> [...], a short variable declaration may redeclare variables [...]
> with the same type [...]. As a consequence, redeclaration can only appear in a multi-variable short
> declaration. Redeclaration does not introduce a new variable; it just assigns
> a new value to the original
Go Language Specification
.play -numbers code/reassignments/issues/main.go /START OMIT/,/END OMIT/
## Finding declarations
.play -numbers code/declprint/main.go /START OMIT/,/END OMIT/
## Putting it together
- Finds all (re)assignments through position, not the operator that is being used
- Recursive function literals need an exception
- For- and range-loops need to be reported explicitly
- Increment & Decrement operators need to be reported explicitly
.code code/funcliteral/main.go /START OMIT/,/END OMIT/
## Functional Go
.play -edit code/quicksort/main.go /START OMIT/,/END OMIT/ HLcustom
## Conclusion
- Clear is better than clever
- Verbosity is (in this case) a good thing
- Effort is lowered by the new built-in functions
- Does not / can not replace actual purely functional languages
- Limitations of the type system
- Performance considerations
- Open Source
.image me.png _ 215
: Type System: Polymorphism, sum types
: Performance: TCO (tail call optimisations)

@ -0,0 +1,10 @@
package main
import (
"github.com/tommyknows/funcheck/prettyprint"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() {
singlechecker.Main(prettyprint.Analyzer)
}

@ -0,0 +1,6 @@
quicksort :: Ord a => [a] -> [a]
quicksort [] = []
quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)
where
lesser = filter (< p) xs
greater = filter (>= p) xs

Binary file not shown.

@ -1 +1 @@
Subproject commit 64915aca0c205d4778180d56ed9a7d62a53f83e2 Subproject commit c15f4ec19900ebd410468ccbec5b02e50d8d2efe
Loading…
Cancel
Save