genkami / dogs Goto Github PK
View Code? Open in Web Editor NEWMake Go functional with dogs
License: Apache License 2.0
Make Go functional with dogs
License: Apache License 2.0
Hello @genkami,
Thank you for you detailed response and a great example. I also noticed you've updated the repo and added the FizzBuzz example:
func main() {
monoid := option.DeriveMonoid[string](algebra.DeriveAdditiveSemigroup[string]())
fizzBuzz := func(i int) string {
fizz := option.Filter(option.Some[string]("Fizz"), func(_ string) bool { return i%3 == 0 })
buzz := option.Filter(option.Some[string]("Buzz"), func(_ string) bool { return i%5 == 0 })
return option.UnwrapOr(monoid.Combine(fizz, buzz), fmt.Sprint(i))
}
it := iterator.Map(iterator.Range[int](1, 15), fizzBuzz)
iterator.ForEach(it, func(s string) { fmt.Println(s) })
}
It looks nice, concise and pretty aligned with idiomatic Golang.
What I'm not sure about is whether combining not monoids is ok, because they don't follow the monodic laws. Is it the expected behavior? In the example above you use a monoid to combine options which don't implement the Monoid interface.
Allow me to drive it a little bit further:
At the moment we have this Filter
function on the option type. But what if (following the ideas from the aforementioned articles) we try to fold over a set of fizz-buzz Rules
, where a Rule is function which takes a predicate and the word:
type FizzBuzzPredicate func(num int) bool
type FizzBuzzRule func(n int) option.String
func Rule(p FizzBuzzPredicate, s string) FizzBuzzRule {
return func(n int) option.String {
if p(n) {
return option.Some(s)
}
return option.NoneString()
}
}
func FizzBuz(n int) string {
fizz := func(n int) bool {
return n%3 == 0
}
buzz := func(n int) bool {
return n%5 == 0
}
// rules should be a foldable of monoids
rules := []FizzBuzzRule{
Rule(fizz, "Fizz"),
Rule(buzz, "Buzz"),
}
// ruleSet is a monoid, which is a function by its nature: monoid[FizzBuzzRule]
// Written in a move verbose way it is the same as: monoid[ func(n int) option.String].
// see https://blog.ploeh.dk/2017/11/06/function-monoids/
ruleSet := fold(rules)
return ruleSet(n).UnwrapOr(strconv.ItoA(i))
}
// fold :: (Foldable t, Monoid m) => t m -> m
func Fold(filters Foldable) monoid[FizzBuzzFilter] {
// fold over filters and turn them into a monoid
}
An excerpt in Haskell:
fizzbuzz i = fromMaybe (show i) (ruleSet i)
ruleSet = fold rules
The reasoning behind this is is as such:
Option
should probably not have the Filter
method, or should it? I've added it for convenience :)Monoids
could be the functions per sei, so the combine
method should be able to join the function together into a single monoid.Tell me what do you think?
Thank you for your wonderful job and for patience!
PS: I'm writing without an IDE at hand, so forgive me for some inconsistencies.
Hello @genkami!
First of all thank you for this nice project. I've recently discovered it and find it interesting.
I wanted to send you a letter, but then thought, that this issue maybe useful to some other enthusiasts, and also I can use the markdown formatting. Apologies for a long read and for the fact that I haven't had the chance to push the project to GitHub yet.
Before the generics were available I was playing around Monoids in Golang. My favourite example is FizzBuzz!
So I implemented the solution which was highly inspired by a couple of articles mentioned bellow.
The first approach looked something like this:
// FizzBuzzFunctional
//
// https://web.archive.org/web/20130511210903/http://dave.fayr.am/posts/2012-10-4-finding-fizzbuzz.html
// https://medium.com/@iopguy/fizzbuzz-can-finally-be-implemented-in-stable-rust-87649a882f2d
// https://www.parsonsmatt.org/2016/02/27/an_elegant_fizzbuzz.html
func FizzBuzzFunctional(n int) string {
m := monoid.ForString("").
Append(monoid.ForString("Fizz").Filtered(func() bool {
return n%3 == 0
})).
Append(monoid.ForString("Buzz").Filtered(func() bool {
return n%5 == 0
}))
return m.UnwrapOr(strconv.Itoa(n))
}
I had to implement an Optional
type (an equivalent of Maybe, the name seemed more conventional for Golang). UnwrapOr
is basically FromMaybe
in Haskell. After that I turned that Optional
into a monoid. The API is not ideal, but the solution looks pretty simple. Given that we don't have the monadic list comprehensions I used Filtered
function to add the predicated. So basically the structure that implemented the monoid, also had a Filtered
method. Not very elegant, but better than nothing.
The beauty of this solution is that we can easily add more predicates: Fizz, Buzz, Bizz, etc... However in order for it to work we need to be able to create a Monoid which is basically a function, and not a concrete a simple type.
My Optional Monoid
looked like this:
package monoid
type OptionString struct {
op option.String
}
// SomeString wraps the s into an optional string.
func SomeString(s string) OptionString {
return OptionString{
op: option.SomeString(s),
}
}
// NoneString returns an empty optional string.
func NoneString() OptionString {
return OptionString{
op: option.NoneString(),
}
}
func (m OptionString) Empty() OptionString {
return NoneString()
}
func (m OptionString) Append(other OptionString) OptionString {
switch {
case m == NoneString():
return other
case other == NoneString():
return m
default:
return SomeString(m.op.Append(other.op).UnwrapOrDefault())
}
}
func (m OptionString) Filtered(fn func() bool) OptionString {
if fn() {
return m
}
return NoneString()
}
func (m OptionString) UnwrapOr(s string) string {
return m.op.UnwrapOr(s)
}
It was using a String Option which supported the Append for convenience, however it was not a Monoid, it didn't respect the Monodic laws, however the monoid.OptionsSting did:
func TestOptionString(t *testing.T) {
t.Parallel()
// A set S equipped with a binary operation S × S → S,
// which we will denote •, is a monoid if it satisfies the following two axioms:
t.Run("it has valid identity", func(t *testing.T) {
t.Parallel()
m := monoid.SomeString("test")
// There exists an element e in S such that for every element a in S,
// the equations e • a = a and a • e = a hold.
AssertEqual(t, m, m.Append(m.Empty()))
AssertEqual(t, m.Empty().Append(m), m.Append(m.Empty()))
})
t.Run("it has valid associativity", func(t *testing.T) {
t.Parallel()
a := monoid.SomeString("foo")
b := monoid.SomeString("bar")
c := monoid.NoneString()
// For all a, b and c in S, the equation (a • b) • c = a • (b • c) holds.
AssertEqual(t, a.Append(b).Append(c), a.Append(b.Append(c)))
})
}
The new iteration was changed a little to use foldable:
func FizzBuzzFunctional(n int) string {
fizz := func(n int) bool {
return n%3 == 0
}
buzz := func(n int) bool {
return n%5 == 0
}
filters := foldable.RuleFoldable{
monoid.ForFizzBuzzPredicate(fizz, "Fizz"),
monoid.ForFizzBuzzPredicate(buzz, "Buzz"),
}
rules := fold(filters, monoid.NewEmptyFizzBuzzRuleset()) // a monoid which combines all the filters together
return monoid.FromStringOption(strconv.Itoa(n), rules(n))
}
// fold :: (Foldable t, Monoid m) => t m -> m
func fold(filters foldable.RuleFoldable, m monoid.FizzBuzzRuleset) monoid.FizzBuzzRuleset {
rules := filters.Foldl(filters.Init(), func(result foldable.T, next foldable.T) foldable.T {
rule, ok := next.(func(int) monoid.OptionString)
mustOk(ok, "cannot cast result foldable.T to func(int) monoid.OptionString")
m = m.Append(rule)
return m
})
ruleSet, ok := rules.(monoid.FizzBuzzRuleset)
mustOk(ok, "cannot cast result foldable.T to monoid.FizzBuzzRuleset")
return ruleSet
}
func mustOk(ok bool, msg string) {
if !ok {
panic(msg)
}
}
Now that we have the generics, and your library we probably can come up with a better solution. I tried to use some of your ideas but at the moment you're changing the API faster than I have the time to follow :-D Nonetheless, I was almost able to implement the monoids I need from your wonderful library, however I stuck at the moment with a monoid to combine two functions. It got pretty ugly on my side.
Could you please add an ExampleTest
for this FizzBuzz
case to demonstrate the usage of your library the way you were intended? What I really would like to achieve is the following translated into Go without being too ugly :)
{-# LANGUAGE MonadComprehensions #-}
module Main where
import Data.Monoid (mappend)
import Data.Maybe (fromMaybe, listToMaybe, maybe)
import System.Environment (getArgs)
fizzbuzz i = fromMaybe (show i) $ mappend ["fizz" | i `rem` 3 == 0]
["buzz" | i `rem` 5 == 0]
main = mapM_ putStrLn [ fizzbuzz i | i <- [1..100] ]
Thank you very much.
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
.github/workflows/test.yaml
actions/setup-go v4
actions/checkout v3
actions/cache v3
go.mod
go 1.18
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df@97b1e661b5df
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.