Repository: gcanti/io-ts
Pull request #567 (2021-02-17 16:34:13)

version 2.2.15

fbunau: I'm pretty new to this library, so excuse the possible ignorance.

Can this new readonly combinator be used to add "_tag" values ? 
I really like for the stable part of the library that you can use "io-ts-extra" for pattern matching. But I am completely lost on how this would work with the new Decoder. I tried adding _tag, but can't get it to work, as I want it to just be a tag, not a decoding requirement. I'm thinking this new combinator would help with that ? 
gcanti: > Can this new readonly combinator be used to add "_tag" values ?

No, the `readonly` combinator just makes the `A` type readonly:

```ts
import * as D from 'io-ts/Decoder'

const d1 = D.struct({
  a: D.string
})
type D1 = D.TypeOf<typeof d1>
/*
type D1 = {
    a: string;
}
*/
const d2 = D.readonly(d1)
type D2 = D.TypeOf<typeof d2>
/*
type D2 = {
    readonly a: string;
}
*/
```

> I am completely lost on how this would work with the new Decoder

Ongoing discussion [here](https://github.com/gcanti/io-ts/issues/453#issuecomment-755318009)
fbunau: Thanks for the quick reply. I just realized that Decoder has the "sum" (tagged union) combinator. It does what I was looking for.

// readonly combinator is good. I use these models for data classes, all fields are used in a readonly way. good usecase :+1: 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c1fd36..74232e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,17 @@
 **Note**: Gaps between patch versions are faulty/broken releases.
 **Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.
 
+# 2.2.15
+
+- **Experimental**
+  - `Schemable`
+    - (\*) add `readonly` combinator (@gcanti)
+    - (\*) add `struct` combinator (@gcanti)
+  - deprecate `type` in favour of `struct` (@gcanti)
+  - deprecate `fromType` in favour of `fromStruct` (@gcanti)
+
+(\*) breaking change
+
 # 2.2.14
 
 - **Experimental**
diff --git a/docs/modules/Codec.ts.md b/docs/modules/Codec.ts.md
index 8953e71..de0027c 100644
--- a/docs/modules/Codec.ts.md
+++ b/docs/modules/Codec.ts.md
@@ -27,9 +27,9 @@ Added in v2.2.3
   - [fromArray](#fromarray)
   - [fromPartial](#frompartial)
   - [fromRecord](#fromrecord)
+  - [fromStruct](#fromstruct)
   - [fromSum](#fromsum)
   - [fromTuple](#fromtuple)
-  - [fromType](#fromtype)
   - [intersect](#intersect)
   - [lazy](#lazy)
   - [mapLeftWithInput](#mapleftwithinput)
@@ -37,9 +37,11 @@ Added in v2.2.3
   - [partial](#partial)
   - [record](#record)
   - [refine](#refine)
+  - [struct](#struct)
   - [sum](#sum)
   - [tuple](#tuple)
-  - [type](#type)
+  - [~~fromType~~](#fromtype)
+  - [~~type~~](#type)
 - [constructors](#constructors)
   - [fromDecoder](#fromdecoder)
   - [literal](#literal)
@@ -137,6 +139,18 @@ export declare function fromRecord<I, O, A>(
 
 Added in v2.2.3
 
+## fromStruct
+
+**Signature**
+
+```ts
+export declare function fromStruct<P extends Record<string, Codec<any, any, any>>>(
+  properties: P
+): Codec<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
+```
+
+Added in v2.2.15
+
 ## fromSum
 
 **Signature**
@@ -167,18 +181,6 @@ export declare const fromTuple: <C extends readonly Codec<any, any, any>[]>(
 
 Added in v2.2.8
 
-## fromType
-
-**Signature**
-
-```ts
-export declare function fromType<P extends Record<string, Codec<any, any, any>>>(
-  properties: P
-): Codec<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
-```
-
-Added in v2.2.8
-
 ## intersect
 
 **Signature**
@@ -260,6 +262,18 @@ export declare const refine: <A, B extends A>(
 
 Added in v2.2.3
 
+## struct
+
+**Signature**
+
+```ts
+export declare function struct<P extends Record<string, Codec<unknown, any, any>>>(
+  properties: P
+): Codec<unknown, { [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
+```
+
+Added in v2.2.15
+
 ## sum
 
 **Signature**
@@ -286,14 +300,26 @@ export declare function tuple<C extends ReadonlyArray<Codec<unknown, any, any>>>
 
 Added in v2.2.3
 
-## type
+## ~~fromType~~
+
+Use `fromStruct` instead.
 
 **Signature**
 
 ```ts
-export declare function type<P extends Record<string, Codec<unknown, any, any>>>(
-  properties: P
-): Codec<unknown, { [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
+export declare const fromType: typeof fromStruct
+```
+
+Added in v2.2.8
+
+## ~~type~~
+
+Use `struct` instead.
+
+**Signature**
+
+```ts
+export declare const type: typeof struct
 ```
 
 Added in v2.2.3
diff --git a/docs/modules/Decoder.ts.md b/docs/modules/Decoder.ts.md
index 866bb53..347edd7 100644
--- a/docs/modules/Decoder.ts.md
+++ b/docs/modules/Decoder.ts.md
@@ -37,22 +37,25 @@ Added in v2.2.7
   - [fromArray](#fromarray)
   - [fromPartial](#frompartial)
   - [fromRecord](#fromrecord)
+  - [fromStruct](#fromstruct)
   - [fromSum](#fromsum)
   - [fromTuple](#fromtuple)
-  - [fromType](#fromtype)
   - [intersect](#intersect)
   - [lazy](#lazy)
   - [mapLeftWithInput](#mapleftwithinput)
   - [nullable](#nullable)
   - [parse](#parse)
   - [partial](#partial)
+  - [readonly](#readonly)
   - [record](#record)
   - [refine](#refine)
+  - [struct](#struct)
   - [sum](#sum)
   - [tuple](#tuple)
-  - [type](#type)
   - [union](#union)
   - [withMessage](#withmessage)
+  - [~~fromType~~](#fromtype)
+  - [~~type~~](#type)
 - [constructors](#constructors)
   - [fromGuard](#fromguard)
   - [fromRefinement](#fromrefinement)
@@ -222,6 +225,18 @@ export declare const fromRecord: <I, A>(codomain: Decoder<I, A>) => Decoder<Reco
 
 Added in v2.2.8
 
+## fromStruct
+
+**Signature**
+
+```ts
+export declare const fromStruct: <P extends Record<string, Decoder<any, any>>>(
+  properties: P
+) => Decoder<{ [K in keyof P]: K.InputOf<'Either', P[K]> }, { [K in keyof P]: K.TypeOf<'Either', P[K]> }>
+```
+
+Added in v2.2.15
+
 ## fromSum
 
 **Signature**
@@ -248,18 +263,6 @@ export declare const fromTuple: <C extends readonly Decoder<any, any>[]>(
 
 Added in v2.2.8
 
-## fromType
-
-**Signature**
-
-```ts
-export declare const fromType: <P extends Record<string, Decoder<any, any>>>(
-  properties: P
-) => Decoder<{ [K in keyof P]: K.InputOf<'Either', P[K]> }, { [K in keyof P]: K.TypeOf<'Either', P[K]> }>
-```
-
-Added in v2.2.8
-
 ## intersect
 
 **Signature**
@@ -328,6 +331,16 @@ export declare const partial: <A>(
 
 Added in v2.2.7
 
+## readonly
+
+**Signature**
+
+```ts
+export declare const readonly: <I, A>(decoder: Decoder<I, A>) => Decoder<I, Readonly<A>>
+```
+
+Added in v2.2.15
+
 ## record
 
 **Signature**
@@ -351,6 +364,18 @@ export declare const refine: <A, B extends A>(
 
 Added in v2.2.7
 
+## struct
+
+**Signature**
+
+```ts
+export declare const struct: <A>(
+  properties: { [K in keyof A]: Decoder<unknown, A[K]> }
+) => Decoder<unknown, { [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.15
+
 ## sum
 
 **Signature**
@@ -375,18 +400,6 @@ export declare const tuple: <A extends readonly unknown[]>(
 
 Added in v2.2.7
 
-## type
-
-**Signature**
-
-```ts
-export declare const type: <A>(
-  properties: { [K in keyof A]: Decoder<unknown, A[K]> }
-) => Decoder<unknown, { [K in keyof A]: A[K] }>
-```
-
-Added in v2.2.7
-
 ## union
 
 **Signature**
@@ -411,6 +424,34 @@ export declare const withMessage: <I>(
 
 Added in v2.2.9
 
+## ~~fromType~~
+
+Use `fromStruct` instead.
+
+**Signature**
+
+```ts
+export declare const fromType: <P extends Record<string, Decoder<any, any>>>(
+  properties: P
+) => Decoder<{ [K in keyof P]: K.InputOf<'Either', P[K]> }, { [K in keyof P]: K.TypeOf<'Either', P[K]> }>
+```
+
+Added in v2.2.8
+
+## ~~type~~
+
+Use `struct` instead.
+
+**Signature**
+
+```ts
+export declare const type: <A>(
+  properties: { [K in keyof A]: Decoder<unknown, A[K]> }
+) => Decoder<unknown, { [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.7
+
 # constructors
 
 ## fromGuard
diff --git a/docs/modules/Encoder.ts.md b/docs/modules/Encoder.ts.md
index e6d576d..2d2cb61 100644
--- a/docs/modules/Encoder.ts.md
+++ b/docs/modules/Encoder.ts.md
@@ -32,9 +32,10 @@ Added in v2.2.3
   - [nullable](#nullable)
   - [partial](#partial)
   - [record](#record)
+  - [struct](#struct)
   - [sum](#sum)
   - [tuple](#tuple)
-  - [type](#type)
+  - [~~type~~](#type)
 - [instances](#instances)
   - [Category](#category-1)
   - [Contravariant](#contravariant-1)
@@ -148,6 +149,18 @@ export declare function record<O, A>(codomain: Encoder<O, A>): Encoder<Record<st
 
 Added in v2.2.3
 
+## struct
+
+**Signature**
+
+```ts
+export declare function struct<P extends Record<string, Encoder<any, any>>>(
+  properties: P
+): Encoder<{ [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
+```
+
+Added in v2.2.15
+
 ## sum
 
 **Signature**
@@ -172,14 +185,14 @@ export declare function tuple<C extends ReadonlyArray<Encoder<any, any>>>(
 
 Added in v2.2.3
 
-## type
+## ~~type~~
+
+Use `struct` instead.
 
 **Signature**
 
 ```ts
-export declare function type<P extends Record<string, Encoder<any, any>>>(
-  properties: P
-): Encoder<{ [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
+export declare const type: typeof struct
 ```
 
 Added in v2.2.3
diff --git a/docs/modules/Eq.ts.md b/docs/modules/Eq.ts.md
index 7c23c1f..5043ed5 100644
--- a/docs/modules/Eq.ts.md
+++ b/docs/modules/Eq.ts.md
@@ -25,10 +25,12 @@ Added in v2.2.2
   - [lazy](#lazy)
   - [nullable](#nullable)
   - [partial](#partial)
+  - [readonly](#readonly)
   - [record](#record)
+  - [struct](#struct)
   - [sum](#sum)
   - [tuple](#tuple)
-  - [type](#type)
+  - [~~type~~](#type)
 - [instances](#instances)
   - [Schemable](#schemable)
   - [WithRefine](#withrefine)
@@ -97,6 +99,16 @@ export declare function partial<A>(properties: { [K in keyof A]: Eq<A[K]> }): Eq
 
 Added in v2.2.2
 
+## readonly
+
+**Signature**
+
+```ts
+export declare const readonly: <A>(eq: E.Eq<A>) => E.Eq<Readonly<A>>
+```
+
+Added in v2.2.15
+
 ## record
 
 **Signature**
@@ -107,6 +119,16 @@ export declare const record: <A>(codomain: E.Eq<A>) => E.Eq<Record<string, A>>
 
 Added in v2.2.2
 
+## struct
+
+**Signature**
+
+```ts
+export declare const struct: <A>(eqs: { [K in keyof A]: E.Eq<A[K]> }) => E.Eq<{ [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.15
+
 ## sum
 
 **Signature**
@@ -129,7 +151,9 @@ export declare const tuple: <A extends readonly unknown[]>(...components: { [K i
 
 Added in v2.2.2
 
-## type
+## ~~type~~
+
+Use `struct` instead.
 
 **Signature**
 
diff --git a/docs/modules/Guard.ts.md b/docs/modules/Guard.ts.md
index b558562..015da58 100644
--- a/docs/modules/Guard.ts.md
+++ b/docs/modules/Guard.ts.md
@@ -28,13 +28,15 @@ Added in v2.2.0
   - [lazy](#lazy)
   - [nullable](#nullable)
   - [partial](#partial)
+  - [readonly](#readonly)
   - [record](#record)
   - [refine](#refine)
+  - [struct](#struct)
   - [sum](#sum)
   - [tuple](#tuple)
-  - [type](#type)
   - [union](#union)
   - [zero](#zero)
+  - [~~type~~](#type)
 - [constructors](#constructors)
   - [literal](#literal)
 - [instances](#instances)
@@ -142,6 +144,16 @@ export declare const partial: <A>(
 
 Added in v2.2.0
 
+## readonly
+
+**Signature**
+
+```ts
+export declare const readonly: <I, A extends I>(guard: Guard<I, A>) => Guard<I, Readonly<A>>
+```
+
+Added in v2.2.15
+
 ## record
 
 **Signature**
@@ -164,6 +176,18 @@ export declare const refine: <I, A extends I, B extends A>(
 
 Added in v2.2.0
 
+## struct
+
+**Signature**
+
+```ts
+export declare const struct: <A>(
+  properties: { [K in keyof A]: Guard<unknown, A[K]> }
+) => Guard<unknown, { [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.15
+
 ## sum
 
 **Signature**
@@ -188,18 +212,6 @@ export declare const tuple: <A extends readonly unknown[]>(
 
 Added in v2.2.0
 
-## type
-
-**Signature**
-
-```ts
-export declare const type: <A>(
-  properties: { [K in keyof A]: Guard<unknown, A[K]> }
-) => Guard<unknown, { [K in keyof A]: A[K] }>
-```
-
-Added in v2.2.0
-
 ## union
 
 **Signature**
@@ -222,6 +234,20 @@ export declare const zero: <I, A extends I>() => Guard<I, A>
 
 Added in v2.2.8
 
+## ~~type~~
+
+Use `struct` instead.
+
+**Signature**
+
+```ts
+export declare const type: <A>(
+  properties: { [K in keyof A]: Guard<unknown, A[K]> }
+) => Guard<unknown, { [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.0
+
 # constructors
 
 ## literal
diff --git a/docs/modules/Kleisli.ts.md b/docs/modules/Kleisli.ts.md
index 428b60e..7bdaeb2 100644
--- a/docs/modules/Kleisli.ts.md
+++ b/docs/modules/Kleisli.ts.md
@@ -25,9 +25,9 @@ Added in v2.2.7
   - [fromArray](#fromarray)
   - [fromPartial](#frompartial)
   - [fromRecord](#fromrecord)
+  - [fromStruct](#fromstruct)
   - [fromSum](#fromsum)
   - [fromTuple](#fromtuple)
-  - [fromType](#fromtype)
   - [id](#id)
   - [intersect](#intersect)
   - [lazy](#lazy)
@@ -37,6 +37,7 @@ Added in v2.2.7
   - [parse](#parse)
   - [refine](#refine)
   - [union](#union)
+  - [~~fromType~~](#fromtype)
 - [constructors](#constructors)
   - [fromRefinement](#fromrefinement)
   - [literal](#literal)
@@ -116,6 +117,22 @@ export declare function fromRecord<M extends URIS2, E>(
 
 Added in v2.2.7
 
+## fromStruct
+
+**Signature**
+
+```ts
+export declare function fromStruct<M extends URIS2, E>(
+  M: Applicative2C<M, E> & Bifunctor2<M>
+): (
+  onPropertyError: (key: string, e: E) => E
+) => <P extends Record<string, Kleisli<M, any, E, any>>>(
+  properties: P
+) => Kleisli<M, { [K in keyof P]: InputOf<M, P[K]> }, E, { [K in keyof P]: TypeOf<M, P[K]> }>
+```
+
+Added in v2.2.15
+
 ## fromSum
 
 **Signature**
@@ -150,22 +167,6 @@ export declare function fromTuple<M extends URIS2, E>(
 
 Added in v2.2.7
 
-## fromType
-
-**Signature**
-
-```ts
-export declare function fromType<M extends URIS2, E>(
-  M: Applicative2C<M, E> & Bifunctor2<M>
-): (
-  onPropertyError: (key: string, e: E) => E
-) => <P extends Record<string, Kleisli<M, any, E, any>>>(
-  properties: P
-) => Kleisli<M, { [K in keyof P]: InputOf<M, P[K]> }, E, { [K in keyof P]: TypeOf<M, P[K]> }>
-```
-
-Added in v2.2.7
-
 ## id
 
 **Signature**
@@ -279,6 +280,18 @@ export declare function union<M extends URIS2, E>(
 
 Added in v2.2.7
 
+## ~~fromType~~
+
+Use `fromStruct` instead.
+
+**Signature**
+
+```ts
+export declare const fromType: typeof fromStruct
+```
+
+Added in v2.2.7
+
 # constructors
 
 ## fromRefinement
diff --git a/docs/modules/Schemable.ts.md b/docs/modules/Schemable.ts.md
index 0f54053..9d5d7d2 100644
--- a/docs/modules/Schemable.ts.md
+++ b/docs/modules/Schemable.ts.md
@@ -61,7 +61,9 @@ export interface Schemable<S> {
   readonly number: HKT<S, number>
   readonly boolean: HKT<S, boolean>
   readonly nullable: <A>(or: HKT<S, A>) => HKT<S, null | A>
+  /** @deprecated */
   readonly type: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, { [K in keyof A]: A[K] }>
+  readonly struct: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, { [K in keyof A]: A[K] }>
   readonly partial: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, Partial<{ [K in keyof A]: A[K] }>>
   readonly record: <A>(codomain: HKT<S, A>) => HKT<S, Record<string, A>>
   readonly array: <A>(item: HKT<S, A>) => HKT<S, Array<A>>
@@ -71,6 +73,7 @@ export interface Schemable<S> {
     tag: T
   ) => <A>(members: { [K in keyof A]: HKT<S, A[K] & Record<T, K>> }) => HKT<S, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => HKT<S, A>) => HKT<S, A>
+  readonly readonly: <A>(sa: HKT<S, A>) => HKT<S, Readonly<A>>
 }
 ```
 
@@ -88,7 +91,9 @@ export interface Schemable1<S extends URIS> {
   readonly number: Kind<S, number>
   readonly boolean: Kind<S, boolean>
   readonly nullable: <A>(or: Kind<S, A>) => Kind<S, null | A>
+  /** @deprecated */
   readonly type: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, { [K in keyof A]: A[K] }>
+  readonly struct: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, { [K in keyof A]: A[K] }>
   readonly partial: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, Partial<{ [K in keyof A]: A[K] }>>
   readonly record: <A>(codomain: Kind<S, A>) => Kind<S, Record<string, A>>
   readonly array: <A>(item: Kind<S, A>) => Kind<S, Array<A>>
@@ -98,6 +103,7 @@ export interface Schemable1<S extends URIS> {
     tag: T
   ) => <A>(members: { [K in keyof A]: Kind<S, A[K] & Record<T, K>> }) => Kind<S, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => Kind<S, A>) => Kind<S, A>
+  readonly readonly: <A>(sa: Kind<S, A>) => Kind<S, Readonly<A>>
 }
 ```
 
@@ -115,7 +121,9 @@ export interface Schemable2C<S extends URIS2, E> {
   readonly number: Kind2<S, E, number>
   readonly boolean: Kind2<S, E, boolean>
   readonly nullable: <A>(or: Kind2<S, E, A>) => Kind2<S, E, null | A>
+  /** @deprecated */
   readonly type: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, { [K in keyof A]: A[K] }>
+  readonly struct: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, { [K in keyof A]: A[K] }>
   readonly partial: <A>(
     properties: { [K in keyof A]: Kind2<S, E, A[K]> }
   ) => Kind2<S, E, Partial<{ [K in keyof A]: A[K] }>>
@@ -129,6 +137,7 @@ export interface Schemable2C<S extends URIS2, E> {
     tag: T
   ) => <A>(members: { [K in keyof A]: Kind2<S, E, A[K] & Record<T, K>> }) => Kind2<S, E, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => Kind2<S, E, A>) => Kind2<S, E, A>
+  readonly readonly: <A>(sa: Kind2<S, E, A>) => Kind2<S, E, Readonly<A>>
 }
 ```
 
diff --git a/docs/modules/TaskDecoder.ts.md b/docs/modules/TaskDecoder.ts.md
index 73a4527..8caa35f 100644
--- a/docs/modules/TaskDecoder.ts.md
+++ b/docs/modules/TaskDecoder.ts.md
@@ -37,22 +37,25 @@ Added in v2.2.7
   - [fromArray](#fromarray)
   - [fromPartial](#frompartial)
   - [fromRecord](#fromrecord)
+  - [fromStruct](#fromstruct)
   - [fromSum](#fromsum)
   - [fromTuple](#fromtuple)
-  - [fromType](#fromtype)
   - [intersect](#intersect)
   - [lazy](#lazy)
   - [mapLeftWithInput](#mapleftwithinput)
   - [nullable](#nullable)
   - [parse](#parse)
   - [partial](#partial)
+  - [readonly](#readonly)
   - [record](#record)
   - [refine](#refine)
+  - [struct](#struct)
   - [sum](#sum)
   - [tuple](#tuple)
-  - [type](#type)
   - [union](#union)
   - [withMessage](#withmessage)
+  - [~~fromType~~](#fromtype)
+  - [~~type~~](#type)
 - [constructors](#constructors)
   - [fromDecoder](#fromdecoder)
   - [fromGuard](#fromguard)
@@ -225,6 +228,18 @@ export declare const fromRecord: <I, A>(
 
 Added in v2.2.8
 
+## fromStruct
+
+**Signature**
+
+```ts
+export declare const fromStruct: <P extends Record<string, TaskDecoder<any, any>>>(
+  properties: P
+) => TaskDecoder<{ [K in keyof P]: K.InputOf<'TaskEither', P[K]> }, { [K in keyof P]: K.TypeOf<'TaskEither', P[K]> }>
+```
+
+Added in v2.2.15
+
 ## fromSum
 
 **Signature**
@@ -251,18 +266,6 @@ export declare const fromTuple: <C extends readonly TaskDecoder<any, any>[]>(
 
 Added in v2.2.8
 
-## fromType
-
-**Signature**
-
-```ts
-export declare const fromType: <P extends Record<string, TaskDecoder<any, any>>>(
-  properties: P
-) => TaskDecoder<{ [K in keyof P]: K.InputOf<'TaskEither', P[K]> }, { [K in keyof P]: K.TypeOf<'TaskEither', P[K]> }>
-```
-
-Added in v2.2.8
-
 ## intersect
 
 **Signature**
@@ -331,6 +334,16 @@ export declare const partial: <A>(
 
 Added in v2.2.7
 
+## readonly
+
+**Signature**
+
+```ts
+export declare const readonly: <I, A>(decoder: TaskDecoder<I, A>) => TaskDecoder<I, Readonly<A>>
+```
+
+Added in v2.2.15
+
 ## record
 
 **Signature**
@@ -354,6 +367,18 @@ export declare const refine: <A, B extends A>(
 
 Added in v2.2.7
 
+## struct
+
+**Signature**
+
+```ts
+export declare const struct: <A>(
+  properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
+) => TaskDecoder<unknown, { [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.15
+
 ## sum
 
 **Signature**
@@ -378,18 +403,6 @@ export declare const tuple: <A extends readonly unknown[]>(
 
 Added in v2.2.7
 
-## type
-
-**Signature**
-
-```ts
-export declare const type: <A>(
-  properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
-) => TaskDecoder<unknown, { [K in keyof A]: A[K] }>
-```
-
-Added in v2.2.7
-
 ## union
 
 **Signature**
@@ -414,6 +427,34 @@ export declare const withMessage: <I>(
 
 Added in v2.2.9
 
+## ~~fromType~~
+
+Use `fromStruct` instead.
+
+**Signature**
+
+```ts
+export declare const fromType: <P extends Record<string, TaskDecoder<any, any>>>(
+  properties: P
+) => TaskDecoder<{ [K in keyof P]: K.InputOf<'TaskEither', P[K]> }, { [K in keyof P]: K.TypeOf<'TaskEither', P[K]> }>
+```
+
+Added in v2.2.8
+
+## ~~type~~
+
+Use `struct` instead.
+
+**Signature**
+
+```ts
+export declare const type: <A>(
+  properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
+) => TaskDecoder<unknown, { [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.7
+
 # constructors
 
 ## fromDecoder
diff --git a/docs/modules/Type.ts.md b/docs/modules/Type.ts.md
index 11d2695..d1d2bd0 100644
--- a/docs/modules/Type.ts.md
+++ b/docs/modules/Type.ts.md
@@ -25,12 +25,14 @@ Added in v2.2.3
   - [lazy](#lazy)
   - [nullable](#nullable)
   - [partial](#partial)
+  - [readonly](#readonly)
   - [record](#record)
   - [refine](#refine)
+  - [struct](#struct)
   - [sum](#sum)
   - [tuple](#tuple)
-  - [type](#type)
   - [union](#union)
+  - [~~type~~](#type)
 - [constructors](#constructors)
   - [literal](#literal)
 - [instances](#instances)
@@ -103,6 +105,16 @@ export declare const partial: <A>(properties: { [K in keyof A]: Type<A[K]> }) =>
 
 Added in v2.2.3
 
+## readonly
+
+**Signature**
+
+```ts
+export declare const readonly: <A>(type: Type<A>) => Type<Readonly<A>>
+```
+
+Added in v2.2.15
+
 ## record
 
 **Signature**
@@ -123,6 +135,16 @@ export declare const refine: <A, B extends A>(refinement: Refinement<A, B>, id:
 
 Added in v2.2.3
 
+## struct
+
+**Signature**
+
+```ts
+export declare const struct: <A>(properties: { [K in keyof A]: Type<A[K]> }) => Type<{ [K in keyof A]: A[K] }>
+```
+
+Added in v2.2.15
+
 ## sum
 
 **Signature**
@@ -145,24 +167,26 @@ export declare const tuple: <A extends readonly unknown[]>(...components: { [K i
 
 Added in v2.2.3
 
-## type
+## union
 
 **Signature**
 
 ```ts
-export declare const type: <A>(properties: { [K in keyof A]: Type<A[K]> }) => Type<{ [K in keyof A]: A[K] }>
+export declare const union: <A extends readonly [unknown, ...unknown[]]>(
+  ...members: { [K in keyof A]: Type<A[K]> }
+) => Type<A[number]>
 ```
 
 Added in v2.2.3
 
-## union
+## ~~type~~
+
+Use `struct` instead.
 
 **Signature**
 
 ```ts
-export declare const union: <A extends readonly [unknown, ...unknown[]]>(
-  ...members: { [K in keyof A]: Type<A[K]> }
-) => Type<A[number]>
+export declare const type: <A>(properties: { [K in keyof A]: Type<A[K]> }) => Type<{ [K in keyof A]: A[K] }>
 ```
 
 Added in v2.2.3
diff --git a/dtslint/ts3.5/Codec.ts b/dtslint/ts3.5/Codec.ts
index 480c974..677e7fe 100644
--- a/dtslint/ts3.5/Codec.ts
+++ b/dtslint/ts3.5/Codec.ts
@@ -3,25 +3,25 @@ import * as _ from '../../src/Codec'
 declare const NumberFromString: _.Codec<string, string, number>
 
 //
-// fromType
+// fromStruct
 //
 
 // $ExpectType Codec<{ a: unknown; b: { c: string; }; }, { a: string; b: { c: string; }; }, { a: string; b: { c: number; }; }>
-_.fromType({
+_.fromStruct({
   a: _.string,
-  b: _.fromType({
+  b: _.fromStruct({
     c: NumberFromString
   })
 })
 
 //
-// type
+// struct
 //
 
 // $ExpectType Codec<unknown, { a: string; b: { c: number; }; }, { a: string; b: { c: number; }; }>
-_.type({
+_.struct({
   a: _.string,
-  b: _.type({
+  b: _.struct({
     c: _.number
   })
 })
@@ -98,16 +98,16 @@ _.tuple(_.string, _.number, _.boolean)
 
 // $ExpectType Codec<{ _tag: unknown; a: unknown; } | { _tag: unknown; b: string; }, { _tag: "A"; a: string; } | { _tag: "B"; b: string; }, { _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 _.fromSum('_tag')({
-  A: _.fromType({ _tag: _.literal('A'), a: _.string }),
-  B: _.fromType({ _tag: _.literal('B'), b: NumberFromString })
+  A: _.fromStruct({ _tag: _.literal('A'), a: _.string }),
+  B: _.fromStruct({ _tag: _.literal('B'), b: NumberFromString })
 })
 
 //
 // sum
 //
 
-const S1 = _.type({ _tag: _.literal('A'), a: _.string })
-const S2 = _.type({ _tag: _.literal('B'), b: _.number })
+const S1 = _.struct({ _tag: _.literal('A'), a: _.string })
+const S2 = _.struct({ _tag: _.literal('B'), b: _.number })
 
 // $ExpectType Codec<unknown, { _tag: "A"; a: string; } | { _tag: "B"; b: number; }, { _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 _.sum('_tag')({ A: S1, B: S2 })
diff --git a/dtslint/ts3.5/Decoder.ts b/dtslint/ts3.5/Decoder.ts
index ad43cff..94e80ca 100644
--- a/dtslint/ts3.5/Decoder.ts
+++ b/dtslint/ts3.5/Decoder.ts
@@ -20,25 +20,25 @@ export type TypeOfNumberFromString = _.TypeOf<typeof NumberFromString>
 export type InputOfNumberFromString = _.InputOf<typeof NumberFromString>
 
 //
-// fromType
+// fromStruct
 //
 
 // $ExpectType Decoder<{ a: unknown; b: { c: string; }; }, { a: string; b: { c: number; }; }>
-_.fromType({
+_.fromStruct({
   a: _.string,
-  b: _.fromType({
+  b: _.fromStruct({
     c: NumberFromString
   })
 })
 
 //
-// type
+// struct
 //
 
 // $ExpectType Decoder<unknown, { a: string; b: { c: number; }; }>
-_.type({
+_.struct({
   a: _.string,
-  b: _.type({
+  b: _.struct({
     c: _.number
   })
 })
@@ -115,16 +115,16 @@ _.tuple(_.string, _.number, _.boolean)
 
 // $ExpectType Decoder<{ _tag: unknown; a: unknown; } | { _tag: unknown; b: string; }, { _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 _.fromSum('_tag')({
-  A: _.fromType({ _tag: _.literal('A'), a: _.string }),
-  B: _.fromType({ _tag: _.literal('B'), b: NumberFromString })
+  A: _.fromStruct({ _tag: _.literal('A'), a: _.string }),
+  B: _.fromStruct({ _tag: _.literal('B'), b: NumberFromString })
 })
 
 //
 // sum
 //
 
-const S1 = _.type({ _tag: _.literal('A'), a: _.string })
-const S2 = _.type({ _tag: _.literal('B'), b: _.number })
+const S1 = _.struct({ _tag: _.literal('A'), a: _.string })
+const S2 = _.struct({ _tag: _.literal('B'), b: _.number })
 
 // $ExpectType Decoder<unknown, { _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 _.sum('_tag')({ A: S1, B: S2 })
@@ -147,3 +147,14 @@ pipe(
   _.number,
   _.mapLeftWithInput((u) => FS.of(DE.leaf(u, 'not a number')))
 )
+
+//
+// readonly
+//
+
+// $ExpectType Decoder<unknown, Readonly<{ a: string; }>>
+_.readonly(
+  _.struct({
+    a: _.string
+  })
+)
diff --git a/dtslint/ts3.5/Encoder.ts b/dtslint/ts3.5/Encoder.ts
index 576a636..8bb901d 100644
--- a/dtslint/ts3.5/Encoder.ts
+++ b/dtslint/ts3.5/Encoder.ts
@@ -9,7 +9,7 @@ const BooleanToNumber: E.Encoder<number, boolean> = {
   encode: (b) => (b ? 1 : 0)
 }
 
-export const OfTest = E.type({ a: E.id<string>(), b: E.type({ c: NumberToString }) })
+export const OfTest = E.struct({ a: E.id<string>(), b: E.struct({ c: NumberToString }) })
 
 //
 // TypeOf
@@ -27,9 +27,9 @@ export type OfTestOutput = E.OutputOf<typeof OfTest> // $ExpectType { a: string;
 E.nullable(NumberToString) // $ExpectType Encoder<string | null, number | null>
 
 //
-// type
+// struct
 //
-E.type({ a: E.type({ b: NumberToString }) }) // $ExpectType Encoder<{ a: { b: string; }; }, { a: { b: number; }; }>
+E.struct({ a: E.struct({ b: NumberToString }) }) // $ExpectType Encoder<{ a: { b: string; }; }, { a: { b: number; }; }>
 
 //
 // partial
@@ -56,19 +56,19 @@ E.tuple(NumberToString, BooleanToNumber) // $ExpectType Encoder<[string, number]
 //
 // intersection
 //
-pipe(E.type({ a: NumberToString }), E.intersect(E.type({ b: BooleanToNumber }))) // $ExpectType Encoder<{ a: string; } & { b: number; }, { a: number; } & { b: boolean; }>
+pipe(E.struct({ a: NumberToString }), E.intersect(E.struct({ b: BooleanToNumber }))) // $ExpectType Encoder<{ a: string; } & { b: number; }, { a: number; } & { b: boolean; }>
 
 //
 // sum
 //
-const S1 = E.type({ _tag: E.id<'A'>(), a: NumberToString })
-const S2 = E.type({ _tag: E.id<'B'>(), b: BooleanToNumber })
+const S1 = E.struct({ _tag: E.id<'A'>(), a: NumberToString })
+const S2 = E.struct({ _tag: E.id<'B'>(), b: BooleanToNumber })
 const sum = E.sum('_tag')
 
 // $ExpectType Encoder<{ _tag: "A"; a: string; } | { _tag: "B"; b: number; }, { _tag: "A"; a: number; } | { _tag: "B"; b: boolean; }>
 sum({ A: S1, B: S2 })
 
-const S3 = E.type({ _tag: E.id<'C'>(), c: E.id<string>() })
+const S3 = E.struct({ _tag: E.id<'C'>(), c: E.id<string>() })
 
 //
 // lazy
@@ -90,14 +90,14 @@ interface BOut {
   as: Array<AOut>
 }
 const A: E.Encoder<AOut, A> = E.lazy(() =>
-  E.type({
+  E.struct({
     a: NumberToString,
     bs: E.array(B)
   })
 )
 
 const B: E.Encoder<BOut, B> = E.lazy(() =>
-  E.type({
+  E.struct({
     b: BooleanToNumber,
     as: E.array(A)
   })
diff --git a/dtslint/ts3.5/Eq.ts b/dtslint/ts3.5/Eq.ts
index 5e948dc..c27c58a 100644
--- a/dtslint/ts3.5/Eq.ts
+++ b/dtslint/ts3.5/Eq.ts
@@ -1,9 +1,9 @@
 import * as _ from '../../src/Eq'
 
 // $ExpectType Eq<{ a: string; b: { c: number; }; }>
-_.type({
+_.struct({
   a: _.string,
-  b: _.type({
+  b: _.struct({
     c: _.number
   })
 })
@@ -20,10 +20,21 @@ _.partial({
 // sum
 //
 
-const S1 = _.type({ _tag: _.Schemable.literal('A'), a: _.string })
-const S2 = _.type({ _tag: _.Schemable.literal('B'), b: _.number })
+const S1 = _.struct({ _tag: _.Schemable.literal('A'), a: _.string })
+const S2 = _.struct({ _tag: _.Schemable.literal('B'), b: _.number })
 
 // $ExpectType Eq<{ _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 _.sum('_tag')({ A: S1, B: S2 })
 // // $ExpectError
 // _.sum('_tag')({ A: S1, B: S1 })
+
+//
+// readonly
+//
+
+// $ExpectType Eq<Readonly<{ a: string; }>>
+_.readonly(
+  _.struct({
+    a: _.string
+  })
+)
diff --git a/dtslint/ts3.5/Guard.ts b/dtslint/ts3.5/Guard.ts
index afc8a9a..31a9d3d 100644
--- a/dtslint/ts3.5/Guard.ts
+++ b/dtslint/ts3.5/Guard.ts
@@ -1,13 +1,13 @@
 import * as _ from '../../src/Guard'
 
 //
-// type
+// struct
 //
 
 // $ExpectType Guard<unknown, { a: string; b: { c: number; }; }>
-const A = _.type({
+const A = _.struct({
   a: _.string,
-  b: _.type({
+  b: _.struct({
     c: _.number
   })
 })
@@ -35,10 +35,21 @@ export type A = _.TypeOf<typeof A>
 // sum
 //
 
-const S1 = _.type({ _tag: _.literal('A'), a: _.string })
-const S2 = _.type({ _tag: _.literal('B'), b: _.number })
+const S1 = _.struct({ _tag: _.literal('A'), a: _.string })
+const S2 = _.struct({ _tag: _.literal('B'), b: _.number })
 
 // $ExpectType Guard<unknown, { _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 _.sum('_tag')({ A: S1, B: S2 })
 // $ExpectError
 _.sum('_tag')({ A: S1, B: S1 })
+
+//
+// readonly
+//
+
+// $ExpectType Guard<unknown, Readonly<{ a: string; }>>
+_.readonly(
+  _.struct({
+    a: _.string
+  })
+)
diff --git a/dtslint/ts3.5/Schema.ts b/dtslint/ts3.5/Schema.ts
index 921fd5a..e14dbf9 100644
--- a/dtslint/ts3.5/Schema.ts
+++ b/dtslint/ts3.5/Schema.ts
@@ -16,7 +16,7 @@ function make<A>(f: Schema<A>): Schema<A> {
 // TypeOf
 //
 
-export const OfTest = make((S) => S.type({ a: S.string, b: S.type({ c: S.number }) }))
+export const OfTest = make((S) => S.struct({ a: S.string, b: S.struct({ c: S.number }) }))
 export type OfTest = TypeOf<typeof OfTest> // $ExpectType { a: string; b: { c: number; }; }
 
 //
@@ -53,10 +53,10 @@ make((S) => S.boolean) // $ExpectType Schema<boolean>
 make((S) => S.nullable(S.string)) // $ExpectType Schema<string | null>
 
 //
-// type
+// struct
 //
 
-make((S) => S.type({ a: S.string, b: S.type({ c: S.number }) })) // $ExpectType Schema<{ a: string; b: { c: number; }; }>
+make((S) => S.struct({ a: S.string, b: S.struct({ c: S.number }) })) // $ExpectType Schema<{ a: string; b: { c: number; }; }>
 
 //
 // partial
@@ -89,14 +89,14 @@ make((S) => S.tuple(S.string, S.number, S.boolean)) // $ExpectType Schema<[strin
 // intersection
 //
 
-make((S) => pipe(S.type({ a: S.string }), S.intersect(S.type({ b: S.number })))) // $ExpectType Schema<{ a: string; } & { b: number; }>
+make((S) => pipe(S.struct({ a: S.string }), S.intersect(S.struct({ b: S.number })))) // $ExpectType Schema<{ a: string; } & { b: number; }>
 
 //
 // sum
 //
 
-const S1 = make((S) => S.type({ _tag: S.literal('A'), a: S.string }))
-const S2 = make((S) => S.type({ _tag: S.literal('B'), b: S.number }))
+const S1 = make((S) => S.struct({ _tag: S.literal('A'), a: S.string }))
+const S2 = make((S) => S.struct({ _tag: S.literal('B'), b: S.number }))
 
 // $ExpectType Schema<{ _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 make((S) => S.sum('_tag')({ A: S1(S), B: S2(S) }))
@@ -119,7 +119,7 @@ interface B {
 
 const A: Schema<A> = make((S) =>
   S.lazy('A', () =>
-    S.type({
+    S.struct({
       a: S.string,
       bs: S.array(B(S))
     })
@@ -128,7 +128,7 @@ const A: Schema<A> = make((S) =>
 
 const B: Schema<B> = make((S) =>
   S.lazy('B', () =>
-    S.type({
+    S.struct({
       b: S.number,
       as: S.array(A(S))
     })
@@ -172,3 +172,10 @@ make((S) =>
 make((S) => S.union())
 make((S) => S.union(S.string)) // $ExpectType Schema<string>
 make((S) => S.union(S.string, S.number)) // $ExpectType Schema<string | number>
+
+//
+// readonly
+//
+
+// $ExpectType Schema<Readonly<{ a: string; }>>
+make((S) => S.readonly(S.struct({ a: S.string })))
diff --git a/dtslint/ts3.5/TaskDecoder.ts b/dtslint/ts3.5/TaskDecoder.ts
index 8d8af5d..10a8e93 100644
--- a/dtslint/ts3.5/TaskDecoder.ts
+++ b/dtslint/ts3.5/TaskDecoder.ts
@@ -4,10 +4,21 @@ import * as _ from '../../src/TaskDecoder'
 // sum
 //
 
-const S1 = _.type({ _tag: _.literal('A'), a: _.string })
-const S2 = _.type({ _tag: _.literal('B'), b: _.number })
+const S1 = _.struct({ _tag: _.literal('A'), a: _.string })
+const S2 = _.struct({ _tag: _.literal('B'), b: _.number })
 
 // $ExpectType TaskDecoder<unknown, { _tag: "A"; a: string; } | { _tag: "B"; b: number; }>
 _.sum('_tag')({ A: S1, B: S2 })
 // $ExpectError
 _.sum('_tag')({ A: S1, B: S1 })
+
+//
+// readonly
+//
+
+// $ExpectType TaskDecoder<unknown, Readonly<{ a: string; }>>
+_.readonly(
+  _.struct({
+    a: _.string
+  })
+)
diff --git a/dtslint/ts3.5/Type.ts b/dtslint/ts3.5/Type.ts
index cf7aec6..503d4b9 100644
--- a/dtslint/ts3.5/Type.ts
+++ b/dtslint/ts3.5/Type.ts
@@ -1,9 +1,9 @@
 import * as _ from '../../src/Type'
 
 // $ExpectType Type<{ a: string; b: { c: number; }; }>
-_.type({
+_.struct({
   a: _.string,
-  b: _.type({
+  b: _.struct({
     c: _.number
   })
 })
@@ -15,3 +15,14 @@ _.partial({
     c: _.number
   })
 })
+
+//
+// readonly
+//
+
+// $ExpectType Type<Readonly<{ a: string; }>>
+_.readonly(
+  _.struct({
+    a: _.string
+  })
+)
diff --git a/package.json b/package.json
index 67b02b6..f949262 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "2.2.14",
+  "version": "2.2.15",
   "description": "TypeScript runtime type system for IO decoding/encoding",
   "main": "lib/index.js",
   "module": "es6/index.js",
diff --git a/src/Codec.ts b/src/Codec.ts
index ec10425..035665f 100644
--- a/src/Codec.ts
+++ b/src/Codec.ts
@@ -144,24 +144,42 @@ export function nullable<I, O, A>(or: Codec<I, O, A>): Codec<null | I, null | O,
 
 /**
  * @category combinators
- * @since 2.2.8
+ * @since 2.2.15
  */
-export function fromType<P extends Record<string, Codec<any, any, any>>>(
+export function fromStruct<P extends Record<string, Codec<any, any, any>>>(
   properties: P
 ): Codec<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }> {
-  return make(D.fromType(properties) as any, E.type(properties))
+  return make(D.fromStruct(properties) as any, E.struct(properties))
 }
 
 /**
+ * Use `fromStruct` instead.
+ *
  * @category combinators
- * @since 2.2.3
+ * @since 2.2.8
+ * @deprecated
+ */
+export const fromType = fromStruct
+
+/**
+ * @category combinators
+ * @since 2.2.15
  */
-export function type<P extends Record<string, Codec<unknown, any, any>>>(
+export function struct<P extends Record<string, Codec<unknown, any, any>>>(
   properties: P
 ): Codec<unknown, { [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }> {
-  return pipe(UnknownRecord, compose(fromType(properties as any))) as any
+  return pipe(UnknownRecord, compose(fromStruct(properties as any))) as any
 }
 
+/**
+ * Use `struct` instead.
+ *
+ * @category combinators
+ * @since 2.2.3
+ * @deprecated
+ */
+export const type = struct
+
 /**
  * @category combinators
  * @since 2.2.8
diff --git a/src/Decoder.ts b/src/Decoder.ts
index a624366..f98ff1d 100644
--- a/src/Decoder.ts
+++ b/src/Decoder.ts
@@ -12,7 +12,7 @@ import { Alt2, Alt2C } from 'fp-ts/lib/Alt'
 import { Bifunctor2 } from 'fp-ts/lib/Bifunctor'
 import { Category2 } from 'fp-ts/lib/Category'
 import * as E from 'fp-ts/lib/Either'
-import { Refinement } from 'fp-ts/lib/function'
+import { identity, Refinement } from 'fp-ts/lib/function'
 import { Functor2 } from 'fp-ts/lib/Functor'
 import { MonadThrow2C } from 'fp-ts/lib/MonadThrow'
 import { pipe } from 'fp-ts/lib/pipeable'
@@ -228,20 +228,38 @@ export const nullable: <I, A>(or: Decoder<I, A>) => Decoder<null | I, null | A>
 
 /**
  * @category combinators
- * @since 2.2.8
+ * @since 2.2.15
  */
-export const fromType = <P extends Record<string, Decoder<any, any>>>(
+export const fromStruct = <P extends Record<string, Decoder<any, any>>>(
   properties: P
 ): Decoder<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }> =>
-  K.fromType(M)((k, e) => FS.of(DE.key(k, DE.required, e)))(properties)
+  K.fromStruct(M)((k, e) => FS.of(DE.key(k, DE.required, e)))(properties)
 
 /**
+ * Use `fromStruct` instead.
+ *
  * @category combinators
- * @since 2.2.7
+ * @since 2.2.8
+ * @deprecated
  */
-export const type = <A>(
+export const fromType = fromStruct
+
+/**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const struct = <A>(
   properties: { [K in keyof A]: Decoder<unknown, A[K]> }
-): Decoder<unknown, { [K in keyof A]: A[K] }> => pipe(UnknownRecord as any, compose(fromType(properties)))
+): Decoder<unknown, { [K in keyof A]: A[K] }> => pipe(UnknownRecord as any, compose(fromStruct(properties)))
+
+/**
+ * Use `struct` instead.
+ *
+ * @category combinators
+ * @since 2.2.7
+ * @deprecated
+ */
+export const type = struct
 
 /**
  * @category combinators
@@ -356,6 +374,12 @@ export const lazy: <I, A>(id: string, f: () => Decoder<I, A>) => Decoder<I, A> =
   /*#__PURE__*/
   K.lazy(M)((id, e) => FS.of(DE.lazy(id, e)))
 
+/**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const readonly: <I, A>(decoder: Decoder<I, A>) => Decoder<I, Readonly<A>> = identity
+
 // -------------------------------------------------------------------------------------
 // non-pipeables
 // -------------------------------------------------------------------------------------
@@ -465,13 +489,15 @@ export const Schemable: S.Schemable2C<URI, unknown> = {
   boolean,
   nullable,
   type,
+  struct,
   partial,
   record,
   array,
   tuple: tuple as S.Schemable2C<URI, unknown>['tuple'],
   intersect,
   sum,
-  lazy
+  lazy,
+  readonly
 }
 
 /**
diff --git a/src/Encoder.ts b/src/Encoder.ts
index f06ad88..5b7eac9 100644
--- a/src/Encoder.ts
+++ b/src/Encoder.ts
@@ -41,9 +41,9 @@ export function nullable<O, A>(or: Encoder<O, A>): Encoder<null | O, null | A> {
 
 /**
  * @category combinators
- * @since 2.2.3
+ * @since 2.2.15
  */
-export function type<P extends Record<string, Encoder<any, any>>>(
+export function struct<P extends Record<string, Encoder<any, any>>>(
   properties: P
 ): Encoder<{ [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }> {
   return {
@@ -57,6 +57,15 @@ export function type<P extends Record<string, Encoder<any, any>>>(
   }
 }
 
+/**
+ * Use `struct` instead.
+ *
+ * @category combinators
+ * @since 2.2.3
+ * @deprecated
+ */
+export const type = struct
+
 /**
  * @category combinators
  * @since 2.2.3
diff --git a/src/Eq.ts b/src/Eq.ts
index 49a4f25..61df119 100644
--- a/src/Eq.ts
+++ b/src/Eq.ts
@@ -10,6 +10,7 @@
  */
 import * as A from 'fp-ts/lib/Array'
 import * as E from 'fp-ts/lib/Eq'
+import { identity } from 'fp-ts/lib/function'
 import * as R from 'fp-ts/lib/Record'
 import { memoize, Schemable1, WithRefine1, WithUnknownContainers1 } from './Schemable'
 import Eq = E.Eq
@@ -89,10 +90,19 @@ export function nullable<A>(or: Eq<A>): Eq<null | A> {
 }
 
 /**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const struct: <A>(eqs: { [K in keyof A]: Eq<A[K]> }) => Eq<{ [K in keyof A]: A[K] }> = E.getStructEq
+
+/**
+ * Use `struct` instead.
+ *
  * @category combinators
  * @since 2.2.2
+ * @deprecated
  */
-export const type: <A>(eqs: { [K in keyof A]: Eq<A[K]> }) => Eq<{ [K in keyof A]: A[K] }> = E.getStructEq
+export const type = struct
 
 /**
  * @category combinators
@@ -173,6 +183,12 @@ export function lazy<A>(f: () => Eq<A>): Eq<A> {
   }
 }
 
+/**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const readonly: <A>(eq: Eq<A>) => Eq<Readonly<A>> = identity
+
 // -------------------------------------------------------------------------------------
 // instances
 // -------------------------------------------------------------------------------------
@@ -189,13 +205,15 @@ export const Schemable: Schemable1<E.URI> = {
   boolean,
   nullable,
   type,
+  struct,
   partial,
   record,
   array,
   tuple,
   intersect,
   sum,
-  lazy: (_, f) => lazy(f)
+  lazy: (_, f) => lazy(f),
+  readonly
 }
 
 /**
diff --git a/src/Guard.ts b/src/Guard.ts
index e7855b1..16bc247 100644
--- a/src/Guard.ts
+++ b/src/Guard.ts
@@ -8,7 +8,7 @@
  *
  * @since 2.2.0
  */
-import { Refinement } from 'fp-ts/lib/function'
+import { identity, Refinement } from 'fp-ts/lib/function'
 import { pipe } from 'fp-ts/lib/pipeable'
 import { Literal, memoize, Schemable1, WithRefine1, WithUnion1, WithUnknownContainers1 } from './Schemable'
 
@@ -120,9 +120,9 @@ export const nullable = <I, A extends I>(or: Guard<I, A>): Guard<null | I, null
 
 /**
  * @category combinators
- * @since 2.2.0
+ * @since 2.2.15
  */
-export const type = <A>(
+export const struct = <A>(
   properties: { [K in keyof A]: Guard<unknown, A[K]> }
 ): Guard<unknown, { [K in keyof A]: A[K] }> =>
   pipe(
@@ -139,6 +139,15 @@ export const type = <A>(
     })
   )
 
+/**
+ * Use `struct` instead.
+ *
+ * @category combinators
+ * @since 2.2.0
+ * @deprecated
+ */
+export const type = struct
+
 /**
  * @category combinators
  * @since 2.2.0
@@ -243,6 +252,12 @@ export const lazy = <A>(f: () => Guard<unknown, A>): Guard<unknown, A> => {
   }
 }
 
+/**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const readonly: <I, A extends I>(guard: Guard<I, A>) => Guard<I, Readonly<A>> = identity
+
 /**
  * @category combinators
  * @since 2.2.8
@@ -309,13 +324,15 @@ export const Schemable: Schemable1<URI> = {
   boolean,
   nullable,
   type,
+  struct,
   partial,
   record,
   array,
   tuple: tuple as Schemable1<URI>['tuple'],
   intersect,
   sum,
-  lazy: (_, f) => lazy(f)
+  lazy: (_, f) => lazy(f),
+  readonly
 }
 
 /**
diff --git a/src/Kleisli.ts b/src/Kleisli.ts
index 81e890b..5a30c19 100644
--- a/src/Kleisli.ts
+++ b/src/Kleisli.ts
@@ -123,9 +123,9 @@ export function nullable<M extends URIS2, E>(
 
 /**
  * @category combinators
- * @since 2.2.7
+ * @since 2.2.15
  */
-export function fromType<M extends URIS2, E>(
+export function fromStruct<M extends URIS2, E>(
   M: Applicative2C<M, E> & Bifunctor2<M>
 ): (
   onPropertyError: (key: string, e: E) => E
@@ -141,6 +141,15 @@ export function fromType<M extends URIS2, E>(
   })
 }
 
+/**
+ * Use `fromStruct` instead.
+ *
+ * @category combinators
+ * @since 2.2.7
+ * @deprecated
+ */
+export const fromType = fromStruct
+
 /**
  * @category combinators
  * @since 2.2.7
diff --git a/src/Schemable.ts b/src/Schemable.ts
index 4834623..4178108 100644
--- a/src/Schemable.ts
+++ b/src/Schemable.ts
@@ -26,7 +26,9 @@ export interface Schemable<S> {
   readonly number: HKT<S, number>
   readonly boolean: HKT<S, boolean>
   readonly nullable: <A>(or: HKT<S, A>) => HKT<S, null | A>
+  /** @deprecated */
   readonly type: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, { [K in keyof A]: A[K] }>
+  readonly struct: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, { [K in keyof A]: A[K] }>
   readonly partial: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, Partial<{ [K in keyof A]: A[K] }>>
   readonly record: <A>(codomain: HKT<S, A>) => HKT<S, Record<string, A>>
   readonly array: <A>(item: HKT<S, A>) => HKT<S, Array<A>>
@@ -36,6 +38,7 @@ export interface Schemable<S> {
     tag: T
   ) => <A>(members: { [K in keyof A]: HKT<S, A[K] & Record<T, K>> }) => HKT<S, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => HKT<S, A>) => HKT<S, A>
+  readonly readonly: <A>(sa: HKT<S, A>) => HKT<S, Readonly<A>>
 }
 
 /**
@@ -48,7 +51,9 @@ export interface Schemable1<S extends URIS> {
   readonly number: Kind<S, number>
   readonly boolean: Kind<S, boolean>
   readonly nullable: <A>(or: Kind<S, A>) => Kind<S, null | A>
+  /** @deprecated */
   readonly type: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, { [K in keyof A]: A[K] }>
+  readonly struct: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, { [K in keyof A]: A[K] }>
   readonly partial: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, Partial<{ [K in keyof A]: A[K] }>>
   readonly record: <A>(codomain: Kind<S, A>) => Kind<S, Record<string, A>>
   readonly array: <A>(item: Kind<S, A>) => Kind<S, Array<A>>
@@ -58,6 +63,7 @@ export interface Schemable1<S extends URIS> {
     tag: T
   ) => <A>(members: { [K in keyof A]: Kind<S, A[K] & Record<T, K>> }) => Kind<S, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => Kind<S, A>) => Kind<S, A>
+  readonly readonly: <A>(sa: Kind<S, A>) => Kind<S, Readonly<A>>
 }
 
 /**
@@ -70,7 +76,9 @@ export interface Schemable2C<S extends URIS2, E> {
   readonly number: Kind2<S, E, number>
   readonly boolean: Kind2<S, E, boolean>
   readonly nullable: <A>(or: Kind2<S, E, A>) => Kind2<S, E, null | A>
+  /** @deprecated */
   readonly type: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, { [K in keyof A]: A[K] }>
+  readonly struct: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, { [K in keyof A]: A[K] }>
   readonly partial: <A>(
     properties: { [K in keyof A]: Kind2<S, E, A[K]> }
   ) => Kind2<S, E, Partial<{ [K in keyof A]: A[K] }>>
@@ -84,6 +92,7 @@ export interface Schemable2C<S extends URIS2, E> {
     tag: T
   ) => <A>(members: { [K in keyof A]: Kind2<S, E, A[K] & Record<T, K>> }) => Kind2<S, E, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => Kind2<S, E, A>) => Kind2<S, E, A>
+  readonly readonly: <A>(sa: Kind2<S, E, A>) => Kind2<S, E, Readonly<A>>
 }
 
 /**
diff --git a/src/TaskDecoder.ts b/src/TaskDecoder.ts
index 4132da3..8ae798c 100644
--- a/src/TaskDecoder.ts
+++ b/src/TaskDecoder.ts
@@ -12,7 +12,7 @@ import { Alt2, Alt2C } from 'fp-ts/lib/Alt'
 import { Bifunctor2 } from 'fp-ts/lib/Bifunctor'
 import { Category2 } from 'fp-ts/lib/Category'
 import * as E from 'fp-ts/lib/Either'
-import { Refinement } from 'fp-ts/lib/function'
+import { identity, Refinement } from 'fp-ts/lib/function'
 import { Functor2 } from 'fp-ts/lib/Functor'
 import { MonadThrow2C } from 'fp-ts/lib/MonadThrow'
 import { pipe } from 'fp-ts/lib/pipeable'
@@ -231,20 +231,38 @@ export const nullable: <I, A>(or: TaskDecoder<I, A>) => TaskDecoder<null | I, nu
 
 /**
  * @category combinators
- * @since 2.2.8
+ * @since 2.2.15
  */
-export const fromType = <P extends Record<string, TaskDecoder<any, any>>>(
+export const fromStruct = <P extends Record<string, TaskDecoder<any, any>>>(
   properties: P
 ): TaskDecoder<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }> =>
-  K.fromType(M)((k, e) => FS.of(DE.key(k, DE.required, e)))(properties)
+  K.fromStruct(M)((k, e) => FS.of(DE.key(k, DE.required, e)))(properties)
 
 /**
+ * Use `fromStruct` instead.
+ *
  * @category combinators
- * @since 2.2.7
+ * @since 2.2.8
+ * @deprecated
  */
-export const type = <A>(
+export const fromType = fromStruct
+
+/**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const struct = <A>(
   properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
-): TaskDecoder<unknown, { [K in keyof A]: A[K] }> => pipe(UnknownRecord as any, compose(fromType(properties)))
+): TaskDecoder<unknown, { [K in keyof A]: A[K] }> => pipe(UnknownRecord as any, compose(fromStruct(properties)))
+
+/**
+ * Use `struct` instead.
+ *
+ * @category combinators
+ * @since 2.2.7
+ * @deprecated
+ */
+export const type = struct
 
 /**
  * @category combinators
@@ -362,6 +380,12 @@ export const lazy: <I, A>(id: string, f: () => TaskDecoder<I, A>) => TaskDecoder
   /*#__PURE__*/
   K.lazy(M)((id, e) => FS.of(DE.lazy(id, e)))
 
+/**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const readonly: <I, A>(decoder: TaskDecoder<I, A>) => TaskDecoder<I, Readonly<A>> = identity
+
 // -------------------------------------------------------------------------------------
 // non-pipeables
 // -------------------------------------------------------------------------------------
@@ -471,13 +495,15 @@ export const Schemable: S.Schemable2C<URI, unknown> = {
   boolean,
   nullable,
   type,
+  struct,
   partial,
   record,
   array,
   tuple: tuple as S.Schemable2C<URI, unknown>['tuple'],
   intersect,
   sum,
-  lazy
+  lazy,
+  readonly
 }
 
 /**
diff --git a/src/Type.ts b/src/Type.ts
index 852cfea..187d334 100644
--- a/src/Type.ts
+++ b/src/Type.ts
@@ -9,7 +9,7 @@
  * @since 2.2.3
  */
 import * as E from 'fp-ts/lib/Either'
-import { Refinement } from 'fp-ts/lib/function'
+import { identity, Refinement } from 'fp-ts/lib/function'
 import { pipe } from 'fp-ts/lib/pipeable'
 import * as t from './index'
 import * as S from './Schemable'
@@ -98,11 +98,20 @@ export const nullable = <A>(or: Type<A>): Type<null | A> => t.union([t.null, or]
 
 /**
  * @category combinators
- * @since 2.2.3
+ * @since 2.2.15
  */
-export const type = <A>(properties: { [K in keyof A]: Type<A[K]> }): Type<{ [K in keyof A]: A[K] }> =>
+export const struct = <A>(properties: { [K in keyof A]: Type<A[K]> }): Type<{ [K in keyof A]: A[K] }> =>
   t.type(properties) as any
 
+/**
+ * Use `struct` instead.
+ *
+ * @category combinators
+ * @since 2.2.3
+ * @deprecated
+ */
+export const type = struct
+
 /**
  * @category combinators
  * @since 2.2.3
@@ -141,6 +150,12 @@ export const intersect = <B>(right: Type<B>) => <A>(left: Type<A>): Type<A & B>
  */
 export const lazy = <A>(id: string, f: () => Type<A>): Type<A> => t.recursion(id, f)
 
+/**
+ * @category combinators
+ * @since 2.2.15
+ */
+export const readonly: <A>(type: Type<A>) => Type<Readonly<A>> = identity
+
 /**
  * @category combinators
  * @since 2.2.3
@@ -191,13 +206,15 @@ export const Schemable: S.Schemable1<URI> = {
   boolean,
   nullable,
   type,
+  struct,
   partial,
   record,
   array,
   tuple: tuple as S.Schemable1<URI>['tuple'],
   intersect,
   sum,
-  lazy
+  lazy,
+  readonly
 }
 
 /**


Test patch:
diff --git a/test/Arbitrary.ts b/test/Arbitrary.ts
index 40b7f25..11185cd 100644
--- a/test/Arbitrary.ts
+++ b/test/Arbitrary.ts
@@ -2,7 +2,7 @@
  * An instance of `Schemable` for `fast-check` arbitraries that emit valid values
  */
 import * as fc from 'fast-check'
-import { Refinement } from 'fp-ts/lib/function'
+import { identity, Refinement } from 'fp-ts/lib/function'
 import * as S from '../src/Schemable'
 
 // -------------------------------------------------------------------------------------
@@ -50,7 +50,7 @@ export function nullable<A>(or: Arbitrary<A>): Arbitrary<null | A> {
   return fc.oneof(fc.constant(null), or)
 }
 
-export function type<A>(properties: { [K in keyof A]: Arbitrary<A[K]> }): Arbitrary<A> {
+export function struct<A>(properties: { [K in keyof A]: Arbitrary<A[K]> }): Arbitrary<A> {
   return fc.record(properties)
 }
 
@@ -94,6 +94,8 @@ export function lazy<A>(f: () => Arbitrary<A>): Arbitrary<A> {
   return fc.constant(null).chain(() => get())
 }
 
+export const readonly: <A>(arb: Arbitrary<A>) => Arbitrary<Readonly<A>> = identity
+
 export function union<A extends [Arbitrary<unknown>, ...Array<Arbitrary<unknown>>]>(
   ...members: { [K in keyof A]: Arbitrary<A[K]> }
 ): Arbitrary<A[number]> {
@@ -121,7 +123,8 @@ export const Schemable: S.Schemable1<URI> & S.WithUnknownContainers1<URI> & S.Wi
   number,
   boolean,
   nullable,
-  type,
+  type: struct,
+  struct,
   partial,
   record,
   array,
@@ -129,6 +132,7 @@ export const Schemable: S.Schemable1<URI> & S.WithUnknownContainers1<URI> & S.Wi
   intersect,
   sum,
   lazy: (_, f) => lazy(f),
+  readonly,
   UnknownArray,
   UnknownRecord,
   union: union as S.WithUnion1<URI>['union'],
diff --git a/test/Codec.ts b/test/Codec.ts
index 45ae60c..cfc48d9 100644
--- a/test/Codec.ts
+++ b/test/Codec.ts
@@ -193,31 +193,31 @@ describe('Codec', () => {
     })
   })
 
-  describe('type', () => {
+  describe('struct', () => {
     describe('decode', () => {
       it('should decode a valid input', () => {
-        const codec = _.type({
+        const codec = _.struct({
           a: _.string
         })
         assert.deepStrictEqual(codec.decode({ a: 'a' }), D.success({ a: 'a' }))
       })
 
       it('should strip additional fields', () => {
-        const codec = _.type({
+        const codec = _.struct({
           a: _.string
         })
         assert.deepStrictEqual(codec.decode({ a: 'a', b: 1 }), D.success({ a: 'a' }))
       })
 
       it('should not strip fields corresponding to undefined values', () => {
-        const codec = _.type({
+        const codec = _.struct({
           a: codecUndefined
         })
         assert.deepStrictEqual(codec.decode({}), D.success({ a: undefined }))
       })
 
       it('should reject an invalid input', () => {
-        const codec = _.type({
+        const codec = _.struct({
           a: _.string
         })
         assert.deepStrictEqual(codec.decode(undefined), D.failure(undefined, 'Record<string, unknown>'))
@@ -228,7 +228,7 @@ describe('Codec', () => {
       })
 
       it('should collect all errors', () => {
-        const codec = _.type({
+        const codec = _.struct({
           a: _.string,
           b: _.number
         })
@@ -252,19 +252,19 @@ describe('Codec', () => {
             return 'b'
           }
         }
-        const codec = _.type({ a: _.string, b: _.string })
+        const codec = _.struct({ a: _.string, b: _.string })
         assert.deepStrictEqual(codec.decode(new A()), D.success({ a: 'a', b: 'b' }))
       })
     })
 
     describe('encode', () => {
       it('should encode a value', () => {
-        const codec = _.type({ a: codecNumber })
+        const codec = _.struct({ a: codecNumber })
         assert.deepStrictEqual(codec.encode({ a: 1 }), { a: '1' })
       })
 
       it('should strip additional fields', () => {
-        const codec = _.type({ a: _.number })
+        const codec = _.struct({ a: _.number })
         const a = { a: 1, b: true }
         assert.deepStrictEqual(codec.encode(a), { a: 1 })
       })
@@ -487,7 +487,7 @@ describe('Codec', () => {
   describe('intersect', () => {
     describe('decode', () => {
       it('should decode a valid input', () => {
-        const codec = pipe(_.type({ a: _.string }), _.intersect(_.type({ b: _.number })))
+        const codec = pipe(_.struct({ a: _.string }), _.intersect(_.struct({ b: _.number })))
         assert.deepStrictEqual(codec.decode({ a: 'a', b: 1 }), D.success({ a: 'a', b: 1 }))
       })
 
@@ -499,7 +499,7 @@ describe('Codec', () => {
 
     describe('encode', () => {
       it('should encode a value', () => {
-        const codec = pipe(_.type({ a: _.string }), _.intersect(_.type({ b: codecNumber })))
+        const codec = pipe(_.struct({ a: _.string }), _.intersect(_.struct({ b: codecNumber })))
         assert.deepStrictEqual(codec.encode({ a: 'a', b: 1 }), { a: 'a', b: '1' })
       })
 
@@ -515,16 +515,16 @@ describe('Codec', () => {
 
     describe('decode', () => {
       it('should decode a valid input', () => {
-        const A = _.type({ _tag: _.literal('A'), a: _.string })
-        const B = _.type({ _tag: _.literal('B'), b: _.number })
+        const A = _.struct({ _tag: _.literal('A'), a: _.string })
+        const B = _.struct({ _tag: _.literal('B'), b: _.number })
         const codec = sum({ A, B })
         assert.deepStrictEqual(codec.decode({ _tag: 'A', a: 'a' }), D.success({ _tag: 'A', a: 'a' }))
         assert.deepStrictEqual(codec.decode({ _tag: 'B', b: 1 }), D.success({ _tag: 'B', b: 1 }))
       })
 
       it('should reject an invalid input', () => {
-        const A = _.type({ _tag: _.literal('A'), a: _.string })
-        const B = _.type({ _tag: _.literal('B'), b: _.number })
+        const A = _.struct({ _tag: _.literal('A'), a: _.string })
+        const B = _.struct({ _tag: _.literal('B'), b: _.number })
         const codec = sum({ A, B })
         assert.deepStrictEqual(codec.decode(null), D.failure(null, 'Record<string, unknown>'))
         assert.deepStrictEqual(
@@ -548,8 +548,8 @@ describe('Codec', () => {
 
     describe('encode', () => {
       it('should encode a value', () => {
-        const A = _.type({ _tag: _.literal('A'), a: _.string })
-        const B = _.type({ _tag: _.literal('B'), b: codecNumber })
+        const A = _.struct({ _tag: _.literal('A'), a: _.string })
+        const B = _.struct({ _tag: _.literal('B'), b: codecNumber })
         const codec = sum({ A, B })
         assert.deepStrictEqual(codec.encode({ _tag: 'A', a: 'a' }), { _tag: 'A', a: 'a' })
         assert.deepStrictEqual(codec.encode({ _tag: 'B', b: 1 }), { _tag: 'B', b: '1' })
@@ -568,7 +568,7 @@ describe('Codec', () => {
     }
 
     const lazyCodec: _.Codec<unknown, AOut, A> = _.lazy('A', () =>
-      pipe(_.type({ a: codecNumber }), _.intersect(_.partial({ b: lazyCodec })))
+      pipe(_.struct({ a: codecNumber }), _.intersect(_.partial({ b: lazyCodec })))
     )
 
     describe('decode', () => {
@@ -639,7 +639,7 @@ describe('Codec', () => {
       { encode: String }
     )
 
-    const User = _.type({ a: _.string, b: pipe(_.string, _.compose(DateFromString)) })
+    const User = _.struct({ a: _.string, b: pipe(_.string, _.compose(DateFromString)) })
 
     const codec = pipe(_.string, _.compose(Base64), _.compose(Json), _.compose(User))
 
diff --git a/test/Decoder.ts b/test/Decoder.ts
index 55b34e6..f132352 100644
--- a/test/Decoder.ts
+++ b/test/Decoder.ts
@@ -94,7 +94,7 @@ describe('Decoder', () => {
 
   it('withMessage', () => {
     const decoder = pipe(
-      _.type({
+      _.struct({
         name: _.string,
         age: _.number
       }),
@@ -153,30 +153,30 @@ describe('Decoder', () => {
     })
   })
 
-  describe('type', () => {
+  describe('struct', () => {
     it('should decode a valid input', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string
       })
       assert.deepStrictEqual(decoder.decode({ a: 'a' }), _.success({ a: 'a' }))
     })
 
     it('should strip additional fields', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string
       })
       assert.deepStrictEqual(decoder.decode({ a: 'a', b: 1 }), _.success({ a: 'a' }))
     })
 
     it('should not strip fields corresponding to undefined values', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: H.decoderUndefined
       })
       assert.deepStrictEqual(decoder.decode({}), _.success({ a: undefined }))
     })
 
     it('should reject an invalid input', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string
       })
       assert.deepStrictEqual(decoder.decode(undefined), E.left(FS.of(DE.leaf(undefined, 'Record<string, unknown>'))))
@@ -187,7 +187,7 @@ describe('Decoder', () => {
     })
 
     it('should collect all errors', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string,
         b: _.number
       })
@@ -211,7 +211,7 @@ describe('Decoder', () => {
           return 'b'
         }
       }
-      const decoder = _.type({ a: _.string, b: _.string })
+      const decoder = _.struct({ a: _.string, b: _.string })
       assert.deepStrictEqual(decoder.decode(new A()), _.success({ a: 'a', b: 'b' }))
     })
   })
@@ -406,7 +406,7 @@ describe('Decoder', () => {
 
   describe('intersect', () => {
     it('should decode a valid input', () => {
-      const decoder = pipe(_.type({ a: _.string }), _.intersect(_.type({ b: _.number })))
+      const decoder = pipe(_.struct({ a: _.string }), _.intersect(_.struct({ b: _.number })))
       assert.deepStrictEqual(decoder.decode({ a: 'a', b: 1 }), _.success({ a: 'a', b: 1 }))
     })
 
@@ -416,7 +416,7 @@ describe('Decoder', () => {
     })
 
     it('should accumulate all errors', () => {
-      const decoder = pipe(_.type({ a: _.string }), _.intersect(_.type({ b: _.number })))
+      const decoder = pipe(_.struct({ a: _.string }), _.intersect(_.struct({ b: _.number })))
       assert.deepStrictEqual(
         decoder.decode({ a: 'a' }),
         E.left(FS.of(DE.key('b', DE.required, FS.of(DE.leaf(undefined, 'number')))))
@@ -441,16 +441,16 @@ describe('Decoder', () => {
     const sum = _.sum('_tag')
 
     it('should decode a valid input', () => {
-      const A = _.type({ _tag: _.literal('A'), a: _.string })
-      const B = _.type({ _tag: _.literal('B'), b: _.number })
+      const A = _.struct({ _tag: _.literal('A'), a: _.string })
+      const B = _.struct({ _tag: _.literal('B'), b: _.number })
       const decoder = sum({ A, B })
       assert.deepStrictEqual(decoder.decode({ _tag: 'A', a: 'a' }), _.success({ _tag: 'A', a: 'a' }))
       assert.deepStrictEqual(decoder.decode({ _tag: 'B', b: 1 }), _.success({ _tag: 'B', b: 1 }))
     })
 
     it('should reject an invalid input', () => {
-      const A = _.type({ _tag: _.literal('A'), a: _.string })
-      const B = _.type({ _tag: _.literal('B'), b: _.number })
+      const A = _.struct({ _tag: _.literal('A'), a: _.string })
+      const B = _.struct({ _tag: _.literal('B'), b: _.number })
       const decoder = sum({ A, B })
       assert.deepStrictEqual(decoder.decode(null), E.left(FS.of(DE.leaf(null, 'Record<string, unknown>'))))
       assert.deepStrictEqual(
@@ -473,8 +473,8 @@ describe('Decoder', () => {
 
     it('should support non-`string` tag values', () => {
       const decoder = _.sum('_tag')({
-        [1]: _.type({ _tag: _.literal(1), a: _.string }),
-        [2]: _.type({ _tag: _.literal(2), b: _.number })
+        [1]: _.struct({ _tag: _.literal(1), a: _.string }),
+        [2]: _.struct({ _tag: _.literal(2), b: _.number })
       })
       assert.deepStrictEqual(decoder.decode({ _tag: 1, a: 'a' }), E.right({ _tag: 1, a: 'a' }))
       assert.deepStrictEqual(decoder.decode({ _tag: 2, b: 1 }), E.right({ _tag: 2, b: 1 }))
@@ -491,7 +491,7 @@ describe('Decoder', () => {
   }
 
   const lazyDecoder: _.Decoder<unknown, A> = _.lazy('A', () =>
-    pipe(_.type({ a: H.decoderNumberFromUnknownString }), _.intersect(_.partial({ b: lazyDecoder })))
+    pipe(_.struct({ a: H.decoderNumberFromUnknownString }), _.intersect(_.partial({ b: lazyDecoder })))
   )
 
   describe('lazy', () => {
@@ -537,7 +537,7 @@ describe('Decoder', () => {
     it('is stack safe', () => {
       expect(() => {
         E.mapLeft(_.draw)(
-          _.record(_.type({ done: _.boolean })).decode(
+          _.record(_.struct({ done: _.boolean })).decode(
             new Array(10000)
               .fill({})
               .map((v, k) => [k, v])
@@ -551,7 +551,7 @@ describe('Decoder', () => {
     })
 
     it('draw', () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string,
         b: _.number,
         c: _.array(_.boolean),
diff --git a/test/Encoder.ts b/test/Encoder.ts
index 4bedd95..d4155a6 100644
--- a/test/Encoder.ts
+++ b/test/Encoder.ts
@@ -20,8 +20,8 @@ describe('Encoder', () => {
     assert.deepStrictEqual(encoder.encode(null), null)
   })
 
-  it('type', () => {
-    const encoder = E.type({ a: H.encoderNumberToString, b: H.encoderBooleanToNumber })
+  it('struct', () => {
+    const encoder = E.struct({ a: H.encoderNumberToString, b: H.encoderBooleanToNumber })
     assert.deepStrictEqual(encoder.encode({ a: 1, b: true }), { a: '1', b: 1 })
   })
 
@@ -50,13 +50,16 @@ describe('Encoder', () => {
   })
 
   it('intersect', () => {
-    const encoder = pipe(E.type({ a: H.encoderNumberToString }), E.intersect(E.type({ b: H.encoderBooleanToNumber })))
+    const encoder = pipe(
+      E.struct({ a: H.encoderNumberToString }),
+      E.intersect(E.struct({ b: H.encoderBooleanToNumber }))
+    )
     assert.deepStrictEqual(encoder.encode({ a: 1, b: true }), { a: '1', b: 1 })
   })
 
   it('sum', () => {
-    const S1 = E.type({ _tag: E.id<'A'>(), a: H.encoderNumberToString })
-    const S2 = E.type({ _tag: E.id<'B'>(), b: H.encoderBooleanToNumber })
+    const S1 = E.struct({ _tag: E.id<'A'>(), a: H.encoderNumberToString })
+    const S2 = E.struct({ _tag: E.id<'B'>(), b: H.encoderBooleanToNumber })
     const sum = E.sum('_tag')
     const encoder = sum({ A: S1, B: S2 })
     assert.deepStrictEqual(encoder.encode({ _tag: 'A', a: 1 }), { _tag: 'A', a: '1' })
@@ -81,14 +84,14 @@ describe('Encoder', () => {
       as: Array<AOut>
     }
     const A: E.Encoder<AOut, A> = E.lazy(() =>
-      E.type({
+      E.struct({
         a: H.encoderNumberToString,
         bs: E.array(B)
       })
     )
 
     const B: E.Encoder<BOut, B> = E.lazy(() =>
-      E.type({
+      E.struct({
         b: H.encoderBooleanToNumber,
         as: E.array(A)
       })
diff --git a/test/Guard.ts b/test/Guard.ts
index ea481f0..23cd431 100644
--- a/test/Guard.ts
+++ b/test/Guard.ts
@@ -79,19 +79,19 @@ describe('Guard', () => {
     })
   })
 
-  describe('type', () => {
+  describe('struct', () => {
     it('should accept valid inputs', () => {
-      const guard = G.type({ a: G.string, b: G.number })
+      const guard = G.struct({ a: G.string, b: G.number })
       assert.strictEqual(guard.is({ a: 'a', b: 1 }), true)
     })
 
     it('should accept additional fields', () => {
-      const guard = G.type({ a: G.string, b: G.number })
+      const guard = G.struct({ a: G.string, b: G.number })
       assert.strictEqual(guard.is({ a: 'a', b: 1, c: true }), true)
     })
 
     it('should reject invalid inputs', () => {
-      const guard = G.type({ a: G.string, b: G.number })
+      const guard = G.struct({ a: G.string, b: G.number })
       assert.strictEqual(guard.is(undefined), false)
       assert.strictEqual(guard.is({ a: 'a' }), false)
     })
@@ -100,7 +100,7 @@ describe('Guard', () => {
       const undef: G.Guard<unknown, undefined> = {
         is: (u): u is undefined => u === undefined
       }
-      const guard = G.type({ a: undef })
+      const guard = G.struct({ a: undef })
       assert.strictEqual(guard.is({}), false)
     })
 
@@ -113,7 +113,7 @@ describe('Guard', () => {
           return 'b'
         }
       }
-      const guard = G.type({ a: G.string, b: G.string })
+      const guard = G.struct({ a: G.string, b: G.string })
       assert.deepStrictEqual(guard.is(new A()), true)
     })
   })
@@ -204,12 +204,12 @@ describe('Guard', () => {
 
   describe('intersect', () => {
     it('should accept valid inputs', () => {
-      const guard = pipe(G.type({ a: G.string }), G.intersect(G.type({ b: G.number })))
+      const guard = pipe(G.struct({ a: G.string }), G.intersect(G.struct({ b: G.number })))
       assert.strictEqual(guard.is({ a: 'a', b: 1 }), true)
     })
 
     it('should reject invalid inputs', () => {
-      const guard = pipe(G.type({ a: G.string }), G.intersect(G.type({ b: G.number })))
+      const guard = pipe(G.struct({ a: G.string }), G.intersect(G.struct({ b: G.number })))
       assert.strictEqual(guard.is({ a: 'a' }), false)
     })
   })
@@ -234,7 +234,7 @@ describe('Guard', () => {
     }
 
     const guard: G.Guard<unknown, A> = G.Schemable.lazy('A', () =>
-      G.type({
+      G.struct({
         a: G.number,
         b: G.array(guard)
       })
@@ -256,8 +256,8 @@ describe('Guard', () => {
 
     it('should accept valid inputs', () => {
       const guard = sum({
-        A: G.type({ _tag: G.literal('A'), a: G.string }),
-        B: G.type({ _tag: G.literal('B'), b: G.number })
+        A: G.struct({ _tag: G.literal('A'), a: G.string }),
+        B: G.struct({ _tag: G.literal('B'), b: G.number })
       })
       assert.deepStrictEqual(guard.is({ _tag: 'A', a: 'a' }), true)
       assert.deepStrictEqual(guard.is({ _tag: 'B', b: 1 }), true)
@@ -265,8 +265,8 @@ describe('Guard', () => {
 
     it('should reject invalid inputs', () => {
       const guard = sum({
-        A: G.type({ _tag: G.literal('A'), a: G.string }),
-        B: G.type({ _tag: G.literal('B'), b: G.number })
+        A: G.struct({ _tag: G.literal('A'), a: G.string }),
+        B: G.struct({ _tag: G.literal('B'), b: G.number })
       })
       assert.strictEqual(guard.is(undefined), false)
       assert.strictEqual(guard.is({}), false)
@@ -274,8 +274,8 @@ describe('Guard', () => {
 
     it('should support non-`string` tag values', () => {
       const guard = G.sum('_tag')({
-        [1]: G.type({ _tag: G.literal(1), a: G.string }),
-        [2]: G.type({ _tag: G.literal(2), b: G.number })
+        [1]: G.struct({ _tag: G.literal(1), a: G.string }),
+        [2]: G.struct({ _tag: G.literal(2), b: G.number })
       })
       assert.deepStrictEqual(guard.is({ _tag: 1, a: 'a' }), true)
       assert.deepStrictEqual(guard.is({ _tag: 2, b: 1 }), true)
diff --git a/test/JsonSchema.test.ts b/test/JsonSchema.test.ts
index 3fbaf7e..c8a86d8 100644
--- a/test/JsonSchema.test.ts
+++ b/test/JsonSchema.test.ts
@@ -41,8 +41,8 @@ describe('JsonSchema', () => {
     assert.strictEqual(validate({ a: 'a', b: 1, c: undefined }), true)
   })
 
-  it('type', () => {
-    const schema = J.type({ a: J.string, b: J.number }).compile()
+  it('struct', () => {
+    const schema = J.struct({ a: J.string, b: J.number }).compile()
     const validate = ajv.compile(schema)
     assert.strictEqual(validate({ a: 'a', b: 1 }), true)
     assert.strictEqual(validate({ a: 'a' }), false)
@@ -79,7 +79,7 @@ describe('JsonSchema', () => {
 
   describe('intersection', () => {
     it('should handle non primitive values', () => {
-      const validate = ajv.compile(pipe(J.type({ a: J.string }), J.intersect(J.type({ b: J.number }))).compile())
+      const validate = ajv.compile(pipe(J.struct({ a: J.string }), J.intersect(J.struct({ b: J.number }))).compile())
       assert.strictEqual(validate({ a: 'a', b: 1 }), true)
       assert.strictEqual(validate({ a: 'a' }), false)
     })
@@ -113,8 +113,8 @@ describe('JsonSchema', () => {
   it('sum', () => {
     const sum = J.sum('_tag')
 
-    const A = J.type({ _tag: J.literal('A'), a: J.string })
-    const B = J.type({ _tag: J.literal('B'), b: J.number })
+    const A = J.struct({ _tag: J.literal('A'), a: J.string })
+    const B = J.struct({ _tag: J.literal('B'), b: J.number })
     const validate = ajv.compile(sum({ A, B }).compile())
     assert.strictEqual(validate({ _tag: 'A', a: 'a' }), true)
     assert.strictEqual(validate({ _tag: 'B', b: 1 }), true)
@@ -137,7 +137,7 @@ describe('JsonSchema', () => {
       }
 
       const schema: J.JsonSchema<A> = J.lazy('A', () =>
-        pipe(J.type({ a: J.number }), J.intersect(J.partial({ b: schema })))
+        pipe(J.struct({ a: J.number }), J.intersect(J.partial({ b: schema })))
       )
 
       const jsonSchema = schema.compile()
diff --git a/test/JsonSchema.ts b/test/JsonSchema.ts
index 0eadcc3..bdc331c 100644
--- a/test/JsonSchema.ts
+++ b/test/JsonSchema.ts
@@ -1,5 +1,5 @@
 import * as C from 'fp-ts/Const'
-import { pipe } from 'fp-ts/function'
+import { identity, pipe } from 'fp-ts/function'
 import * as R from 'fp-ts/ReadonlyRecord'
 import { JSONSchema7 } from 'json-schema'
 import * as S from '../src/Schemable'
@@ -61,7 +61,7 @@ export function nullable<A>(or: JsonSchema<A>): JsonSchema<null | A> {
   return union(nullJsonSchema, or)
 }
 
-export function type<A>(properties: { [K in keyof A]: JsonSchema<A[K]> }): JsonSchema<A> {
+export function struct<A>(properties: { [K in keyof A]: JsonSchema<A[K]> }): JsonSchema<A> {
   return {
     compile: (lazy) =>
       C.make({
@@ -160,6 +160,8 @@ export function lazy<A>(id: string, f: () => JsonSchema<A>): JsonSchema<A> {
   }
 }
 
+export const readonly: <A>(arb: JsonSchema<A>) => JsonSchema<Readonly<A>> = identity
+
 export function union<A extends readonly [unknown, ...ReadonlyArray<unknown>]>(
   ...members: { [K in keyof A]: JsonSchema<A[K]> }
 ): JsonSchema<A[number]> {
@@ -191,7 +193,8 @@ export const Schemable: S.Schemable1<URI> & S.WithUnknownContainers1<URI> & S.Wi
   UnknownArray,
   UnknownRecord,
   nullable,
-  type,
+  type: struct,
+  struct,
   partial,
   record,
   array,
@@ -199,5 +202,6 @@ export const Schemable: S.Schemable1<URI> & S.WithUnknownContainers1<URI> & S.Wi
   intersect,
   sum,
   lazy,
+  readonly,
   union: union as S.WithUnion1<URI>['union']
 }
diff --git a/test/Schema.ts b/test/Schema.ts
index e2ec4ff..08890a6 100644
--- a/test/Schema.ts
+++ b/test/Schema.ts
@@ -38,10 +38,10 @@ describe('Schema', () => {
     check(make((S) => S.nullable(S.string)))
   })
 
-  it('type', () => {
+  it('struct', () => {
     check(
       make((S) =>
-        S.type({
+        S.struct({
           name: S.string,
           age: S.number
         })
@@ -75,12 +75,12 @@ describe('Schema', () => {
   })
 
   it('intersect', () => {
-    check(make((S) => pipe(S.type({ a: S.string }), S.intersect(S.type({ b: S.number })))))
+    check(make((S) => pipe(S.struct({ a: S.string }), S.intersect(S.struct({ b: S.number })))))
   })
 
   it('sum', () => {
-    const A = make((S) => S.type({ _tag: S.literal('A'), a: S.string }))
-    const B = make((S) => S.type({ _tag: S.literal('B'), b: S.number }))
+    const A = make((S) => S.struct({ _tag: S.literal('A'), a: S.string }))
+    const B = make((S) => S.struct({ _tag: S.literal('B'), b: S.number }))
     check(make((S) => S.sum('_tag')({ A: A(S), B: B(S) })))
   })
 
@@ -92,7 +92,7 @@ describe('Schema', () => {
     }
 
     const schema: Schema<A> = make((S) =>
-      S.lazy('A', () => pipe(S.type({ a: S.string }), S.intersect(S.partial({ b: schema(S), c: S.number }))))
+      S.lazy('A', () => pipe(S.struct({ a: S.string }), S.intersect(S.partial({ b: schema(S), c: S.number }))))
     )
     check(schema)
   })
diff --git a/test/TaskDecoder.ts b/test/TaskDecoder.ts
index aaafd27..da48f05 100644
--- a/test/TaskDecoder.ts
+++ b/test/TaskDecoder.ts
@@ -137,7 +137,7 @@ describe('UnknownTaskDecoder', () => {
 
   it('withMessage', async () => {
     const decoder = pipe(
-      _.type({
+      _.struct({
         name: _.string,
         age: _.number
       }),
@@ -196,30 +196,30 @@ describe('UnknownTaskDecoder', () => {
     })
   })
 
-  describe('type', () => {
+  describe('struct', () => {
     it('should decode a valid input', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string
       })
       assert.deepStrictEqual(await decoder.decode({ a: 'a' })(), D.success({ a: 'a' }))
     })
 
     it('should strip additional fields', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string
       })
       assert.deepStrictEqual(await decoder.decode({ a: 'a', b: 1 })(), D.success({ a: 'a' }))
     })
 
     it('should not strip fields corresponding to undefined values', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: undef
       })
       assert.deepStrictEqual(await decoder.decode({})(), D.success({ a: undefined }))
     })
 
     it('should reject an invalid input', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string
       })
       assert.deepStrictEqual(await decoder.decode(undefined)(), D.failure(undefined, 'Record<string, unknown>'))
@@ -230,7 +230,7 @@ describe('UnknownTaskDecoder', () => {
     })
 
     it('should collect all errors', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string,
         b: _.number
       })
@@ -254,7 +254,7 @@ describe('UnknownTaskDecoder', () => {
           return 'b'
         }
       }
-      const decoder = _.type({ a: _.string, b: _.string })
+      const decoder = _.struct({ a: _.string, b: _.string })
       assert.deepStrictEqual(await decoder.decode(new A())(), D.success({ a: 'a', b: 'b' }))
     })
   })
@@ -465,7 +465,7 @@ describe('UnknownTaskDecoder', () => {
 
   describe('intersect', () => {
     it('should decode a valid input', async () => {
-      const decoder = pipe(_.type({ a: _.string }), _.intersect(_.type({ b: _.number })))
+      const decoder = pipe(_.struct({ a: _.string }), _.intersect(_.struct({ b: _.number })))
       assert.deepStrictEqual(await decoder.decode({ a: 'a', b: 1 })(), D.success({ a: 'a', b: 1 }))
     })
 
@@ -475,7 +475,7 @@ describe('UnknownTaskDecoder', () => {
     })
 
     it('should accumulate all errors', async () => {
-      const decoder = pipe(_.type({ a: _.string }), _.intersect(_.type({ b: _.number })))
+      const decoder = pipe(_.struct({ a: _.string }), _.intersect(_.struct({ b: _.number })))
       assert.deepStrictEqual(
         await decoder.decode({ a: 'a' })(),
         E.left(FS.of(DE.key('b', DE.required, FS.of(DE.leaf(undefined, 'number')))))
@@ -500,16 +500,16 @@ describe('UnknownTaskDecoder', () => {
     const sum = _.sum('_tag')
 
     it('should decode a valid input', async () => {
-      const A = _.type({ _tag: _.literal('A'), a: _.string })
-      const B = _.type({ _tag: _.literal('B'), b: _.number })
+      const A = _.struct({ _tag: _.literal('A'), a: _.string })
+      const B = _.struct({ _tag: _.literal('B'), b: _.number })
       const decoder = sum({ A, B })
       assert.deepStrictEqual(await decoder.decode({ _tag: 'A', a: 'a' })(), D.success({ _tag: 'A', a: 'a' }))
       assert.deepStrictEqual(await decoder.decode({ _tag: 'B', b: 1 })(), D.success({ _tag: 'B', b: 1 }))
     })
 
     it('should reject an invalid input', async () => {
-      const A = _.type({ _tag: _.literal('A'), a: _.string })
-      const B = _.type({ _tag: _.literal('B'), b: _.number })
+      const A = _.struct({ _tag: _.literal('A'), a: _.string })
+      const B = _.struct({ _tag: _.literal('B'), b: _.number })
       const decoder = sum({ A, B })
       assert.deepStrictEqual(await decoder.decode(null)(), D.failure(null, 'Record<string, unknown>'))
       assert.deepStrictEqual(
@@ -537,7 +537,7 @@ describe('UnknownTaskDecoder', () => {
   }
 
   const lazyDecoder: _.TaskDecoder<unknown, A> = _.lazy('A', () =>
-    pipe(_.type({ a: NumberFromString }), _.intersect(_.partial({ b: lazyDecoder })))
+    pipe(_.struct({ a: NumberFromString }), _.intersect(_.partial({ b: lazyDecoder })))
   )
 
   describe('lazy', () => {
@@ -581,7 +581,7 @@ describe('UnknownTaskDecoder', () => {
 
   describe('draw', () => {
     it('draw', async () => {
-      const decoder = _.type({
+      const decoder = _.struct({
         a: _.string,
         b: _.number,
         c: _.array(_.boolean),
diff --git a/test/Type.ts b/test/Type.ts
index 2c20a15..567fe09 100644
--- a/test/Type.ts
+++ b/test/Type.ts
@@ -105,10 +105,10 @@ describe('Type', () => {
     )
   })
 
-  it('type', () => {
+  it('struct', () => {
     check(
       make((S) =>
-        S.type({
+        S.struct({
           name: S.string,
           age: S.number
         })
@@ -162,7 +162,7 @@ describe('Type', () => {
 
   it('intersect', () => {
     check(
-      make((S) => pipe(S.type({ a: S.string }), S.intersect(S.type({ b: S.number })))),
+      make((S) => pipe(S.struct({ a: S.string }), S.intersect(S.struct({ b: S.number })))),
       t.intersection([t.type({ a: t.string }), t.type({ b: t.number })])
     )
   })
@@ -171,8 +171,8 @@ describe('Type', () => {
     check(
       make((S) =>
         S.sum('_tag')({
-          A: S.type({ _tag: S.literal('A'), a: S.string }),
-          B: S.type({ _tag: S.literal('B'), b: S.number })
+          A: S.struct({ _tag: S.literal('A'), a: S.string }),
+          B: S.struct({ _tag: S.literal('B'), b: S.number })
         })
       ),
       t.union([t.type({ _tag: t.literal('A'), a: t.string }), t.type({ _tag: t.literal('B'), b: t.number })])
@@ -187,7 +187,7 @@ describe('Type', () => {
     }
 
     const schema: Schema<A> = make((S) =>
-      S.lazy('A', () => pipe(S.type({ a: S.string }), S.intersect(S.partial({ b: schema(S), c: S.number }))))
+      S.lazy('A', () => pipe(S.struct({ a: S.string }), S.intersect(S.partial({ b: schema(S), c: S.number }))))
     )
     const type: t.Type<A> = t.recursion('A', () =>
       t.intersection([t.type({ a: t.string }), t.partial({ b: type, c: t.number })])