Skip to content

Instantly share code, notes, and snippets.

@mauroc8
Last active September 16, 2021 01:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mauroc8/a2a56adb70e3a4ee72967966085955ad to your computer and use it in GitHub Desktop.
Save mauroc8/a2a56adb70e3a4ee72967966085955ad to your computer and use it in GitHub Desktop.
WIP Stringify (~= Debug.toString)
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