Sorry this is going to be a long response:
The goal of this exercise is to teach first class functions. Lets take the thinnest possible slice of the problem which can teach this concept.
This slice would exclude the structure DaysPeriod
and its associated functions ByDaysPeriod
, TotalByPeriod
, and CategoryExpenses
. This is because whatever is learnt from structure Record
and it’s associated functions Filter
and ByCategory
, can be applied to the current problem in its entirety.
So now we’re left with the below implementation (lets call it the final implementation):
package expenses
type Record struct {
Day int
Amount float64
Category string
}
func Filter(records []Record, f func(Record) bool) []Record {
var result []Record
for _, record := range records {
if f(record) {
result = append(result, record)
}
}
return result
}
func ByCategory(category string) func(Record) bool {
return func(r Record) bool {
return r.Category == category
}
}
and tests:
package expenses
import "testing"
var testExpensesRecords = []Record{
{
Day: 1,
Amount: 5.15,
Category: "grocery",
},
{
Day: 1,
Amount: 3.45,
Category: "grocery",
},
{
Day: 13,
Amount: 55.67,
Category: "utility",
},
{
Day: 15,
Amount: 11,
Category: "grocery",
},
{
Day: 18,
Amount: 244.33,
Category: "utility",
},
{
Day: 20,
Amount: 300,
Category: "university",
},
{
Day: 23,
Amount: 20.0,
Category: "grocery",
},
{
Day: 25,
Amount: 24.65,
Category: "grocery",
},
{
Day: 30,
Amount: 1300,
Category: "rent",
},
}
func TestFilterByCategory(t *testing.T) {
testCases := []struct {
name string
category string
expected []Record
}{
{
name: "returns expenses in grocery category",
category: "grocery",
expected: []Record{
{
Day: 1,
Amount: 5.15,
Category: "grocery",
},
{
Day: 1,
Amount: 3.45,
Category: "grocery",
},
{
Day: 15,
Amount: 11,
Category: "grocery",
},
{
Day: 23,
Amount: 20.0,
Category: "grocery",
},
{
Day: 25,
Amount: 24.65,
Category: "grocery",
},
},
},
{
name: "returns empty list for unknown category",
category: "ABC",
expected: []Record{},
},
}
for _, tC := range testCases {
t.Run(tC.name, func(t *testing.T) {
got := Filter(testExpensesRecords, ByCategory(tC.category))
if len(got) != len(tC.expected) {
t.Fatalf("Filter by category got %d records, want %d", len(got), len(tC.expected))
}
for i, expected := range tC.expected {
if got[i] != expected {
t.Fatalf("Filter by category got %v, want %v", got, tC.expected)
}
}
})
}
}
Now coming to your questions:
Can you explain a bit more what you mean by break down […] into regular functions?
Feel free to provide example code snippets, if applicable.
Lets try to break down the above final implementation into concepts the Go track has covered so far - structs, slice of structs, for loop, etc.
So the simplest implementation would look like (lets call this V1):
package expenses
type Record struct {
Day int
Amount float64
Category string
}
func Filter(records []Record, category string) []Record {
var result []Record
for _, record := range records {
if record.Category == category {
result = append(result, record)
}
}
return result
}
(skipping tests for brevity).
Now lets try to get this V1 implementation to the final implementation. This may look something like (lets call this V2):
package expenses
type Record struct {
Day int
Amount float64
Category string
}
func Filter(records []Record, category string) []Record {
var result []Record
for _, record := range records {
if ByCategory(record, category) {
result = append(result, record)
}
}
return result
}
func ByCategory(record Record, category string) bool {
return record.Category == category
}
(again, skipping the tests for brevity - they’re the same as tests for V1)
Lets try again to get this V2 implementation to the final implementation. Lets call this V3 - I’m not able to do it. I’ve looked at the Introduction section, but it doesn’t help.
The Fibonacci example is irrelevant to tasks 1 and 2. The functions Filter and ByDaysPeriod do not need the full fanciness of closures that fib is using.
The fibonacci example is very much relevant to the ByCategory
task, because it returns a variable of type function, i.e., middle two lines of the below code:
func ByCategory(category string) func(Record) bool {
return func(r Record) bool {
return r.Category == category
}
}
For tasks 1 and 2, look at all other examples, but not fib
I think you may be referring to the second code block in the “Introduction” section, which has functions dialog
and espGreeting
. If this is the case, it only explains the Filter
function, which also takes predicate functions as parameters, but doesn’t return it.
The magic is happening in the below part of ByCategory
implementation, and I’m not able to get around it:
............................... func(Record) bool {
return func(r Record) bool {
The Introduction goes from a simple greeting example to a more complicated Fibonacci example. I think they’re unrelated and looking at the code blocks is not seamless.
I’m trying to implement V3, which would be one level “lower” than the final implementation, and one level “higher” than the V2 implementation.
Can you help me with a V3 ?