Skip to content

Commit

Permalink
Merge pull request #12 from IBM/cleue-more-iterator
Browse files Browse the repository at this point in the history
Add more iterators and auto generate sequence operations
  • Loading branch information
CarstenLeue committed Jul 27, 2023
2 parents 41b792b + dc836fd commit 903669c
Show file tree
Hide file tree
Showing 55 changed files with 12,776 additions and 8,596 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,41 @@ This library aims to provide a set of data types and functions that make it easy

The library itself also comprises many small functions, but it's admittedly harder to maintain than code that uses it. However this asymmetry is intended because it offloads complexity from users into a central component.

## Comparation to Idiomatic Go

In this section we discuss how the functional APIs differ from idiomatic go function signatures and how to convert back and forth.

### Pure functions

Pure functions are functions that take input parameters and that compute an output without changing any global state and without mutating the input parameters. They will always return the same output for the same input.

#### Without Errors

If your pure function does not return an error, the idiomatic signature is just fine and no changes are required.

#### With Errors

If your pure function can return an error, then it will have a `(T, error)` return value in idiomatic go. In functional style the return value is [Either[error, T]](./either/) because function composition is easier with such a return type. Use the `EitherizeXXX` methods in ["github.com/IBM/fp-go/either"](./either/) to convert from idiomatic to functional style and `UneitherizeXXX` to convert from functional to idiomatic style.

### Effectful functions

An effectful function (or function with a side effect) is one that changes data outside the scope of the function or that does not always produce the same output for the same input (because it depends on some external, mutable state). There is no special way in idiomatic go to identify such a function other than documentation. In functional style we represent them as functions that do not take an input but that produce an output. The base type for these functions is [IO[T]](./io) because in many cases such functions represent `I/O` operations.

#### Without Errors

If your effectful function does not return an error, the functional signature is [IO[T]](./io)

#### With Errors

If your effectful function can return an error, the functional signature is [IOEither[error, T]](./ioeither). Use `EitherizeXXX` from ["github.com/IBM/fp-go/ioeither"](./ioeither/) to convert an idiomatic go function to functional style.

### Go Context

Functions that take a [context](https://pkg.go.dev/context) are per definition effectful because they depend on the context parameter that is designed to be mutable (it can e.g. be used to cancel a running operation). Furthermore in idiomatic go the parameter is typically passed as the first parameter to a function.

In functional style we isolate the [context](https://pkg.go.dev/context) and represent the nature of the effectful function as an [IOEither[error, T]](./ioeither). The resulting type is [ReaderIOEither[T]](./readerioeither), a function taking a [context](https://pkg.go.dev/context) that returns a function without parameters returning an [Either[error, T]](./either/). Use the `EitherizeXXX` methods from ["github.com/IBM/fp-go/context/readerioeither"](./context/readerioeither/) to convert an idiomatic go function with a [context](https://pkg.go.dev/context) to functional style.


## Implementation Notes

### Generics
Expand Down
2 changes: 1 addition & 1 deletion array/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func Intersperse[A any](middle A) func([]A) []A {
}

func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A {
concatAll := ConcatAll[A](m)(m.Empty())
concatAll := ConcatAll[A](m)
return func(middle A) func([]A) A {
return Match(m.Empty, F.Flow2(Intersperse(middle), concatAll))
}
Expand Down
7 changes: 3 additions & 4 deletions array/magma.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
package array

import (
F "github.com/IBM/fp-go/function"
M "github.com/IBM/fp-go/magma"
M "github.com/IBM/fp-go/monoid"
)

func ConcatAll[A any](m M.Magma[A]) func(A) func([]A) A {
return F.Bind1st(Reduce[A, A], m.Concat)
func ConcatAll[A any](m M.Monoid[A]) func([]A) A {
return Reduce(m.Concat, m.Empty())
}
8 changes: 4 additions & 4 deletions array/magma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ import (

"github.com/stretchr/testify/assert"

M "github.com/IBM/fp-go/magma"
M "github.com/IBM/fp-go/monoid"
)

var subInt = M.MakeMagma(func(first int, second int) int {
var subInt = M.MakeMonoid(func(first int, second int) int {
return first - second
})
}, 0)

func TestConcatAll(t *testing.T) {

var subAll = ConcatAll(subInt)(0)
var subAll = ConcatAll(subInt)

assert.Equal(t, subAll([]int{1, 2, 3}), -6)

Expand Down
3 changes: 3 additions & 0 deletions cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@ func Commands() []*C.Command {
ContextReaderIOEitherCommand(),
ReaderIOEitherCommand(),
ReaderCommand(),
IOEitherCommand(),
IOCommand(),
IOOptionCommand(),
}
}
136 changes: 136 additions & 0 deletions cli/io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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 cli

import (
"fmt"
"log"
"os"
"path/filepath"
"time"

A "github.com/IBM/fp-go/array"
C "github.com/urfave/cli/v2"
)

func nonGenericIO(param string) string {
return fmt.Sprintf("IO[%s]", param)
}

func genericIO(param string) string {
return fmt.Sprintf("func() %s", param)
}

var extrasIO = A.Empty[string]()

func generateIOSequenceT(f, fg *os.File, i int) {
generateGenericSequenceT(nonGenericIO, genericIO, extrasIO)(f, fg, i)
}

func generateIOSequenceTuple(f, fg *os.File, i int) {
generateGenericSequenceTuple(nonGenericIO, genericIO, extrasIO)(f, fg, i)
}

func generateIOTraverseTuple(f, fg *os.File, i int) {
generateGenericTraverseTuple(nonGenericIO, genericIO, extrasIO)(f, fg, i)
}

func generateIOHelpers(filename string, count int) error {
dir, err := os.Getwd()
if err != nil {
return err
}
absDir, err := filepath.Abs(dir)
if err != nil {
return err
}
pkg := filepath.Base(absDir)
f, err := os.Create(filepath.Clean(filename))
if err != nil {
return err
}
defer f.Close()
// construct subdirectory
genFilename := filepath.Join("generic", filename)
err = os.MkdirAll("generic", os.ModePerm)
if err != nil {
return err
}
fg, err := os.Create(filepath.Clean(genFilename))
if err != nil {
return err
}
defer fg.Close()

// log
log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count)

// some header
fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.")
fmt.Fprintln(f, "// This file was generated by robots at")
fmt.Fprintf(f, "// %s\n", time.Now())

fmt.Fprintf(f, "package %s\n\n", pkg)

fmt.Fprintf(f, `
import (
G "github.com/IBM/fp-go/%s/generic"
T "github.com/IBM/fp-go/tuple"
)
`, pkg)

// some header
fmt.Fprintln(fg, "// Code generated by go generate; DO NOT EDIT.")
fmt.Fprintln(fg, "// This file was generated by robots at")
fmt.Fprintf(fg, "// %s\n", time.Now())

fmt.Fprintf(fg, "package generic\n\n")

fmt.Fprintf(fg, `
import (
T "github.com/IBM/fp-go/tuple"
A "github.com/IBM/fp-go/internal/apply"
)
`)

for i := 1; i <= count; i++ {
// sequenceT
generateIOSequenceT(f, fg, i)
// sequenceTuple
generateIOSequenceTuple(f, fg, i)
// traverseTuple
generateIOTraverseTuple(f, fg, i)
}

return nil
}

func IOCommand() *C.Command {
return &C.Command{
Name: "io",
Usage: "generate code for IO",
Flags: []C.Flag{
flagCount,
flagFilename,
},
Action: func(ctx *C.Context) error {
return generateIOHelpers(
ctx.String(keyFilename),
ctx.Int(keyCount),
)
},
}
}
Loading

0 comments on commit 903669c

Please sign in to comment.