Skip to content

OlegStotsky/go-monads

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-monads

go-monads is a library that implements basic Haskell monads, based on Go 2 Generics Draft.

Table of Contents

Maybe[T]

Maybe represents type that can either have value or be empty

Create Maybe

m1 := maybe.Return(5)

or

val := new(int)
m2 := maybe.OfNullable[int](val)

or

m3 := maybe.Empty[any]()

Transform Maybe into new value

x := maybe.Return[int](5)
fmt.Println(maybe.Map[int, string](x, strconv.Itoa).Get())

or


func divideBy(x int, y int) maybe.Maybe[int] {
    if y == 0 {
        return maybe.Empty[int]()
    }
    return maybe.Return(x / y)
}

four := maybe.Map(divideBy(6, 3), func(x int) int { return x * 2 })    //four equals Just{obj: 4}
nothing := maybe.Map(divideBy(6, 0), func(x int) int { return x * 2 }) //nothing equals Nothing{}

or

x := 0
y := 7
m5 := maybe.FlatMap[int, int](divideBy(6, x), func(x int) maybe.Maybe[int] { return divideBy(x, y) }) //x, y are some unknown integers, might be zeros

IO[T]

IO encodes computation that potentially contains side effects as pure value

Create IO

//countNumberOfBytesInFile is an effectfull computation that returns number of bytes in file
func countNumberOfBytesInFile(f os.File) int {
  fi, _ := f.Stat()
  return fi.Size()
}

f, err := os.CreateTemp("./", "ab*")
if err != nil {
    log.Fatalln(err.Error())
}
defer os.Remove(f.Name())
f.WriteString("hello world")

x := io.Return[int64](func() int64 { return countNumberOfBytesInFile(f) })
fmt.Println(x.UnsafePerformIO())

Transform IO into new value

func printVal[T any](val T) io.IO[util.Unit] {
    return io.Return[util.Unit](func() util.Unit {
        fmt.Println(val)
        return util.UnitVar
    })
}

func main() {
    x2 := io.FlatMap(x, printVal[int64])
    fmt.Println(x2.UnsafePerformIO())
}

Either(L, R)

Either represents value that can be of one of two types

Create Either

x := either.AsRight(5)
y := either.AsLeft("xyz")
fmt.Println(either.ToMaybe(x).Get()) // prints 5, nil
fmt.Println(either.ToMaybe(y).Get()) // prints <nil> Trying to get from Nothing

Example of usage

For example, we want to check whether file contains substring 'go'

Some helper functions

func toString(b []byte) string {
    return string(b)
}

func contains(text string) bool {
    return strings.Contains(text, "go")
}

Standard way

func ContainsGo(reader io.Reader) (bool, error) {
    bytes, err:= ioutil.ReadAll(reader)
    if err != nil {
    return false, err
    }
    text := toString(bytes)
    return contains(text), nil
}

ReadAllE - function that call ReaderAll and wrap result to Either

func ReadAllE(reader io.Reader) either.Either[error, []byte] {
	x := either.FromErrorable[[]byte](ioutil.ReadAll(reader))
	return x
}

Generic way

func ContainsGoE(reader io.Reader) either.Either[error, bool] {
    return either.Map(either.Map(ReadAllE(reader), toString), contains)
}

State(S, A)

Represent function func (S) (A, S), where S is state, A is result

Create State

x := state.Return[any, int](5)
y := state.Put(100)
fmt.Println(x.RunState(nil)) // prints 5
fmt.Println(y.RunState(6))   // prints 100

Transform State into new value

func CountZeroes(ints []int) int {
    counter := state.Put[int](0)
    for _, x := range ints {
        if x == 0 {
            s := state.BindI[int, util.Unit, int](counter, state.Get[int]())
            counter = state.FlatMap[int, int, util.Unit](s, func(c int) state.State[int, util.Unit] { return state.Put[int](c + 1) })
        }
    }
    _, s := counter.RunState(0)
    return s
}