Git Product home page Git Product logo

dogs's Introduction

Hi there 👋

ci status

Metrics Notable contributions Commit calendar Most used languages

dogs's People

Contributors

genkami avatar neglect-yp avatar renovate-bot avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

dogs's Issues

Examples needed.

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.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/test.yaml
  • actions/setup-go v4
  • actions/checkout v3
  • actions/cache v3
gomod
go.mod
  • go 1.18
  • github.com/stretchr/testify v1.8.4
  • golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df@97b1e661b5df

  • Check this box to trigger a request for Renovate to run again on this repository

Examples with functional monoids.

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.