Packages
Successful design is based on a principle known since the days of Julius Caesar: Divide and conquer. — Structured Design, Edward Yourdon and Larry L. Constantine
Go programs are organized into packages. A package is a collection of source files in the same directory that are compiled together. Functions, types, variables, and constants defined in one source file are visible to all other source files within the same package. — How to Write Go Code
Fahrenheit/Celsius Table
As per K&R tradition, programming basics such variable assignment, types (int vs float), for
and while
loops, conditionals… are introduced by converting Fahrenheit to Celsius.
I’m going to use this simple example to create a function separate from main, housed in a subdirectory. Something that confused me is f2c
is the module while ftoc
is the package, whose path is f2c/ftoc
, ie module/package
.
My tree for this example:
f2c
├── f2c_test.go
├── ftoc
│ ├── ftoc.go
│ └── ftoc_test.go
├── go.mod
└── main.go
go.mod
Produced by go mod init f2c
module f2c
go 1.22.2
main.go
/*
My f2c package is based on K&R's Fahrenheit to Celsius tutorial.
*/
package main
import (
"fmt"
"f2c/ftoc"
)
const lower = -40.0
const upper = 300.0
const step = 20.0
/*
A comment above main gets ignored.
*/
func main() {
fahr := lower
for fahr <= upper {
fmt.Printf("%3.0f\t%6.1f\n", fahr, ftoc.FToC(fahr))
fahr = fahr + step
}
}
ftoc/ftoc.go
/*
Comment above the package declaration is put into "Overview" by godoc.
*/
package ftoc
/*
Comment above the function declaration
*/
func FToC(f float64) float64 {
/*
Comment below the function declaration is ignored.
*/
return (5.0 / 9.0) * (f - 32.0)
}
f2c_test.go
package main
// How do I get this into my documentation?
func Example() {
main()
// Output:
// -40 -40.0
// -20 -28.9
// 0 -17.8
// 20 -6.7
// 40 4.4
// 60 15.6
// 80 26.7
// 100 37.8
// 120 48.9
// 140 60.0
// 160 71.1
// 180 82.2
// 200 93.3
// 220 104.4
// 240 115.6
// 260 126.7
// 280 137.8
// 300 148.9
}
ftoc/ftoc_test.go
Here I’m moving to a TestXxx style test.
An advantage of seperating auxiliary functions from the main.go file is we can test them independently.
package ftoc
import (
"fmt"
"testing"
)
func TestFToC(t *testing.T) {
var tests = []struct {
fahrenheit float64
celsius float64
}{
{-40.0, -40.0},
{-20.0, -28.888889},
{0.0, -17.777778},
{20.0, -6.666667},
{40.0, 4.444444},
{60.0, 15.555556},
{80.0, 26.666667},
{100.0, 37.777778},
{120.0, 48.888889},
{140.0, 60.0},
{160.0, 71.111111},
{180.0, 82.222222},
{200.0, 93.333333},
{220.0, 104.444444},
{240.0, 115.555556},
{260.0, 126.666667},
{280.0, 137.777778},
{300.0, 148.888889},
}
for _, test := range tests {
got := FToC(test.fahrenheit)
if fmt.Sprintf("%6.2f", got) != fmt.Sprintf("%6.2f", test.celsius) {
t.Errorf("FToC(%f) = %f; want %f", test.fahrenheit, got, test.celsius)
}
}
}
// This appears in the docs as a collapsible Example.
func ExampleFToC() {
fmt.Printf("%6.2f", FToC(-40.0))
// Output:
// -40.00
}
This example lends itself to Table-Driven Tests. That to avoid floating comparison errors I’d need to convert want and got into strings wasn’t obvious to me.
By naming the test ExampleFToC
it automatically gets added to the documentation.
Assignment
Introducing assignment into our programming language leads us into a thicket of difficult conceptual issues. — Structure and Interpretation of Computer Programs
As in C, all variables must be declared before they are used, usually at the beginning of the function before any executable statements.
The differences from the original C example in K&R:
- In go, type declarations come at the end instead of the beginning.
- There is only a for loop which can be made to act just like C’s while.
- I put ; at the end of each statement which
go fmt fahrenheit_celsius.go
removed.
Symbolic constants
#define LOWER 0 /* lower limit of table */
#define UPPER 300 /* upper limit */
#define STEP 20 /* step size */
Go’s const
is fairly identical to C’s #define
. Not sure if Go has an all upercase convention, since that would export constants even if I don’t want to.
:= vs =
Instead of declaring fahr
and celsius
as typed variables C-style, Go allows variables to be introduced anywhere (as in Bash, JavaScript, Python…) using the :=
operator which is used in Hugo. Later reassignment of these variables requires the =
operator.
package main
import (
"fmt"
)
const lower = -40
const upper = 300
const step = 20
func main() {
fahr := lower
for fahr <= upper {
celsius := 5 * (fahr - 32) / 9
fmt.Printf("%d\t%d\n", fahr, celsius)
fahr = fahr + step
}
}
The short variable declaration operator :=
can only be used within functions, not if variables and constants are defined outside of functions:
Type inference
A snag with the :=
short variable declaration operator is it infers int
, so if I want the table to be float, I have to declare it:
package main
import (
"fmt"
)
const lower = -40
const upper = 300
const step = 20
func main() {
var fahr, celsius float64
fahr = lower
for fahr <= upper {
celsius = (5.0 / 9.0) * (fahr - 32.0)
fmt.Printf("%f\t%f\n", fahr, celsius)
fahr = fahr + step
}
}
Using Go’s for in a more conventional way:
package main
import (
"fmt"
)
const lower = -40
const upper = 300
const step = 20
func main() {
var fahr, celsius float64
for fahr = lower; fahr <= upper; fahr = fahr + step {
celsius = (5.0 / 9.0) * (fahr - 32.0)
fmt.Printf("%f\t%f\n", fahr, celsius)
}
}
server
Simple file server
// Simple static webserver:
package main
import (
"log"
"net/http"
)
func main() {
log.Fatal(http.ListenAndServe("localhost:8000", http.FileServer(http.Dir("/usr/share/doc"))))
}
FileServer returns a handler that serves HTTP requests with the contents of the file system rooted at root. As a special case, the returned file server redirects any request ending in “/index.html” to the same path, without the final “index.html”.
ServeFile doesn’t return a handler, but rather is a function that can be used in a handler to return the contents of a file.