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
- Use Records: Prefer
record types for immutable data structures - Pattern Matching: Use exhaustive pattern matching for ADTs
- Validation: Implement smart constructors for validated types
- 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);
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
- Exhaustive Testing: Test all ADT cases
- Edge Cases: Test boundary conditions
- Roundtrip Tests: Always test serialization roundtrips
- Property Tests: Use property-based testing for invariants
- Coverage: Maintain >= 80% code coverage