Packages

Type unions without wrappers.

Current section

Files

Jump to
untag src untag.gleam
Raw

src/untag.gleam

//// The type `Or` is an type union with no wrapping.
//// `Or2(Int, String, Float)` is the same as `Int | String | Float`,
//// not `{Left, Int} | {Right, {Left, String} | {Right, Float}}`
////
//// You can either treat it as a dynamic type that gives you some
//// options on what it might be, or as a group of types that can
//// be whittled down with decoders.
////
//// There are functions for (example)
//// ```
//// * Encoding: tail_3rd t3 -> Or2(t1,t2,t3)
//// * Decoding: dehead Or2(t1,t2,t3) -> t1?
//// * Moving: flip Or(a,b) -> Or(b,a)
//// ```
////
//// The repeated functions with numbers are used to move around
//// the types, so you dont have to make an external casting function.
//// For example, `rev4_with_tail` just returns the same value with
//// a different, equivalent type. It asserts that
//// `Or(k, Or(l, Or(m, n)))` is equivalent to `Or(n, Or(m, Or(l, k)))`.
////
//// head_4th puts a type in the 4th position in a union.
//// ```
//// V------------------------->V
//// fn(k) -> Or(l, Or(m, Or(n, Or(k, o))))
//// ```
////
//// Using tail_4th puts the type in the 4th position at the
//// very end of the union, terminating the list of types.
//// ```
//// V-----------------------V
//// fn(k) -> Or(l, Or(m, Or(n, k)))
//// ```
import gleam/dynamic
import gleam/dynamic/decode.{type Decoder} as dec
import gleam/result
@external(erlang, "untag_ffi", "id")
@external(javascript, "untag_ffi.mjs", "id")
@internal
pub fn id(x: a) -> b
pub fn to_dynamic(or: Or(a, b)) -> dynamic.Dynamic {
id(or)
}
pub fn decode(this or, with decoder: Decoder(_)) {
or |> to_dynamic |> dec.run(decoder)
}
/// Tries to decode the head. if it fails, it will return the value as the tail type.
///
/// This function is unsound when the generic type of the decoder
/// does not directly match with the data it wants to decode.
/// For example,
/// ```
/// "string"
/// // String -> Or(String, b)
/// |> head
/// |> dehead_unsafe(dec.int |> dec.map(int.to_string))
/// ```
/// The value of the Or(String, b) is "string", not any integer.
///
pub fn dehead_unsafe(
this union: Or(head, tail),
with decoder: Decoder(head),
) -> Result(head, #(tail, List(dec.DecodeError))) {
union
|> to_dynamic
|> dec.run(decoder)
|> result.map_error(fn(errs) { #(id(union), errs) })
}
/// Unsafe. See [dehead_unsafe]
pub fn detail_unsafe(
this union: Or(head, tail),
with decoder: Decoder(tail),
) -> Result(tail, #(head, List(dec.DecodeError))) {
union
|> to_dynamic
|> dec.run(decoder)
|> result.map_error(fn(errs) { #(id(union), errs) })
}
pub type Or(t1, t2)
pub type Or2(t1, t2, t3) =
Or(t1, Or(t2, t3))
pub type Or3(a, b, c, d) =
Or(a, Or2(b, c, d))
pub type Or4(a, b, c, d, e) =
Or(a, Or3(b, c, d, e))
pub type Or5(a, b, c, d, e, f) =
Or(a, Or4(b, c, d, e, f))
pub type Or6(a, b, c, d, e, f, g) =
Or(a, Or5(b, c, d, e, f, g))
pub type Or7(a, b, c, d, e, f, g, h) =
Or(a, Or6(b, c, d, e, f, g, h))
pub type Or8(a, b, c, d, e, f, g, h, i) =
Or(a, Or7(b, c, d, e, f, g, h, i))
pub type Or9(a, b, c, d, e, f, g, h, i, j) =
Or(a, Or8(b, c, d, e, f, g, h, i, j))
pub const head: fn(h) -> Or(h, _) = id
pub const tail: fn(t) -> Or(_, t) = id
fn tail_in(tn, x) {
tn(tail(x))
}
pub const tail_2nd = tail
pub fn tail_3rd(x) {
tail_2nd |> tail_in(x)
}
pub fn tail_4th(x) {
tail_3rd |> tail_in(x)
}
pub fn tail_5th(x) {
tail_4th |> tail_in(x)
}
pub fn tail_6th(x) {
tail_5th |> tail_in(x)
}
pub fn tail_7th(x) {
tail_6th |> tail_in(x)
}
pub fn tail_8th(x) {
tail_7th |> tail_in(x)
}
pub fn tail_9th(x) {
tail_8th |> tail_in(x)
}
pub fn tail_10th(x) {
tail_9th |> tail_in(x)
}
fn head_in(tn, x) {
tn(head(x))
}
/// h_<n>th = become nth head
pub const head_1st = head
pub fn head_2nd(x) {
tail_2nd |> head_in(x)
}
pub fn head_3th(x) {
tail_3rd |> head_in(x)
}
pub fn head_4th(x) {
tail_4th |> head_in(x)
}
pub fn head_5th(x) {
tail_5th |> head_in(x)
}
pub fn head_6th(x) {
tail_6th |> head_in(x)
}
pub fn head_7th(x) {
tail_7th |> head_in(x)
}
pub fn head_8th(x) {
tail_8th |> head_in(x)
}
pub fn head_9th(x) {
tail_9th |> head_in(x)
}
pub fn head_10th(x) {
tail_10th |> head_in(x)
}
pub fn flip(or: Or(a, b)) -> Or(b, a) {
id(or)
}
pub fn flip_tail(or: Or(t1, Or(t2, t3))) -> Or(t1, Or(t3, t2)) {
id(or)
}
pub fn flip_head(or) {
or |> flipped(flip_tail)
}
pub fn flipped(or, f) {
or |> flip |> f |> flip
}
pub fn give(or: Or(Or(t1, t2), t3)) -> Or(t2, Or(t1, t3)) {
id(or)
}
pub fn give2(or) {
or |> give |> give
}
pub fn give3(or) {
or |> give |> give2
}
pub fn give4(or) {
or |> give |> give3
}
pub fn give5(or) {
or |> give |> give4
}
pub fn give6(or) {
or |> give |> give5
}
pub fn give7(or) {
or |> give |> give6
}
pub fn give8(or) {
or |> give |> give7
}
pub fn take(or: Or(t1, Or(t2, t3))) -> Or(Or(t2, t1), t3) {
or |> flipped(give)
}
pub fn take2(or) {
or |> take |> take
}
pub fn take3(or) {
or |> take |> take2
}
pub fn take4(or) {
or |> take |> take3
}
pub fn take5(or) {
or |> take |> take4
}
pub fn take6(or) {
or |> take |> take5
}
pub fn take7(or) {
or |> take |> take6
}
pub fn take8(or) {
or |> take |> take7
}
pub fn rev3_with_tail(or) {
or |> flip |> give
}
pub fn rev4_with_tail(or) {
or |> rev3_with_tail |> give
}
pub fn rev5_with_tail(or) {
or |> rev4_with_tail |> give
}
pub fn rev6_with_tail(or) {
or |> rev5_with_tail |> give
}
pub fn rev7_with_tail(or) {
or |> rev6_with_tail |> give
}
pub fn rev8_with_tail(or) {
or |> rev7_with_tail |> give
}
pub fn rev9_with_tail(or) {
or |> rev8_with_tail |> give
}
pub fn rev10_with_tail(or) {
or |> rev9_with_tail |> give
}