Library Comparison

January 13, 2026 · View on GitHub

Table of Contents


UniFP vs Unity-NOPE

Performance Comparison

UniFP improves upon NOPE's performance issues.

1. Zero-GC Struct Design

  • UniFP: All core types are readonly struct allocated on stack
  • Unity-NOPE: Result<T,E> is readonly struct, but generic error type E can cause boxing

2. Delegate Caching

  • UniFP: DelegateCache reuses frequently-used lambdas → prevents heap allocation
  • Unity-NOPE: No delegate caching → repeated creation in Update loops

3. ResultPool & ListPool

  • UniFP: Built-in object pooling for high-frequency scenarios
  • Unity-NOPE: No pooling mechanism

Feature Comparison

UniFP implements all core features from NOPE, but with C#-friendly naming. UniFP's Then = NOPE's Bind, UniFP's Filter = NOPE's Ensure

High-Level Feature Comparison

FeatureUniFPUnity-NOPE
Result MonadResult<T> (single type)Result<T,E> (typed errors)
Option MonadOption<T>Maybe<T>
Async SupportUniTask + AwaitableUniTask + Awaitable
Error TypeErrorCode (struct, efficient)E (generic, flexible but can box)
Pipeline OperationsThen, Map, Filter, Recover, Do...Bind, Map, Ensure, Tap, Finally...
Retry LogicRetry, RetryWithBackoff, RepeatNot supported
Result CombinationResultCombinators (Combine, Zip...)Result.Combine, CombineValues
Collection TraversalSelectResults, CombineAll, PartitionLimited
Performance OptimizationDelegateCache, Pools, Span extensionsBasic structs only
Debugging ToolsTrace, Breakpoint, SafeExecutorBasic Match only

Detailed Method Comparison

Method CategoryUniFPUnity-NOPEDescription
Basic Transformations
MapTransform value on success (T → U)
Bind (Then)ThenBindChain functions returning Result (T → Result<U>)
Filter⚠️ EnsureConditional validation (fails to Failure)
Error Handling
MapError⚠️ ErrorCode onlyTransform error type
Recover⚠️ OrElseReplace failure with default value
IfFailed⚠️ OrProvide alternative Result on failure
CatchIntercept specific errors for recovery
Side Effects
Do⚠️ TapExecute side effect on success (no value change)
DoStrictAbort pipeline if side effect fails
IfFailed(Action)Execute side effect only on failure
Conditional Execution
ThenIfConditional Then
MapIfConditional Map
Where⚠️ Option only✅ Maybe onlyCondition filtering
Result Inspection
MatchExecute different functions based on success/failure
Finally⚠️ Similar to MatchChain termination and final processing
Assert⚠️ Similar to EnsureCondition validation (for debugging)
Async (UniTask/Awaitable)
ThenAsync⚠️ Bind overloadAsync Result chaining
MapAsync⚠️ Map overloadAsync value transformation
FilterAsyncAsync condition validation
DoAsyncAsync side effects
TryAsync⚠️ OfConvert exceptions to Result (async)
Resilience
RetryRetry on failure
RetryAsyncAsync retry
RetryWithBackoffExponential backoff retry
RepeatRequire N consecutive successes
Result Combination
CombineCombine multiple Results
Zip⚠️ CombineValuesCombine multiple Results into tuple
CombineAllList<Result> → Result<List>
PartitionSeparate success/failure
Collection Extensions
SelectResultsCollection → List<Result>, abort on failure
FilterResultsConditional filtering + Result return
FoldCollection aggregation (returns Result)
AggregateResultsComplex aggregation logic
Creation Helpers
SuccessCreate success Result
FailureCreate failure Result
FromValue⚠️ implicitCreate Result from value
SuccessIf⚠️ Similar to FilterConditional success/failure creation
FailureIf⚠️ Opposite of FilterConditional failure/success creation
Of⚠️ TryException → Result conversion
Safe Operations
BindSafeBind with exception handling
MapSafeMap with exception handling
TapSafeTap with exception handling
Debugging
TraceTrace pipeline steps
TraceWithTrace with custom message
TraceOnFailureTrace only on failure
BreakpointSet debugger breakpoint

Legend:

  • ✅ Fully supported
  • ⚠️ Partially supported or provided with different name
  • ❌ Not supported

Error Typing: Unnecessary in 99% of Cases

Unity-NOPE allows error typing with Result<T,E>, but this is over-engineering for most Unity game development:

Why Typed Errors Are Unnecessary:

  • Unity game logic mainly cares about "Did it succeed? Did it fail?"
  • Error messages are more useful than types (for debugging/logging)
  • Typed errors increase generic parameters → code complexity rises
  • Most failures are simple categories like "resource load failed", "validation failed"

UniFP's Approach: ErrorCode Struct

// UniFP: Efficient and clear error categorization
var result = LoadAsset()
    .Filter(x => x != null, ErrorCode.NotFound)
    .Then(ValidateAsset);  // Can return ErrorCode.ValidationFailed

if (result.IsFailure)
{
    Debug.LogError($"[{result.ErrorCode.Category}] {result.Error}");
    // [Resource] Asset not found: player_model.prefab
}

The 1% Case: When Type-Safe Errors Are Needed

For complex domain logic where typed errors are truly necessary:

// Method 1: Custom ErrorCode
public static class PaymentErrors
{
    public static readonly ErrorCode InsufficientFunds = ErrorCode.Custom(1001, "Payment");
    public static readonly ErrorCode InvalidCard = ErrorCode.Custom(1002, "Payment");
    public static readonly ErrorCode NetworkTimeout = ErrorCode.Custom(1003, "Payment");
}

var paymentResult = ProcessPayment()
    .Recover(code => code == PaymentErrors.NetworkTimeout 
        ? RetryPayment() 
        : RefundUser());

// Method 2: Discriminated Union Pattern (C# 9.0+)
public record PaymentError
{
    public record InsufficientFunds(decimal Required, decimal Available) : PaymentError;
    public record InvalidCard(string CardNumber) : PaymentError;
    public record NetworkTimeout(int Attempts) : PaymentError;
}

// Serialize to Result's Error message
var result = payment switch
{
    PaymentError.InsufficientFunds e => 
        Result<Payment>.Failure(ErrorCode.Custom(1001, "Payment"), 
                                $"Insufficient: {e.Required - e.Available} more needed"),
    // ...
};

UniFP vs language-ext

Why Not Use language-ext Directly in Unity?

language-ext is the best functional library in the .NET ecosystem, but it's not suitable for Unity.

1. No Unity Runtime Optimization

  • language-ext is designed for general .NET
  • Many types are class-based → increased GC pressure
  • Potential compatibility issues with Unity's IL2CPP AOT compilation

2. Overwhelming Feature Complexity

  • 100+ monads and transformers
  • Higher-kinded types simulation (complex generic patterns)
  • Unnecessary for game dev: Parsec, Lenses, Free monads, etc.

3. Learning Curve

  • Haskell-style naming conventions (camelCase static functions)
  • Complex abstractions in Trait system
  • Excessive functional concepts unfamiliar to Unity developers

4. Performance Overhead

  • Indirect calls due to high abstraction
  • Difficult to identify hot paths in Unity Profiler

Feature Comparison

Categorylanguage-extUniFPUnity Game Dev Perspective
Core MonadsOption, Either, Try, Validation, FinResult, Option, NonEmptyUniFP provides Unity-specific minimal set ✅
Immutable CollectionsArr, Lst, Seq, Map, HashMap, Set...Standard C# collections + extensionslanguage-ext superior but excessive for Unity ⚠️
AsyncIO monad, Eff, Pipes, StreamTAsyncResult (UniTask/Awaitable)UniFP better Unity ecosystem integration ✅
Error HandlingEither<L,R>, Validation<E,S>, FinResult + ErrorCodeUniFP simpler and clearer ✅
Parser CombinatorsParsec (full implementation)Not supportedUnnecessary for games (language-ext wins) ❌
Lenses & OpticsFull supportNot supportedExcessive for games (Unreal's FProperty more suitable) ❌
Atomic ConcurrencyAtom, Ref, AtomHashMapNot supportedUnity is single-threaded focused, use C# standard if needed ⚠️
PerformanceOverhead from high abstractionZero-GC structs, pooling optimizationUniFP optimized for Unity ✅
Learning CurveSteep (requires Haskell background)Gentle (C# LINQ experience sufficient)UniFP better accessibility ✅