Newtype
Creates compile-time-only wrappers around existing value types.
A newtype lets TypeScript distinguish values with the same runtime shape, such
as two different ids that are both strings. The tag exists only in the type
system, so wrapping does not allocate a runtime object. This module includes
the base Newtype interface, wrapping and unwrapping helpers, optics, and
helpers for reusing carrier instances such as Equivalence, Order,
Combiner, and Reducer.
Constructors
makeCombiner
Lifts a Combiner for the carrier type into a Combiner for the newtype.
When to use
Use when you need to combine newtype-wrapped values with the carrier's combining logic, without manually unwrapping.
Details
The returned combiner delegates to the provided carrier combiner.
See
- makeReducer — lift a
Reducerfor the carrier
Signature
declare const makeCombiner: <N extends Newtype.Any>(combiner: Combiner.Combiner<Newtype.Carrier<N>>) => Combiner.Combiner<N>Example
(Combining newtypes)
import { Combiner, Newtype } from "effect"
interface Amount extends Newtype.Newtype<"Amount", number> {}
const sum = Combiner.make<number>((a, b) => a + b)const combiner = Newtype.makeCombiner<Amount>(sum)const iso = Newtype.makeIso<Amount>()
const total = combiner.combine(iso.set(10), iso.set(20))Newtype.value(total) // => 30makeEquivalence
Lifts an Equivalence for the carrier type into an Equivalence for the
newtype.
When to use
Use when you need equality for newtype-wrapped values to behave like equality for the wrapped carrier value, without manually unwrapping.
Details
The returned equivalence delegates to the provided carrier equivalence and has zero runtime cost beyond the underlying equivalence check.
See
- makeOrder — lift an
Orderfor the carrier
Signature
declare const makeEquivalence: <N extends Newtype.Any>(equivalence: Equivalence.Equivalence<Newtype.Carrier<N>>) => Equivalence.Equivalence<N>Example
(Comparing newtypes)
import { Equivalence, Newtype } from "effect"
interface Label extends Newtype.Newtype<"Label", string> {}
const eq = Newtype.makeEquivalence<Label>(Equivalence.String)const iso = Newtype.makeIso<Label>()
eq(iso.set("a"), iso.set("a")) // => trueeq(iso.set("a"), iso.set("b")) // => falseCreates an Optic.Iso for a newtype, providing both wrapping (set) and
unwrapping (get).
When to use
Use as the primary way to construct and deconstruct newtype values.
Details
The returned iso composes with other optics via the standard Optic API.
Both directions have zero runtime cost because they are identity casts.
See
- value — unwrap only
Signature
declare function makeIso<N extends Any>(): Iso<N, Carrier<N>>Example
(Wrapping and unwrapping with an iso)
import { Newtype } from "effect"
interface Label extends Newtype.Newtype<"Label", string> {}
const labelIso = Newtype.makeIso<Label>()
const label: Label = labelIso.set("world")const str: string = labelIso.get(label)str // => "world"Lifts an Order for the carrier type into an Order for the newtype.
When to use
Use when you need to sort newtype-wrapped values according to the ordering of the wrapped carrier value, without manually unwrapping.
Details
The returned order delegates to the provided carrier order.
See
- makeEquivalence — lift an
Equivalencefor the carrier
Signature
declare const makeOrder: <N extends Newtype.Any>(order: Order.Order<Newtype.Carrier<N>>) => Order.Order<N>Example
(Ordering newtypes)
import { Newtype, Order } from "effect"
interface Score extends Newtype.Newtype<"Score", number> {}
const ord = Newtype.makeOrder<Score>(Order.Number)const iso = Newtype.makeIso<Score>()
ord(iso.set(1), iso.set(2)) // => -1makeReducer
Lifts a Reducer for the carrier type into a Reducer for the newtype.
When to use
Use when you need to reduce a collection of newtype-wrapped values with the carrier's reducer, without manually unwrapping.
Details
The returned reducer delegates to the provided carrier reducer.
See
- makeCombiner — lift a
Combinerfor the carrier
Signature
declare const makeReducer: <N extends Newtype.Any>(reducer: Reducer.Reducer<Newtype.Carrier<N>>) => Reducer.Reducer<N>Example
(Reducing newtypes)
import { Newtype, Reducer } from "effect"
interface Score extends Newtype.Newtype<"Score", number> {}
const sum = Reducer.make<number>((a, b) => a + b, 0)const reducer = Newtype.makeReducer<Score>(sum)const iso = Newtype.makeIso<Score>()
const total = reducer.combineAll([iso.set(1), iso.set(2), iso.set(3)])Newtype.value(total) // => 6Getters
Unwraps a newtype value, returning the underlying carrier value.
When to use
Use when you need the carrier value from an existing newtype without constructing a new newtype value at the same call site.
Details
This has zero runtime cost because it is an identity cast.
See
- makeIso — two-way conversion (wrap and unwrap)
Signature
declare const value: <N extends Newtype.Any>(newtype: N) => Newtype.Carrier<N>Example
(Unwrapping a newtype)
import { Newtype } from "effect"
interface Label extends Newtype.Newtype<"Label", string> {}
const iso = Newtype.makeIso<Label>()const label = iso.set("hello")
const raw: string = Newtype.value(label)raw // => "hello"Models
A tagged interface that wraps a carrier type under a unique key, preventing accidental interchange of structurally identical values.
When to use
Use to define a newtype as an interface extending
Newtype<"MyKey", CarrierType> when structurally identical carrier types
should remain distinct in TypeScript.
Details
The tag is compile-time only, so no runtime wrapper is allocated. Use makeIso to create a two-way conversion, or value to unwrap.
See
Signature
interface Newtype<in out Key extends string, out Carrier> { readonly "~effect/Newtype": { readonly carrier: Carrier; readonly key: Key; };}Example
(Defining a newtype)
import { Newtype } from "effect"
interface UserId extends Newtype.Newtype<"UserId", number> {}interface OrderId extends Newtype.Newtype<"OrderId", number> {}
const userId = Newtype.makeIso<UserId>().set(1)// UserId and OrderId are not assignable to each other// even though both wrap `number`.// @ts-expect-errorconst orderId: OrderId = userId
Newtype.value(userId) // => 1