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