Skip to content

Instantly share code, notes, and snippets.

@tynanbe
Last active October 22, 2023 23:25
Show Gist options
  • Save tynanbe/0d2a870ad099c2196598277016aef85f to your computer and use it in GitHub Desktop.
Save tynanbe/0d2a870ad099c2196598277016aef85f to your computer and use it in GitHub Desktop.
Proposed Gleam tuple spread syntax, Erlang target proof of concept
import gleam/dynamic.{DecodeErrors, Dynamic, field, int, list, string}
import gleam/io
import gleam/json
import gleam/list
import gleam/result
type DynResult(a) =
Result(a, DecodeErrors)
pub opaque type Collector(a) {
Collector(data: Dynamic, result: DynResult(a))
}
pub fn collector(data: Dynamic) -> Collector(#()) {
Collector(data: data, result: Ok(#()))
}
// Spread tuple type's elements into another tuple type
pub fn get(
from prev: Collector(a),
with decoder: fn(Dynamic) -> DynResult(b),
) -> Collector(spread_a_and_append_b_into_tuple) {
let Collector(data, result) = prev
case decoder(data), result {
Ok(value), Ok(tuple) ->
// Spread tuple's elements into another tuple
tuple_spread_into_tuple(tuple, value)
|> Ok
Error(new_errors), Error(errors) ->
errors
|> list.append(new_errors)
|> Error
Error(errors), _ | _, Error(errors) -> Error(errors)
}
|> Collector(data: data)
}
pub fn apply(
from prev: Collector(a),
// Spread tuple type's elements into fn type's parameters
to constructor: spread_a_into_fn_params_returning_b,
) -> DynResult(b) {
use args <- result.map(over: prev.result)
// Spread tuple's elements into fn's arguments
tuple_spread_into_constructor(args, constructor)
}
@external(erlang, "erlang", "append_element")
fn tuple_spread_into_tuple(tuple: a, value: b) -> c
fn tuple_spread_into_constructor(tuple: a, constructor: b) -> c {
do_apply(constructor, tuple_to_list(tuple))
}
@external(erlang, "erlang", "apply")
fn do_apply(constructor: f, args: dyn_list) -> record
@external(erlang, "erlang", "tuple_to_list")
fn tuple_to_list(tuple: a) -> dyn_list
/// Begin Demo
pub type FooBar {
FooBar(foo: Int, bar: String)
}
pub fn decode_foobar(data: Dynamic) -> DynResult(FooBar) {
collector(data)
|> get(field("foo", of: int))
|> get(field("bar", of: string))
|> apply(to: FooBar)
}
pub fn decode_foobars(data: String) -> Result(List(FooBar), json.DecodeError) {
let decoder = list(of: decode_foobar)
json.decode(from: data, using: decoder)
}
pub fn bad_decode_foobars(
data: String,
) -> Result(List(FooBar), json.DecodeError) {
let decode_foobar = fn(data) {
collector(data)
|> get(field("foo", of: string))
|> get(field("bar", of: int))
|> apply(to: FooBar)
}
let decoder = list(of: decode_foobar)
json.decode(from: data, using: decoder)
}
pub fn old_decode_foobars(
data: String,
) -> Result(List(FooBar), json.DecodeError) {
let decode_foobar =
FooBar
|> dynamic.decode2(field("foo", of: int), field("bar", of: string))
let decoder = list(of: decode_foobar)
json.decode(from: data, using: decoder)
}
pub fn main() {
let data =
"
[
{
\"foo\" : 0,
\"bar\" : \"a\"
},
{
\"foo\" : 1,
\"bar\" : \"b\"
}
]
"
data
|> old_decode_foobars
|> io.debug
data
|> decode_foobars
|> io.debug
data
|> bad_decode_foobars
|> io.debug
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment