ioref

package
v2.1.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 17, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package ioref provides mutable references in the IO monad.

Overview

IORef represents a mutable reference that can be read and written within IO computations. It provides thread-safe access to shared mutable state using read-write locks, making it safe to use across multiple goroutines.

This package is inspired by Haskell's Data.IORef module and provides a functional approach to managing mutable state with explicit IO effects.

Core Operations

The package provides four main operations:

  • MakeIORef: Creates a new IORef with an initial value
  • Read: Atomically reads the current value from an IORef
  • Write: Atomically writes a new value to an IORef
  • Modify: Atomically modifies the value using a transformation function
  • ModifyWithResult: Atomically modifies the value and returns a computed result

Thread Safety

All operations on IORef are thread-safe:

  • Read operations use read locks, allowing multiple concurrent readers
  • Write and Modify operations use write locks, ensuring exclusive access
  • The underlying sync.RWMutex ensures proper synchronization

Basic Usage

Creating and using an IORef:

import (
    "github.com/IBM/fp-go/v2/ioref"
)

// Create a new IORef
ref := ioref.MakeIORef(42)()

// Read the current value
value := ioref.Read(ref)()  // 42

// Write a new value
ioref.Write(100)(ref)()

// Read the updated value
newValue := ioref.Read(ref)()  // 100

Modifying Values

Use Modify to transform the value in place:

ref := ioref.MakeIORef(10)()

// Double the value
ioref.Modify(func(x int) int { return x * 2 })(ref)()

// Chain multiple modifications
ioref.Modify(func(x int) int { return x + 5 })(ref)()
ioref.Modify(func(x int) int { return x * 3 })(ref)()

result := ioref.Read(ref)()  // (10 * 2 + 5) * 3 = 75

Atomic Modify with Result

Use ModifyWithResult when you need to both transform the value and compute a result from the old value in a single atomic operation:

ref := ioref.MakeIORef(42)()

// Increment and return the old value
oldValue := ioref.ModifyWithResult(func(x int) pair.Pair[int, int] {
    return pair.MakePair(x+1, x)
})(ref)()

// oldValue is 42, ref now contains 43

This is particularly useful for implementing counters, swapping values, or any operation where you need to know the previous state.

Concurrent Usage

IORef is safe to use across multiple goroutines:

ref := ioref.MakeIORef(0)()

// Multiple goroutines can safely modify the same IORef
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        ioref.Modify(func(x int) int { return x + 1 })(ref)()
    }()
}
wg.Wait()

result := ioref.Read(ref)()  // 100

Comparison with Haskell's IORef

This implementation provides the following Haskell IORef operations:

  • newIORef → MakeIORef
  • readIORef → Read
  • writeIORef → Write
  • modifyIORef → Modify
  • atomicModifyIORef → ModifyWithResult

The main difference is that Go's implementation uses explicit locking (sync.RWMutex) rather than relying on the runtime's STM (Software Transactional Memory) as Haskell does.

Performance Considerations

IORef operations are highly optimized:

  • Read operations are very fast (~5ns) and allow concurrent access
  • Write and Modify operations are slightly slower (~7-8ns) due to exclusive locking
  • ModifyWithResult is marginally slower (~9ns) due to tuple creation
  • All operations have zero allocations in the common case

For high-contention scenarios, consider:

  • Using multiple IORefs to reduce lock contention
  • Batching modifications when possible
  • Using Read locks for read-heavy workloads

Examples

Counter with atomic increment:

counter := ioref.MakeIORef(0)()

increment := func() int {
    return ioref.ModifyWithResult(func(x int) pair.Pair[int, int] {
        return pair.MakePair(x+1, x+1)
    })(counter)()
}

id1 := increment()  // 1
id2 := increment()  // 2
id3 := increment()  // 3

Shared configuration:

type Config struct {
    MaxRetries int
    Timeout    time.Duration
}

configRef := ioref.MakeIORef(Config{
    MaxRetries: 3,
    Timeout:    5 * time.Second,
})()

// Update configuration
ioref.Modify(func(c Config) Config {
    c.MaxRetries = 5
    return c
})(configRef)()

// Read configuration
config := ioref.Read(configRef)()

Stack implementation:

type Stack []int

stackRef := ioref.MakeIORef(Stack{})()

push := func(value int) {
    ioref.Modify(func(s Stack) Stack {
        return append(s, value)
    })(stackRef)()
}

pop := func() option.Option[int] {
    return ioref.ModifyWithResult(func(s Stack) pair.Pair[Stack, option.Option[int]] {
        if len(s) == 0 {
            return pair.MakePair(s, option.None[int]())
        }
        return pair.MakePair(s[:len(s)-1], option.Some(s[len(s)-1]))
    })(stackRef)()
}

Package ioref provides mutable references in the IO monad.

IORef represents a mutable reference that can be read and written within IO computations. It provides thread-safe access to shared mutable state using read-write locks.

This is inspired by Haskell's Data.IORef module and provides a functional approach to managing mutable state with explicit IO effects.

Example usage:

// Create a new IORef
ref := ioref.MakeIORef(42)()

// Read the current value
value := ioref.Read(ref)()  // 42

// Write a new value
ioref.Write(100)(ref)()

// Modify the value
ioref.Modify(func(x int) int { return x * 2 })(ref)()

// Read the modified value
newValue := ioref.Read(ref)()  // 200

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Modify

func Modify[A any](f Endomorphism[A]) io.Kleisli[IORef[A], A]

Modify atomically modifies the value in an IORef using the given function.

This function returns a Kleisli arrow that takes an IORef and produces an IO computation that applies the transformation function to the current value. The modification is atomic and thread-safe, using a write lock to ensure exclusive access during the read-modify-write cycle.

Parameters:

  • f: An endomorphism (function from A to A) that transforms the current value

Returns:

  • A Kleisli arrow from IORef[A] to IO[IORef[A]]

Example:

ref := ioref.MakeIORef(42)()

// Double the value
ioref.Modify(func(x int) int { return x * 2 })(ref)()

// Chain multiple modifications
pipe.Pipe2(
    ref,
    ioref.Modify(func(x int) int { return x + 10 }),
    io.Chain(ioref.Modify(func(x int) int { return x * 2 })),
)()

func ModifyWithResult

func ModifyWithResult[A, B any](f func(A) Pair[A, B]) io.Kleisli[IORef[A], B]

ModifyWithResult atomically modifies the value in an IORef and returns both the new value and an additional result computed from the old value.

This function is useful when you need to both transform the stored value and compute some result based on the old value in a single atomic operation. It's similar to Haskell's atomicModifyIORef.

Parameters:

  • f: A function that takes the old value and returns a Pair of (new value, result)

Returns:

  • A Kleisli arrow from IORef[A] to IO[B] that produces the result

Example:

ref := ioref.MakeIORef(42)()

// Increment and return the old value
oldValue := ioref.ModifyWithResult(func(x int) pair.Pair[int, int] {
    return pair.MakePair(x+1, x)
})(ref)()  // Returns 42, ref now contains 43

// Swap and return the old value
old := ioref.ModifyWithResult(func(x int) pair.Pair[int, int] {
    return pair.MakePair(100, x)
})(ref)()  // Returns 43, ref now contains 100

func Write

func Write[A any](a A) io.Kleisli[IORef[A], A]

Types

type Endomorphism

type Endomorphism[A any] = endomorphism.Endomorphism[A]

Endomorphism represents a function from A to A. It's commonly used with Modify to transform the value in an IORef.

type IO

type IO[A any] = io.IO[A]

IO represents a synchronous computation that may have side effects. It's a function that takes no arguments and returns a value of type A.

func MakeIORef

func MakeIORef[A any](a A) IO[IORef[A]]

MakeIORef creates a new IORef containing the given initial value.

This function returns an IO computation that, when executed, creates a new mutable reference initialized with the provided value. The reference is thread-safe and can be safely shared across goroutines.

Parameters:

  • a: The initial value to store in the IORef

Returns:

  • An IO computation that produces a new IORef[A]

Example:

// Create a new IORef with initial value 42
refIO := ioref.MakeIORef(42)
ref := refIO()  // Execute the IO to get the IORef

// Create an IORef with a string
strRefIO := ioref.MakeIORef("hello")
strRef := strRefIO()

func Read

func Read[A any](ref IORef[A]) IO[A]

Read atomically reads the current value from an IORef.

This function returns an IO computation that reads the value stored in the IORef. The read operation is thread-safe, using a read lock that allows multiple concurrent readers but excludes writers.

Parameters:

  • ref: The IORef to read from

Returns:

  • An IO computation that produces the current value of type A

Example:

ref := ioref.MakeIORef(42)()

// Read the current value
value := ioref.Read(ref)()  // 42

// Use in a pipeline
result := pipe.Pipe2(
    ref,
    ioref.Read[int],
    io.Map(func(x int) int { return x * 2 }),
)()

type IORef

type IORef[A any] = *ioRef[A]

IORef represents a mutable reference to a value of type A. Operations on IORef are thread-safe and performed within the IO monad.

IORef provides a way to work with mutable state in a functional style, where mutations are explicit and contained within IO computations.

type Pair

type Pair[A, B any] = pair.Pair[A, B]

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL