Last active
September 16, 2021 01:49
-
-
Save mauroc8/a2a56adb70e3a4ee72967966085955ad to your computer and use it in GitHub Desktop.
WIP Stringify (~= Debug.toString)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Stringify exposing | |
( Stringify | |
, toString | |
, int, float, string, unit | |
, list, tuple, triple | |
, lazy | |
, Record, record, field, buildRecord | |
, Union, union, variant0, variant1, variant2, variant3, variant4, buildUnion, Variant | |
, bool, maybe, result | |
) | |
{-| Combinatorial API to create stringifiers: functions that convert an elm value | |
in a `Debug.toString`-like text representation. | |
Similar to JSON encoders, but more ergonomic with elm types. | |
@docs Stringify | |
@docs toString | |
@docs int, float, string, unit | |
@docs list, tuple, triple | |
@docs lazy | |
@docs Record, record, field, buildRecord | |
@docs Union, union, variant0, variant1, variant2, variant3, variant4, buildUnion, Variant | |
@docs bool, maybe, result | |
-} | |
import Json.Encode | |
{-| -} | |
type Stringify a | |
= Stringify (( Metadata, a ) -> String) | |
{-| -} | |
toString : Stringify a -> a -> String | |
toString = | |
toStringWithMetadata { context = Root } | |
toStringWithMetadata : Metadata -> Stringify a -> a -> String | |
toStringWithMetadata metadata (Stringify f) a = | |
f ( metadata, a ) | |
type alias Metadata = | |
{ context : Context } | |
type Context | |
= Root | |
| RecordValue | |
| ListValue | |
| VariantValue | |
| TupleValue | |
{-| Creates a stringifier that ignores the metadata and always calls the given function. | |
-} | |
fromFunction : (b -> String) -> Stringify b | |
fromFunction f = | |
Stringify (\( _, a ) -> f a) | |
{-| -} | |
int : Stringify Int | |
int = | |
fromFunction String.fromInt | |
{-| -} | |
float : Stringify Float | |
float = | |
fromFunction String.fromFloat | |
{-| -} | |
string : Stringify String | |
string = | |
fromFunction (Json.Encode.encode 0 << Json.Encode.string) | |
{-| -} | |
unit : Stringify () | |
unit = | |
fromFunction (\() -> "()") | |
{-| -} | |
tuple : Stringify a -> Stringify b -> Stringify ( a, b ) | |
tuple stringifyA stringifyB = | |
Stringify <| | |
\( _, ( a, b ) ) -> | |
let | |
first = | |
toStringWithMetadata { context = TupleValue } stringifyA a | |
second = | |
toStringWithMetadata { context = TupleValue } stringifyB b | |
in | |
"( " ++ first ++ ", " ++ second ++ " )" | |
{-| -} | |
triple : Stringify a -> Stringify b -> Stringify c -> Stringify ( a, b, c ) | |
triple stringifyA stringifyB stringifyC = | |
Stringify <| | |
\( _, ( a, b, c ) ) -> | |
let | |
metadata = | |
{ context = TupleValue } | |
first = | |
toStringWithMetadata metadata stringifyA a | |
second = | |
toStringWithMetadata metadata stringifyB b | |
third = | |
toStringWithMetadata metadata stringifyC c | |
in | |
"( " | |
++ first | |
++ ", " | |
++ second | |
++ ", " | |
++ third | |
++ " )" | |
{-| -} | |
list : Stringify a -> Stringify (List a) | |
list stringifyA = | |
Stringify <| | |
\( _, list_ ) -> | |
case list_ of | |
[] -> | |
"[]" | |
_ -> | |
let | |
values = | |
List.map | |
(toStringWithMetadata { context = ListValue } stringifyA) | |
list_ | |
|> String.join ", " | |
in | |
"[ " ++ values ++ " ]" | |
{-| -} | |
lazy : (() -> Stringify a) -> Stringify a | |
lazy f = | |
Stringify | |
(\params -> | |
let | |
(Stringify g) = | |
f () | |
in | |
g params | |
) | |
--- Record | |
{-| -} | |
type Record record | |
= Record (( Metadata, record ) -> List ( String, String )) | |
{-| | |
point : Stringify { x : Int, y : Int } | |
point = | |
record | |
|> field "x" .x int | |
|> field "y" .y int | |
|> buildRecord | |
-} | |
record : Record record | |
record = | |
Record (\_ -> []) | |
{-| -} | |
field : String -> (record -> field) -> Stringify field -> Record record -> Record record | |
field name getter stringifyField (Record getFields) = | |
Record <| | |
\( meta, record_ ) -> | |
let | |
fields = | |
getFields ( meta, record_ ) | |
in | |
( name | |
, toStringWithMetadata | |
{ context = RecordValue } | |
stringifyField | |
(getter record_) | |
) | |
:: fields | |
{-| -} | |
buildRecord : Record record -> Stringify record | |
buildRecord (Record getFields) = | |
Stringify <| | |
\( meta, record_ ) -> | |
let | |
fields = | |
getFields ( meta, record_ ) | |
|> List.reverse | |
in | |
case fields of | |
[] -> | |
"{}" | |
_ -> | |
let | |
propToStr ( name, value ) = | |
name ++ " = " ++ value | |
csv = | |
List.map propToStr fields | |
|> String.join ", " | |
in | |
"{ " ++ csv ++ " }" | |
-- Union | |
{-| -} | |
type Union match | |
= Union match | |
{-| -} | |
type Variant | |
= Variant | |
{-| -} | |
union : match -> Union match | |
union match = | |
Union match | |
variantToString : Stringify a -> a -> String | |
variantToString stringifyA a = | |
toStringWithMetadata { context = VariantValue } stringifyA a | |
stringifyVariant : String -> List String -> Stringify b | |
stringifyVariant name params = | |
Stringify <| | |
\( meta, _ ) -> | |
(name :: params) | |
|> String.join " " | |
|> withParenthesisIfNeeded meta.context | |
{-| -} | |
variant0 : String -> Union (Stringify Variant -> match) -> Union match | |
variant0 name (Union f) = | |
Union (f (Stringify (always name))) | |
{-| -} | |
variant1 : String -> Stringify a -> Union ((a -> Stringify Variant) -> match) -> Union match | |
variant1 name stringifyA (Union f) = | |
Union | |
(f | |
(\a -> | |
stringifyVariant name | |
[ variantToString stringifyA a | |
] | |
) | |
) | |
{-| -} | |
variant2 : | |
String | |
-> Stringify a | |
-> Stringify b | |
-> Union ((a -> b -> Stringify Variant) -> match) | |
-> Union match | |
variant2 name stringifyA stringifyB (Union match) = | |
Union | |
(match | |
(\a b -> | |
stringifyVariant name | |
[ variantToString stringifyA a | |
, variantToString stringifyB b | |
] | |
) | |
) | |
{-| -} | |
variant3 : | |
String | |
-> Stringify a | |
-> Stringify b | |
-> Stringify c | |
-> Union ((a -> b -> c -> Stringify Variant) -> match) | |
-> Union match | |
variant3 name strA strB strC (Union match) = | |
Union <| | |
match <| | |
\a b c -> | |
stringifyVariant name | |
[ variantToString strA a | |
, variantToString strB b | |
, variantToString strC c | |
] | |
{-| -} | |
variant4 : | |
String | |
-> Stringify a | |
-> Stringify b | |
-> Stringify c | |
-> Stringify d | |
-> Union ((a -> b -> c -> d -> Stringify Variant) -> match) | |
-> Union match | |
variant4 name strA strB strC strD (Union match) = | |
Union <| | |
match <| | |
\a b c d -> | |
stringifyVariant name | |
[ variantToString strA a | |
, variantToString strB b | |
, variantToString strC c | |
, variantToString strD d | |
] | |
{-| -} | |
buildUnion : Union (union -> Stringify Variant) -> Stringify union | |
buildUnion (Union stringify) = | |
Stringify <| | |
\( meta, union_ ) -> | |
toStringWithMetadata | |
meta | |
(stringify union_) | |
Variant | |
withParenthesisIfNeeded : Context -> String -> String | |
withParenthesisIfNeeded context withNoParenthesis = | |
case context of | |
Root -> | |
withNoParenthesis | |
RecordValue -> | |
withNoParenthesis | |
ListValue -> | |
withNoParenthesis | |
VariantValue -> | |
"(" ++ withNoParenthesis ++ ")" | |
TupleValue -> | |
withNoParenthesis | |
{-| -} | |
bool : Stringify Bool | |
bool = | |
union | |
(\true_ false_ value -> | |
if value then | |
true_ | |
else | |
false_ | |
) | |
|> variant0 "True" | |
|> variant0 "False" | |
|> buildUnion | |
{-| -} | |
maybe : Stringify a -> Stringify (Maybe a) | |
maybe stringifyA = | |
union | |
(\just nothing value -> | |
case value of | |
Just a -> | |
just a | |
Nothing -> | |
nothing | |
) | |
|> variant1 "Just" stringifyA | |
|> variant0 "Nothing" | |
|> buildUnion | |
{-| -} | |
result : Stringify e -> Stringify a -> Stringify (Result e a) | |
result stringifyError stringifyData = | |
union | |
(\ok err value -> | |
case value of | |
Ok data -> | |
ok data | |
Err error -> | |
err error | |
) | |
|> variant1 "Ok" stringifyData | |
|> variant1 "Err" stringifyError | |
|> buildUnion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment