Open chrischen opened 4 years ago
While you show an example of pattern matching here, it would be nice to have an example of how to do pattern matching with custom types
Not sure what you mean, Option
is a custom type.
export interface None {
readonly _tag: 'None'
}
export interface Some<A> {
readonly _tag: 'Some'
readonly value: A
}
export type Option<A> = None | Some<A>
const maybe = <A, B>(whenNone: () => B, whenSome: (a: A) => B) => (fa: Option<A>): B => {
switch (fa._tag) {
case 'None':
return whenNone()
case 'Some':
return whenSome(fa.value)
}
}
For example, should we just add a _tag property to all run time interface types?
Yes, that's how you define sum types in TypeScript (aka Discriminated Unions).
Note that _tag
is just my convention, you can choose another name.
How should we do it for opaque scalar types, or Newtypes? Should it be used with Branded types and simply set a runtime value on the branded type definition?
Not sure what you mean
How should we do it for opaque scalar types, or Newtypes? Should it be used with Branded types and simply set a runtime value on the branded type definition?
Not sure what you mean
For example branding a type makes it an intersection with { SomeTypeBranded: unique symbol }. If I then also want the type to have a runtime value such as for pattern matching, do I also still define a { _tag: 'TypeName' } property or should I just add a runtime value to the branded type like so:
interface Target {
_tag: 'Target';
size: number;
}
const BigTarget = t.brand(
Target,
(T): T is t.Branded<Target, { readonly _tag: 'BigTarget' }> =>
Target.is(T),
'BigTarget'
);
If a type is not a sum type (like string
, number
or Int
) you don't need to provide a tag field
export interface IntBrand {
readonly Int: unique symbol
}
export type Int = number & IntBrand
export interface Person {
name: string
age: Int
}
You add a tag when you define a sum type
export type User = { type: 'Anonymous' } | { type: 'LoggedIn'; person: Person }
export const match = <R>(onAnonymous: () => R, onLoggedIn: (person: Person) => R) => (user: User): R => {
switch (user.type) {
case 'Anonymous':
return onAnonymous()
case 'LoggedIn':
return onLoggedIn(user.person)
}
}
Is it correct to say that the branded type must be used on a non-sum type and it must be a sub-type of the input type?
Is it correct to say that the branded type must be used on a non-sum type
@chrischen I'm not sure how these things are related to fp-ts
: discriminated unions are a TypeScript feature and branded types are a hack to make up for the lack of opaque types in TypeScript (there's no branded types in fp-ts
).
Anyway if you are talking about this kind of implementation of branded types:
export interface IntBrand {
readonly Int: unique symbol
}
export type Int = number & IntBrand
then you can use Int
everywhere you would use any other type, that's not different from using string
or number
.
Here I'm using Int
in a sum type (the sum type, or "discriminated union", is User
)
export type User = { type: 'Anonymous' } | { type: 'LoggedIn'; name: string, age: Int }
and it must be a sub-type of the input type?
again, if you are talking about this kind of implementation of branded types, then Int
is trivially a subtype of number
since is defined as
number & IntBrand
Sorry, I meant to post this in io-ts. I was specifically referring to the brand() constructor in io-ts, and if it's meant to be used to make interface types opaque.
📖 Documentation
While you show an example of pattern matching here, it would be nice to have an example of how to do pattern matching with custom types. https://gcanti.github.io/fp-ts/guides/purescript.html
For example, should we just add a
_tag
property to all run time interface types? How should we do it for opaque scalar types, or Newtypes?Should it be used with Branded types and simply set a runtime value on the branded type definition?
These are just some questions I have as a new user of your excellent libraries.