First Class Functions Exercise "Expense" Should Have More Information

I’m having a tough time wrapping my head around higher order functions.

In the exercise Expenses, it would be be extremely helpful to break down functions Filter and ByCategory (or Filter and ByDaysPeriod) into regular functions, in the Introduction section.

The current Fibonacci example (anonymous function) is very different from what’s actually going on in the exercise (predicate function with higher order function).

I would love to submit a PR but don’t understand the concept.

Can you explain a bit more what you mean by break down […] into regular functions?

Feel free to provide example code snippets, if applicable.


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.

For taks 1 and 2, look at all other examples, but not fib :wink:


Here are some guiding questions about Filter and ByDaysPeriod:

  • Can you explain what Filter should do?
  • Can you give Filter’s type?
  • Can you explain what ByDaysPeriod should do?
  • Can you give ByDaysPeriod’s type?

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 :wink:

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 ?

To be clear, first class functions is not so much a single concept as a cluster of concepts:

  • functions can be returned,
  • functions can be passed as arguments,
  • (functions can be assigned to variables,)
  • closed-over variables in the presence of mutation (“closures”),
  • function literals (“lambdas” / “anonymous functions”).

These are packaged together because they each have trouble being useful or even meaningful on their own.

I’m not entirely sure I follow, but perhaps this is the missing step you are looking for?

// Pardon my Go; I do not know this language
func Filter(records []Record, category string) []Record {
    var result []Record

    predicate := func(candidate Record) bool {
        return ByCategory(candidate, category)
    }

    for _, record := range records {
        if predicate(record) {
            result = append(result, record)
        }
    }

    return result
}

The final step then is to notice that category is only used in building the predicate, and that therefore we might as well parametrize over predicate instead and thereby generalize Filter.

Your right; distracted by the fancy mutation, I overlooked this.

Perhaps an extra example would be good here. Of returning a function, without the fancy mutation business, a la

// Again, excuse my Go.
func TranslateBy(dx int) {
    return func (x int) { return x + dx }
}

add5 := TranslateBy(5)

add5(9) // 14
1 Like

Yes, I meant first class functions and the cluster of associated concepts you’ve listed.

I’m not entirely sure I follow, but perhaps this is the missing step you are looking for?

This is exactly what I was looking for - Thank you so much !!


I feel that the introduction section should cover something similar to the ByCategory function.