diff --git a/number/integer/string.go b/number/integer/string.go new file mode 100644 index 0000000..3cc5489 --- /dev/null +++ b/number/integer/string.go @@ -0,0 +1,23 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integer + +import "strconv" + +var ( + // ToString converts an integer to a string + ToString = strconv.Itoa +) diff --git a/ord/ord.go b/ord/ord.go index d90e387..dce7c0f 100644 --- a/ord/ord.go +++ b/ord/ord.go @@ -131,9 +131,7 @@ func FromStrictCompare[A C.Ordered]() Ord[A] { return MakeOrd(strictCompare[A], strictEq[A]) } -/** - * Test whether one value is _strictly less than_ another - */ +// Lt tests whether one value is _strictly less than_ another func Lt[A any](O Ord[A]) func(A) func(A) bool { return func(second A) func(A) bool { return func(first A) bool { @@ -142,9 +140,7 @@ func Lt[A any](O Ord[A]) func(A) func(A) bool { } } -/** - * Test whether one value is less or equal than_ another - */ +// Leq Tests whether one value is less or equal than_ another func Leq[A any](O Ord[A]) func(A) func(A) bool { return func(second A) func(A) bool { return func(first A) bool { @@ -156,7 +152,7 @@ func Leq[A any](O Ord[A]) func(A) func(A) bool { /** * Test whether one value is _strictly greater than_ another */ -func Gt[A any](O Ord[A]) func(A) func(A) bool { +func cc[A any](O Ord[A]) func(A) func(A) bool { return func(second A) func(A) bool { return func(first A) bool { return O.Compare(first, second) > 0 @@ -164,9 +160,7 @@ func Gt[A any](O Ord[A]) func(A) func(A) bool { } } -/** - * Test whether one value is greater or equal than_ another - */ +// Geq tests whether one value is greater or equal than_ another func Geq[A any](O Ord[A]) func(A) func(A) bool { return func(second A) func(A) bool { return func(first A) bool { diff --git a/samples/mostly-adequate/README.md b/samples/mostly-adequate/README.md new file mode 100644 index 0000000..caf7b84 --- /dev/null +++ b/samples/mostly-adequate/README.md @@ -0,0 +1,6 @@ +# Mostly Adequate: fp-go Companion Guide + +This resource is meant to serve as a go "companion" resource to Professor [Frisby's Mostly Adequate Guide](https://github.com/MostlyAdequate/mostly-adequate-guide). + +It is a port of the [mostly-adequate-fp-ts](https://github.com/ChuckJonas/mostly-adequate-fp-ts/) book. + diff --git a/samples/mostly-adequate/chapter01_test.go b/samples/mostly-adequate/chapter01_test.go new file mode 100644 index 0000000..a604c6b --- /dev/null +++ b/samples/mostly-adequate/chapter01_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mostlyadequate + +import "fmt" + +type Flock struct { + Seagulls int +} + +func MakeFlock(n int) Flock { + return Flock{Seagulls: n} +} + +func (f *Flock) Conjoin(other *Flock) *Flock { + f.Seagulls += other.Seagulls + return f +} + +func (f *Flock) Breed(other *Flock) *Flock { + f.Seagulls = f.Seagulls * other.Seagulls + return f +} + +func Example_flock() { + + flockA := MakeFlock(4) + flockB := MakeFlock(2) + flockC := MakeFlock(0) + + fmt.Println(flockA.Conjoin(&flockC).Breed(&flockB).Conjoin(flockA.Breed(&flockB)).Seagulls) + + // Output: 32 +} diff --git a/samples/mostly-adequate/chapter04_currying_test.go b/samples/mostly-adequate/chapter04_currying_test.go new file mode 100644 index 0000000..df0f877 --- /dev/null +++ b/samples/mostly-adequate/chapter04_currying_test.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mostlyadequate + +import ( + "regexp" + "strings" + + F "github.com/IBM/fp-go/function" + N "github.com/IBM/fp-go/number" + I "github.com/IBM/fp-go/number/integer" + S "github.com/IBM/fp-go/string" +) + +var ( + Match = F.Curry2((*regexp.Regexp).FindStringSubmatch) + Split = F.Curry2(F.Bind3of3((*regexp.Regexp).Split)(-1)) + + Add = N.Add[int] + ToString = I.ToString + ToLower = strings.ToLower + ToUpper = strings.ToUpper + Concat = F.Curry2(S.Monoid.Concat) +) + +// Replace cannot be generated via [F.Curry3] because the order of parameters does not match our desired curried order +func Replace(search *regexp.Regexp) func(replace string) func(s string) string { + return func(replace string) func(s string) string { + return func(s string) string { + return search.ReplaceAllString(s, replace) + } + } +} diff --git a/samples/mostly-adequate/chapter05_composing_test.go b/samples/mostly-adequate/chapter05_composing_test.go new file mode 100644 index 0000000..df63b76 --- /dev/null +++ b/samples/mostly-adequate/chapter05_composing_test.go @@ -0,0 +1,60 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mostlyadequate + +import ( + "fmt" + "regexp" + + A "github.com/IBM/fp-go/array" + F "github.com/IBM/fp-go/function" + S "github.com/IBM/fp-go/string" +) + +var ( + Exclaim = S.Format[string]("%s!") + Shout = F.Flow2(ToUpper, Exclaim) + Dasherize = F.Flow4( + Replace(regexp.MustCompile(`\s{2,}`))(" "), + Split(regexp.MustCompile(` `)), + A.Map(ToLower), + A.Intercalate(S.Monoid)("-"), + ) +) + +func Example_shout() { + fmt.Println(Shout("send in the clowns")) + + // Output: SEND IN THE CLOWNS! +} + +func Example_dasherize() { + fmt.Println(Dasherize("The world is a vampire")) + + // Output: the-world-is-a-vampire +} + +func Example_pipe() { + output := F.Pipe2( + "send in the clowns", + ToUpper, + Exclaim, + ) + + fmt.Println(output) + + // Output: SEND IN THE CLOWNS! +} diff --git a/samples/mostly-adequate/chapter08_tupperware_test.go b/samples/mostly-adequate/chapter08_tupperware_test.go new file mode 100644 index 0000000..bd41f40 --- /dev/null +++ b/samples/mostly-adequate/chapter08_tupperware_test.go @@ -0,0 +1,133 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mostlyadequate + +import ( + "fmt" + "time" + + E "github.com/IBM/fp-go/either" + "github.com/IBM/fp-go/errors" + F "github.com/IBM/fp-go/function" + N "github.com/IBM/fp-go/number" + O "github.com/IBM/fp-go/option" + "github.com/IBM/fp-go/ord" + S "github.com/IBM/fp-go/string" +) + +type Account struct { + Balance float32 +} + +func MakeAccount(b float32) Account { + return Account{Balance: b} +} + +func getBalance(a Account) float32 { + return a.Balance +} + +var ( + ordFloat32 = ord.FromStrictCompare[float32]() + UpdateLedger = F.Identity[Account] + RemainingBalance = F.Flow2( + getBalance, + S.Format[float32]("Your balance is $%0.2f"), + ) + FinishTransaction = F.Flow2( + UpdateLedger, + RemainingBalance, + ) + getTwenty = F.Flow2( + Withdraw(20), + O.Fold(F.Constant("You're broke!"), FinishTransaction), + ) +) + +func Withdraw(amount float32) func(account Account) O.Option[Account] { + + return F.Flow3( + getBalance, + O.FromPredicate(ord.Geq(ordFloat32)(amount)), + O.Map(F.Flow2( + N.Add(-amount), + MakeAccount, + ))) +} + +type User struct { + BirthDate string +} + +func getBirthDate(u User) string { + return u.BirthDate +} + +func MakeUser(d string) User { + return User{BirthDate: d} +} + +var parseDate = F.Bind1of2(E.Eitherize2(time.Parse))(time.DateOnly) + +func GetAge(now time.Time) func(User) E.Either[error, float64] { + return F.Flow3( + getBirthDate, + parseDate, + E.Map[error](F.Flow3( + now.Sub, + time.Duration.Hours, + N.Mul(1/24.0), + )), + ) +} + +func Example_widthdraw() { + fmt.Println(getTwenty(MakeAccount(200))) + fmt.Println(getTwenty(MakeAccount(10))) + + // Output: + // Your balance is $180.00 + // You're broke! +} + +func Example_getAge() { + now, err := time.Parse(time.DateOnly, "2023-09-01") + if err != nil { + panic(err) + } + + fmt.Println(GetAge(now)(MakeUser("2005-12-12"))) + fmt.Println(GetAge(now)(MakeUser("July 4, 2001"))) + + fortune := F.Flow3( + N.Add(365.0), + S.Format[float64]("%0.0f"), + Concat("If you survive, you will be "), + ) + + zoltar := F.Flow3( + GetAge(now), + E.Map[error](fortune), + E.GetOrElse(errors.ToString), + ) + + fmt.Println(zoltar(MakeUser("2005-12-12"))) + + // Output: + // Right[, float64](6472) + // Left[*time.ParseError, float64](parsing time "July 4, 2001" as "2006-01-02": cannot parse "July 4, 2001" as "2006") + // If you survive, you will be 6837 +} diff --git a/samples/mostly-adequate/chapter09_monadiconions_test.go b/samples/mostly-adequate/chapter09_monadiconions_test.go new file mode 100644 index 0000000..e6a435a --- /dev/null +++ b/samples/mostly-adequate/chapter09_monadiconions_test.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mostlyadequate + +import ( + "fmt" + + A "github.com/IBM/fp-go/array" + F "github.com/IBM/fp-go/function" + O "github.com/IBM/fp-go/option" +) + +type ( + Street struct { + Name string + Number int + } + + Address struct { + Street Street + Postcode string + } + + AddressBook struct { + Addresses []Address + } +) + +func getAddresses(ab AddressBook) []Address { + return ab.Addresses +} + +func getStreet(s Address) Street { + return s.Street +} + +var FirstAddressStreet = F.Flow3( + getAddresses, + A.Head[Address], + O.Map(getStreet), +) + +func Example_street() { + s := FirstAddressStreet(AddressBook{ + Addresses: A.From(Address{Street: Street{Name: "Mulburry", Number: 8402}, Postcode: "WC2N"}), + }) + fmt.Println(s) + + // Output: + // Some[mostlyadequate.Street]({Mulburry 8402}) +} diff --git a/samples/mostly-adequate/doc.go b/samples/mostly-adequate/doc.go new file mode 100644 index 0000000..b6369df --- /dev/null +++ b/samples/mostly-adequate/doc.go @@ -0,0 +1,19 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package mostlyadequate is meant to serve as a go "companion" resource to Professor [Frisby's Mostly Adequate Guide]. +// +// [Frisby's Mostly Adequate Guide]: https://github.com/MostlyAdequate/mostly-adequate-guide +package mostlyadequate