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=
|
After Width: | Height: | Size: 329 KiB |
After Width: | Height: | Size: 228 KiB |
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…
Reference in new issue