This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Guides

Comprehensive guides for using Morphir .NET

Explore our guides to learn how to use Morphir .NET effectively.

Available Guides

Best Practices

  • Immutability First: Always prefer immutable data structures
  • ADT Design: Use algebraic data types to make illegal states unrepresentable
  • Type Safety: Leverage C# 14 features for strong typing
  • Testing: Write comprehensive tests using TUnit and Reqnroll

1 - IR Modeling

Learn how to model Morphir IR in .NET

Overview

Morphir IR (Intermediate Representation) is the core data structure that represents your business logic. In Morphir .NET, we model the IR using C# record types and algebraic data types (ADTs).

Type Expressions

Type expressions represent the types in your Morphir model:

public abstract record TypeExpr
{
    public sealed record TInt() : TypeExpr;
    public sealed record TString() : TypeExpr;
    public sealed record TBool() : TypeExpr;
    public sealed record TTuple(IReadOnlyList<TypeExpr> Items) : TypeExpr;
    public sealed record TRecord(IReadOnlyDictionary<string, TypeExpr> Fields) : TypeExpr;
    public sealed record TFunc(TypeExpr Input, TypeExpr Output) : TypeExpr;
}

Value Expressions

Value expressions represent the actual values and computations:

public abstract record ValueExpr
{
    public sealed record Literal(LiteralValue Value) : ValueExpr;
    public sealed record Variable(string Name) : ValueExpr;
    public sealed record Lambda(string Parameter, TypeExpr ParameterType, ValueExpr Body) : ValueExpr;
    public sealed record Apply(ValueExpr Function, ValueExpr Argument) : ValueExpr;
}

Best Practices

  1. Use Records: Prefer record types for immutable data structures
  2. Pattern Matching: Use exhaustive pattern matching for ADTs
  3. Validation: Implement smart constructors for validated types
  4. Immutability: Keep all types immutable

Example

Here’s a complete example of modeling a simple function:

var addFunction = new ValueExpr.Lambda(
    Parameter: "x",
    ParameterType: new TypeExpr.TInt(),
    Body: new ValueExpr.Lambda(
        Parameter: "y",
        ParameterType: new TypeExpr.TInt(),
        Body: new ValueExpr.Apply(
            Function: new ValueExpr.Variable("+"),
            Argument: new ValueExpr.Tuple(new[]
            {
                new ValueExpr.Variable("x"),
                new ValueExpr.Variable("y")
            })
        )
    )
);

2 - Serialization

Working with JSON serialization in Morphir .NET

Overview

Morphir .NET provides JSON serialization support for Morphir IR, enabling interoperability with other Morphir tooling.

Basic Usage

Serializing IR to JSON

using Morphir.Core.IR;
using System.Text.Json;

var typeExpr = new TypeExpr.TInt();
var json = JsonSerializer.Serialize(typeExpr, new JsonSerializerOptions
{
    WriteIndented = true
});

Deserializing JSON to IR

var json = @"{""_tag"": ""TInt""}";
var typeExpr = JsonSerializer.Deserialize<TypeExpr>(json);

JSON Format

Morphir IR uses a tagged union format in JSON:

{
  "_tag": "TTuple",
  "items": [
    { "_tag": "TInt" },
    { "_tag": "TString" }
  ]
}

Custom Serialization

For custom serialization needs, you can implement your own converters:

public class CustomTypeExprConverter : JsonConverter<TypeExpr>
{
    // Implementation
}

Roundtrip Testing

Always test roundtrip serialization to ensure compatibility:

var original = new TypeExpr.TInt();
var json = JsonSerializer.Serialize(original);
var deserialized = JsonSerializer.Deserialize<TypeExpr>(json);
Assert.Equal(original, deserialized);

3 - Testing

Testing strategies and best practices for Morphir .NET

Overview

Morphir .NET supports multiple testing approaches to ensure code quality and correctness.

Unit Testing with TUnit

TUnit is the primary unit testing framework:

using TUnit.Assertions;
using TUnit.Core;

public class TypeExprTests
{
    [Test]
    public void TInt_Should_Be_Equal()
    {
        var type1 = new TypeExpr.TInt();
        var type2 = new TypeExpr.TInt();
        
        Assert.That(type1).IsEqualTo(type2);
    }
}

Behavior-Driven Development with Reqnroll

Reqnroll enables BDD-style testing:

Feature: Type Expression Creation
  Scenario: Create an integer type
    Given I want to create a type expression
    When I create a TInt
    Then it should be a valid type expression

Property-Based Testing

Use property-based testing for invariant validation:

[Property]
public bool RoundtripSerialization(TypeExpr typeExpr)
{
    var json = JsonSerializer.Serialize(typeExpr);
    var deserialized = JsonSerializer.Deserialize<TypeExpr>(json);
    return typeExpr.Equals(deserialized);
}

Contract Testing

Test compatibility with Morphir IR format:

[Test]
public void Should_Roundtrip_With_Morphir_Elm()
{
    // Load canonical IR sample
    var json = File.ReadAllText("samples/canonical.json");
    var ir = JsonSerializer.Deserialize<IR>(json);
    
    // Serialize back
    var roundtrip = JsonSerializer.Serialize(ir);
    
    // Verify compatibility
    Assert.That(roundtrip).IsValidJson();
}

Best Practices

  1. Exhaustive Testing: Test all ADT cases
  2. Edge Cases: Test boundary conditions
  3. Roundtrip Tests: Always test serialization roundtrips
  4. Property Tests: Use property-based testing for invariants
  5. Coverage: Maintain >= 80% code coverage