---
title: "Language boundaries"
description: "Supported composition, current limitations, and alternatives."
image: "https://hem.run/social/hem.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://hem.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Language boundaries

Use this page to check whether a language form is supported and find an
alternative when it is not. The linked references give exact syntax and diagnostics.

## Supported composition

Structural-record patterns support whole and nested `as` aliases and record
alternatives at arm and field level. Alternatives bind the same names with
compatible checked types. The first complete match supplies the bindings;
a false guard advances to the next arm without retrying alternatives.

Control-flow values work in the ordinary and optional-call argument positions
listed in [control flow](/reference/control-flow). Optional short-circuiting
skips argument work. Synchronous postfix `catch` works in supported value
positions, including nested calls. Effect `catch` also supports expression use.

For exact pattern restrictions, see [pattern matching](/reference/pattern-matching).
Method-tagged templates and arbitrary statement-block positions are not generally
supported. Keep branches explicit and bind intermediate values in unsupported positions.

## Unsupported forms and alternatives

| Unsupported form | Alternative |
| --- | --- |
| Entirely unannotated `None` match inference | Supply a contextual type so Hem can select the nullish sentinel. |
| Class accessors, decorators, access modifiers, or declaration-only members | Use the supported [class subset](/reference/typescript-interop). |
| Arbitrary class extractors | Use `instanceof` and explicit property access. |
| First-class ranges | Use loop-head ranges in `for` and ordinary iterables elsewhere. |
| Destructuring a range-loop binder | A range produces one number per iteration: write `for index in start..end`. Destructure values from an ordinary iterable instead. |
| Partially annotated awaited `and` groups, including carrier groups | Annotate every member or omit every annotation. Keep the group to preserve concurrent evaluation and first-rejection behavior. |
| Mutation, annotations, or `else` on a wildcard discard | Use unmodified `let _ = value` to discard. Bind a named value when you need annotations, mutation, or recovery. |
| Catch-arm guards | Use explicit branching and rethrowing; unmatched failures must still propagate. |
| Group-level binding recovery | Recover individual values. |
| Direct `.hem` imports across tsconfig boundaries | Use emitted modules and declarations at package boundaries. |
| Index presence inferred from a bounds check | Keep explicit nullish handling. |
| Contextual cancellation `signal` | Use explicit cancellation-aware Effect lifts, with API-specific cleanup. |
| Custom discriminants on Hem-authored variants | Keep `_tag`, or use imported TypeScript unions, which can have other discriminant fields. |
| Labelled positional arguments | Use ordinary trailing options objects. |
| Native dprint integration | Use Hem's formatter. |
| Static cyclic-equality detection | Use derived equality for acyclic values and a custom comparator for intentional cycles. |

## Other boundaries

Runtime debugging uses separate version-3 source maps in generated siblings. Node can consume them with `--enable-source-maps`; Vite composes the TypeScript/JSX and bundle maps. A plain `tsc` JavaScript build needs explicit map composition. Generated scaffolding maps to the nearest authored construct rather than pretending every generated operation exists in Hem. See [runtime debugging](/reference/cli#runtime-debugging).

Hem functions are arrows and modules are ESM. Constructor parameter properties
are excluded by the erasable-output contract; use explicit fields and constructor
assignments. Unsupported source forms can remain in separately compiled
TypeScript modules, subject to the same [foreign trust boundaries](/reference/typescript-interop)
as other imports. Moving a refused unsafe operation into a shim does not make it checked.

Runtime schemas are explicit library values, not automatic `deriving Schema`
output. Retry, scheduling, Layer architecture, fiber lifetime, and cancellation
policy remain explicit Effect or library code.

Migration acceptance is whole-file. Manually migrate or retain a TypeScript
module when a file cannot be accepted; partial output is not a successful migration.

Source: https://hem.run/reference/language-boundaries/index.mdx
