Moved to skeeto/scratch/misc
-
-
Save skeeto/d8b18fb4663f4a282e8c0db252a70faf to your computer and use it in GitHub Desktop.
skeeto
commented
May 5, 2020
via email
Hi Chris,
This is some really great feedback! I really appreciate it. I have a habit of switching between vim and VSCode, which automatically uses gofmt
. I need to get into the habit of running it when using vim or find some type of plug-in which does this on save.
I am going to experiment with buffered output and using byte slices instead of strings -- those are excellent ideas.
Could you please give me a good GitHub example that is not too complex but implements a configuration struct and a "filter" interface? I am not too familiar with these paradigms. You also mentioned good design is generally preferred over a small performance boost. My code is definitely ugly (but works). I would love to make it cleaner and more maintainable.
Thanks again,
-John
Could you please give me a good GitHub example that is not too complex but implements a configuration struct
https://github.com/skeeto/passphrase2pgp/blob/870b0a4/passphrase2pgp.go#L83
That struct is filled out by the option parser and gets passed around the program so that the different components can access the parts of the configuration they need. If I add or remove an option I don't have to change all the call sites to add/remove parameters/arguments.
and a "filter" interface?
Here are two approaches: functional and interface. I'll start with the one I prefer:
package main
import (
"fmt"
"strings"
)
type filter func(string) string
func identity(s string) string {
return s
}
func chain(a, b filter) filter {
return func(s string) string {
return b(a(s))
}
}
func substring(beg, end int) filter {
return func(s string) string {
return s[beg:end]
}
}
func reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
func main() {
f := filter(identity)
f = chain(f, strings.ToLower)
f = chain(f, substring(0, 4))
f = chain(f, reverse)
fmt.Println(f("FooBar"))
}
Note how I start with the identity filter, then chain on new filters dynamically. Your option parser could append new filters as it parses arguments, so that the order of filters depends on the order the of arguments. Also notice how I could use strings.ToLower
directly because it was already a filter
.
Here's the interface version:
package main
import (
"fmt"
"strings"
)
type filter interface {
Transform(string) string
}
type identity struct{}
func (_ *identity) Transform(s string) string {
return s
}
type chain [2]filter
func (c *chain) Transform(s string) string {
return c[1].Transform(c[0].Transform(s))
}
type lower struct{}
func (_ *lower) Transform(s string) string {
return strings.ToLower(s)
}
type substring struct{ beg, end int }
func (x *substring) Transform(s string) string {
return s[x.beg:x.end]
}
type reverse struct{}
func (_ *reverse) Transform(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
func main() {
f := filter(&identity{})
f = &chain{f, &lower{}}
f = &chain{f, &substring{0, 4}}
f = &chain{f, &reverse{}}
fmt.Println(f.Transform("FooBar"))
}
Quite a bit more verbose, but potentially more flexible if you filters become more complex. In the function version the substring filter captured its configuration in a closure. In the interface version it's stored on a struct.
Thanks again for another great reply - and what a cool reverse()
function! I also noticed that your passphrase code imports your own optparse
library. The configuration struct
idea seems pretty straight forward.
The functional
version it easier for me to follow. The chaining concept looks really useful. I am going to experiment with it. OTOH, the interface
version seems syntactically cumbersome and is harder for me to follow. I think ease of use is going to win out for me vs potential flexibility.