packages feed

haskell-to-elm 0.2.1.0 → 0.3.0.0

raw patch · 6 files changed

+1034/−691 lines, 6 filesdep −protoludedep ~elm-syntaxdep ~generics-sopnew-component:exe:parameterised-examplePVP ok

version bump matches the API change (PVP)

Dependencies removed: protolude

Dependency ranges changed: elm-syntax, generics-sop

API changes (from Hackage documentation)

+ Language.Haskell.To.Elm: class DeriveParameterisedElmDecoderDefinition numParams value a
+ Language.Haskell.To.Elm: class DeriveParameterisedElmEncoderDefinition numParams value a
+ Language.Haskell.To.Elm: class DeriveParameterisedElmTypeDefinition numParams a
+ Language.Haskell.To.Elm: data Parameter (n :: Nat)
+ Language.Haskell.To.Elm: deriveParameterisedElmDecoderDefinition :: DeriveParameterisedElmDecoderDefinition numParams value a => Options -> Options -> Qualified -> Definition
+ Language.Haskell.To.Elm: deriveParameterisedElmEncoderDefinition :: DeriveParameterisedElmEncoderDefinition numParams value a => Options -> Options -> Qualified -> Definition
+ Language.Haskell.To.Elm: deriveParameterisedElmTypeDefinition :: DeriveParameterisedElmTypeDefinition numParams a => Options -> Qualified -> Definition
+ Language.Haskell.To.Elm: instance (GHC.TypeNats.KnownNat numParams, Generics.SOP.Universe.HasDatatypeInfo a, Data.SOP.Constraint.All2 Language.Haskell.To.Elm.HasElmType (Generics.SOP.Universe.Code a)) => Language.Haskell.To.Elm.DeriveParameterisedElmTypeDefinition numParams a
+ Language.Haskell.To.Elm: instance (Language.Haskell.To.Elm.HasElmType a, GHC.TypeNats.KnownNat numParams, Generics.SOP.Universe.HasDatatypeInfo a, Data.SOP.Constraint.All2 (Language.Haskell.To.Elm.HasElmDecoder Data.Aeson.Types.Internal.Value) (Generics.SOP.Universe.Code a)) => Language.Haskell.To.Elm.DeriveParameterisedElmDecoderDefinition numParams Data.Aeson.Types.Internal.Value a
+ Language.Haskell.To.Elm: instance (Language.Haskell.To.Elm.HasElmType a, GHC.TypeNats.KnownNat numParams, Generics.SOP.Universe.HasDatatypeInfo a, Data.SOP.Constraint.All2 (Language.Haskell.To.Elm.HasElmEncoder Data.Aeson.Types.Internal.Value) (Generics.SOP.Universe.Code a)) => Language.Haskell.To.Elm.DeriveParameterisedElmEncoderDefinition numParams Data.Aeson.Types.Internal.Value a
+ Language.Haskell.To.Elm: instance GHC.TypeNats.KnownNat n => Language.Haskell.To.Elm.HasElmType (Language.Haskell.To.Elm.Parameter n)
+ Language.Haskell.To.Elm: instance forall b (numParams :: GHC.Types.Nat) (f :: * -> b). Language.Haskell.To.Elm.DeriveParameterisedElmTypeDefinition (numParams GHC.TypeNats.+ 1) (f (Language.Haskell.To.Elm.Parameter numParams)) => Language.Haskell.To.Elm.DeriveParameterisedElmTypeDefinition numParams f
+ Language.Haskell.To.Elm: instance forall k (n :: GHC.Types.Nat) (value :: k). GHC.TypeNats.KnownNat n => Language.Haskell.To.Elm.HasElmDecoder value (Language.Haskell.To.Elm.Parameter n)
+ Language.Haskell.To.Elm: instance forall k (n :: GHC.Types.Nat) (value :: k). GHC.TypeNats.KnownNat n => Language.Haskell.To.Elm.HasElmEncoder value (Language.Haskell.To.Elm.Parameter n)
+ Language.Haskell.To.Elm: instance forall k b (numParams :: GHC.Types.Nat) (value :: k) (f :: * -> b). Language.Haskell.To.Elm.DeriveParameterisedElmDecoderDefinition (numParams GHC.TypeNats.+ 1) value (f (Language.Haskell.To.Elm.Parameter numParams)) => Language.Haskell.To.Elm.DeriveParameterisedElmDecoderDefinition numParams value f
+ Language.Haskell.To.Elm: instance forall k b (numParams :: GHC.Types.Nat) (value :: k) (f :: * -> b). Language.Haskell.To.Elm.DeriveParameterisedElmEncoderDefinition (numParams GHC.TypeNats.+ 1) value (f (Language.Haskell.To.Elm.Parameter numParams)) => Language.Haskell.To.Elm.DeriveParameterisedElmEncoderDefinition numParams value f
+ Language.Haskell.To.Elm: parameterName :: Int -> Qualified
- Language.Haskell.To.Elm: deriveElmJSONDecoder :: forall a. (HasDatatypeInfo a, HasElmType a, All2 (HasElmDecoder Value) (Code a)) => Options -> Options -> Qualified -> Definition
+ Language.Haskell.To.Elm: deriveElmJSONDecoder :: forall a. DeriveParameterisedElmDecoderDefinition 0 Value a => Options -> Options -> Qualified -> Definition
- Language.Haskell.To.Elm: deriveElmJSONEncoder :: forall a. (HasDatatypeInfo a, HasElmType a, All2 (HasElmEncoder Value) (Code a)) => Options -> Options -> Qualified -> Definition
+ Language.Haskell.To.Elm: deriveElmJSONEncoder :: forall a. DeriveParameterisedElmEncoderDefinition 0 Value a => Options -> Options -> Qualified -> Definition
- Language.Haskell.To.Elm: deriveElmTypeDefinition :: forall a. (HasDatatypeInfo a, All2 HasElmType (Code a)) => Options -> Qualified -> Definition
+ Language.Haskell.To.Elm: deriveElmTypeDefinition :: forall a. DeriveParameterisedElmTypeDefinition 0 a => Options -> Qualified -> Definition

Files

CHANGELOG.md view
@@ -2,6 +2,15 @@  ## Unreleased changes +## 0.3.0.0++- Make compatible with GHC 8.8+- Add support for parameterised types++## 0.2.1.0++- Update to `elm-syntax-0.2.0.0`, adding simplification of the generated definitions+ ## 0.2.0.0  - Merged the `HasElmType` and `HasElmDefinition` classes
README.md view
@@ -1,4 +1,4 @@-# haskell-to-elm [![Hackage](https://img.shields.io/hackage/v/haskell-to-elm.svg)](https://hackage.haskell.org/package/haskell-to-elm)+# haskell-to-elm [![Build Status](https://travis-ci.com/folq/haskell-to-elm.svg?branch=master)](https://travis-ci.com/folq/haskell-to-elm) [![Hackage](https://img.shields.io/hackage/v/haskell-to-elm.svg)](https://hackage.haskell.org/package/haskell-to-elm)  `haskell-to-elm` is a library that takes Haskell type definitions as input and generates matching Elm type definitions and JSON encoders and decoders that@@ -89,11 +89,11 @@ main = do   let     definitions =-      jsonDefinitions @User+      Simplification.simplifyDefinition <$>+        jsonDefinitions @User      modules =-      Simplification.simplifyDefinition <$>-        Pretty.modules definitions+      Pretty.modules definitions    forM_ (HashMap.toList modules) $ \(_moduleName, contents) ->     print contents@@ -130,6 +130,109 @@  See [this file](examples/User.hs) for the full code with imports. +## Parameterised types++Since version 0.3.0.0, `haskell-to-elm` supports generating code for types with type parameters.++For example, let's say we have the following Haskell type:++```haskell+data Result e a+  = Err e+  | Ok a+  deriving (Generic, Aeson.ToJSON, SOP.Generic, SOP.HasDatatypeInfo)+```++We can derive the corresponding Elm type and JSON encoders and decoder+definitions with the following code:++```haskell+instance HasElmType Result where+  elmDefinition =+    Just $ deriveElmTypeDefinition @Result defaultOptions "Api.Result.Result"++instance HasElmDecoder Aeson.Value Result where+  elmDecoderDefinition =+    Just $ deriveElmJSONDecoder @Result defaultOptions Aeson.defaultOptions "Api.Result.decoder"++instance HasElmEncoder Aeson.Value Result where+  elmEncoderDefinition =+    Just $ deriveElmJSONEncoder @Result defaultOptions Aeson.defaultOptions "Api.Result.encoder"+```++For parameterised types we also have to add instances for how to handle the+type when it's fully applied to type arguments. Like this:++```haskell+instance (HasElmType a, HasElmType b) => HasElmType (Result a b) where+  elmType =+    Type.apps (elmType @Result) [elmType @a, elmType @b]++instance (HasElmDecoder Aeson.Value a, HasElmDecoder Aeson.Value b) => HasElmDecoder Aeson.Value (Result a b) where+  elmDecoder =+    Expression.apps (elmDecoder @Aeson.Value @Result) [elmDecoder @Aeson.Value @a, elmDecoder @Aeson.Value @b]++instance (HasElmEncoder Aeson.Value a, HasElmDecoder Aeson.Value b) => HasElmEncoder Aeson.Value (Result a b) where+  elmEncoder =+    Expression.apps (elmEncoder @Aeson.Value @Result) [elmEncoder @Aeson.Value @a, elmDecoder @Aeson.Value @b]+```++The rationale for having two instances of the classes for each type is that we+both have to describe how the type is defined with the unapplied instances,+which generates parameterised types, encoders, and decoders, and then we have+to describe how to actually use those parameterised entities with the applied+instances.++These instances print the following code when run:++```elm+module Api.Result exposing (..)++import Json.Decode+import Json.Decode.Pipeline+import Json.Encode+++type Result a b+    = Err a+    | Ok b+++encoder : (a -> Json.Encode.Value) -> (b -> Json.Encode.Value) -> Result a b -> Json.Encode.Value+encoder a b c =+    case c of+        Err d ->+            Json.Encode.object [ ("tag" , Json.Encode.string "Err")+            , ("contents" , a d) ]++        Ok d ->+            Json.Encode.object [ ("tag" , Json.Encode.string "Ok")+            , ("contents" , b d) ]+++decoder : Json.Decode.Decoder a -> Json.Decode.Decoder b -> Json.Decode.Decoder (Result a b)+decoder a b =+    Json.Decode.field "tag" Json.Decode.string |>+    Json.Decode.andThen (\c -> case c of+        "Err" ->+            Json.Decode.succeed Err |>+            Json.Decode.Pipeline.required "contents" a++        "Ok" ->+            Json.Decode.succeed Ok |>+            Json.Decode.Pipeline.required "contents" b++        _ ->+            Json.Decode.fail "No matching constructor")+```++Notice that the generator encoder and decoder are parameterised by the encoder+and decoder for their argument.++In an actual project we would be writing the code to disk instead of printing it.++See [this file](examples/Parameterised.hs) for the full code with imports.+ ## Roadmap  - [x] Derive JSON encoders and generically@@ -138,7 +241,8 @@   - [x] Separate pretty printing from code generation: [elm-syntax](https://github.com/folq/elm-syntax) - [x] Generate Elm modules - [x] Servant client library generation: [servant-to-elm](https://github.com/folq/servant-to-elm)-- [x] Test that encoding and decoding round-trip: [elm-to-haskell-test](https://github.com/folq/elm-to-haskell-test)+- [x] Test that encoding and decoding round-trip: [haskell-to-elm-test](https://github.com/folq/haskell-to-elm-test)+- [x] Support parameterised types  ## Related projects 
+ examples/Parameterised.hs view
@@ -0,0 +1,62 @@+{-# language DeriveAnyClass #-}+{-# language DeriveGeneric #-}+{-# language FlexibleContexts #-}+{-# language MultiParamTypeClasses #-}+{-# language OverloadedStrings #-}+{-# language ScopedTypeVariables #-}+{-# language TypeApplications #-}+module Parameterised where++import qualified Data.Aeson as Aeson+import Data.Foldable+import qualified Data.HashMap.Lazy as HashMap+import qualified Generics.SOP as SOP+import GHC.Generics++import qualified Language.Elm.Expression as Expression+import qualified Language.Elm.Pretty as Pretty+import qualified Language.Elm.Simplification as Simplification+import qualified Language.Elm.Type as Type+import Language.Haskell.To.Elm++data Result e a+  = Err e+  | Ok a+  deriving (Generic, Aeson.ToJSON, SOP.Generic, SOP.HasDatatypeInfo)++instance HasElmType Result where+  elmDefinition =+    Just $ deriveElmTypeDefinition @Result defaultOptions "Api.Result.Result"++instance HasElmDecoder Aeson.Value Result where+  elmDecoderDefinition =+    Just $ deriveElmJSONDecoder @Result defaultOptions Aeson.defaultOptions "Api.Result.decoder"++instance HasElmEncoder Aeson.Value Result where+  elmEncoderDefinition =+    Just $ deriveElmJSONEncoder @Result defaultOptions Aeson.defaultOptions "Api.Result.encoder"++instance (HasElmType a, HasElmType b) => HasElmType (Result a b) where+  elmType =+    Type.apps (elmType @Result) [elmType @a, elmType @b]++instance (HasElmDecoder Aeson.Value a, HasElmDecoder Aeson.Value b) => HasElmDecoder Aeson.Value (Result a b) where+  elmDecoder =+    Expression.apps (elmDecoder @Aeson.Value @Result) [elmDecoder @Aeson.Value @a, elmDecoder @Aeson.Value @b]++instance (HasElmEncoder Aeson.Value a, HasElmDecoder Aeson.Value b) => HasElmEncoder Aeson.Value (Result a b) where+  elmEncoder =+    Expression.apps (elmEncoder @Aeson.Value @Result) [elmEncoder @Aeson.Value @a, elmDecoder @Aeson.Value @b]++main :: IO ()+main = do+  let+    definitions =+      Simplification.simplifyDefinition <$>+        jsonDefinitions @Result++    modules =+      Pretty.modules definitions++  forM_ (HashMap.toList modules) $ \(_moduleName, contents) ->+    print contents
examples/User.hs view
@@ -1,16 +1,16 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-}+{-# language DeriveAnyClass #-}+{-# language DeriveGeneric #-}+{-# language MultiParamTypeClasses #-}+{-# language OverloadedStrings #-}+{-# language TypeApplications #-} module User where -import Protolude- import qualified Data.Aeson as Aeson+import Data.Foldable import qualified Data.HashMap.Lazy as HashMap+import Data.Text (Text) import qualified Generics.SOP as SOP+import GHC.Generics  import qualified Language.Elm.Pretty as Pretty import qualified Language.Elm.Simplification as Simplification
haskell-to-elm.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 11ea0d703e5c54aa3a79066bb30c43082a688d310a648d6a80e6577cbad53ecf+-- hash: 9e59ffab4bdc032dd26d95e1ed709e107c6cc2ecac032d51ab4d8bac15ede294  name:           haskell-to-elm-version:        0.2.1.0+version:        0.3.0.0 synopsis:       Generate Elm types and JSON encoders and decoders from Haskell types description:    Please see the README on GitHub at <https://github.com/folq/haskell-to-elm#readme> category:       Elm, Compiler, Language@@ -18,6 +18,7 @@ copyright:      2019 Olle Fredriksson license:        BSD3 license-file:   LICENSE+tested-with:    GHC == 8.4.3, GHC == 8.6.5, GHC == 8.8.3 build-type:     Simple extra-source-files:     README.md@@ -44,17 +45,39 @@       aeson >=1.4.0     , base >=4.7 && <5     , bound >=2.0.0-    , elm-syntax >=0.2.0 && <0.2.1-    , generics-sop >=0.4.0 && <0.5.0-    , protolude >=0.2.3+    , elm-syntax >=0.3.0 && <0.3.1+    , generics-sop >=0.4.0 && <0.6.0     , text >=1.2.0     , time >=1.8.0     , unordered-containers >=0.2.8   default-language: Haskell2010 +executable parameterised-example+  main-is: Parameterised.hs+  other-modules:+      User+      Paths_haskell_to_elm+  hs-source-dirs:+      examples+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -Wtabs -funbox-strict-fields+  build-depends:+      aeson >=1.4.0+    , base >=4.7 && <5+    , bound >=2.0.0+    , elm-syntax >=0.3.0 && <0.3.1+    , generics-sop >=0.4.0 && <0.6.0+    , haskell-to-elm+    , text >=1.2.0+    , time >=1.8.0+    , unordered-containers >=0.2.8+  if !flag(examples)+    buildable: False+  default-language: Haskell2010+ executable user-example   main-is: User.hs   other-modules:+      Parameterised       Paths_haskell_to_elm   hs-source-dirs:       examples@@ -63,10 +86,9 @@       aeson >=1.4.0     , base >=4.7 && <5     , bound >=2.0.0-    , elm-syntax >=0.2.0 && <0.2.1-    , generics-sop >=0.4.0 && <0.5.0+    , elm-syntax >=0.3.0 && <0.3.1+    , generics-sop >=0.4.0 && <0.6.0     , haskell-to-elm-    , protolude >=0.2.3     , text >=1.2.0     , time >=1.8.0     , unordered-containers >=0.2.8@@ -86,10 +108,9 @@       aeson >=1.4.0     , base >=4.7 && <5     , bound >=2.0.0-    , elm-syntax >=0.2.0 && <0.2.1-    , generics-sop >=0.4.0 && <0.5.0+    , elm-syntax >=0.3.0 && <0.3.1+    , generics-sop >=0.4.0 && <0.6.0     , haskell-to-elm-    , protolude >=0.2.3     , text >=1.2.0     , time >=1.8.0     , unordered-containers >=0.2.8
src/Language/Haskell/To/Elm.hs view
@@ -1,670 +1,817 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PartialTypeSignatures #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeOperators #-}-{-# OPTIONS_GHC -Wno-partial-type-signatures #-}-module Language.Haskell.To.Elm where--import Protolude hiding (All, Infix, Type)--import qualified Bound-import qualified Data.Aeson as Aeson-import Data.HashMap.Lazy (HashMap)-import qualified Data.HashMap.Lazy as HashMap-import Data.String-import Data.Text (Text)-import Data.Time-import Generics.SOP as SOP--import Language.Elm.Definition (Definition)-import qualified Language.Elm.Definition as Definition-import Language.Elm.Expression (Expression)-import qualified Language.Elm.Expression as Expression-import qualified Language.Elm.Name as Name-import qualified Language.Elm.Pattern as Pattern-import Language.Elm.Type (Type)-import qualified Language.Elm.Type as Type------------------------------------------------------------------------------------ * Classes---- | Represents that the corresponding Elm type for the Haskell type @a@ is @'elmType' \@a@.-class HasElmType a where-  elmType :: Type v-  default elmType :: Type v-  elmType =-    Type.Global $-      maybe-        (panic "default-implemented 'elmType' without a definition")-        Definition.name $-          elmDefinition @a--  -- | When 'Just', this represents that we can generate the definition for the-  -- Elm type that corresponds to @a@ using @'elmDefinition' \@a@.-  ---  -- See 'deriveElmTypeDefinition' for a way to automatically derive 'elmDefinition'.-  ---  -- When 'Nothing', it means that the type is an already existing Elm type-  -- that does not need to be generated.-  elmDefinition :: Maybe Definition-  elmDefinition =-    Nothing--  {-# minimal elmType | elmDefinition #-}---- | Represents that the Elm type that corresponds to @a@ has a decoder from--- @value@, namely @'elmDecoder' \@value \@a@.-class HasElmType a => HasElmDecoder value a where-  elmDecoder :: Expression v-  default elmDecoder :: Expression v-  elmDecoder =-    Expression.Global $-      maybe-        (panic "default-implemented 'elmDecoder' without a definition")-        Definition.name $-          elmDecoderDefinition @value @a--  -- | When 'Just', this represents that we can generate the Elm decoder definition-  -- from @value@ for the Elm type that corresponds to @a@.-  ---  -- See 'deriveElmJSONDecoder' for a way to automatically derive-  -- 'elmDecoderDefinition' when @value = 'Aeson.Value'@.-  elmDecoderDefinition :: Maybe Definition-  elmDecoderDefinition =-    Nothing--  {-# minimal elmDecoder | elmDecoderDefinition #-}---- | Represents that the Elm type that corresponds to @a@ has an encoder into--- @value@, namely @'elmEncoder' \@value \@a@.------ This class has a default instance for types that satisfy--- 'HasElmEncoderDefinition', which refers to the name of that definition.-class HasElmType a => HasElmEncoder value a where-  elmEncoder :: Expression v-  default elmEncoder :: Expression v-  elmEncoder =-    Expression.Global $-      maybe-        (panic "default-implemented 'elmEncoder' without a definition")-        Definition.name $-          elmEncoderDefinition @value @a--  -- | When 'Just', this represents that we can generate the Elm encoder-  -- definition into @value@ for the Elm type that corresponds to @a@.-  ---  -- See 'deriveElmJSONEncoder' for a way to automatically derive-  -- 'elmEncoderDefinition' when @value = 'Aeson.Value'@.-  elmEncoderDefinition :: Maybe Definition-  elmEncoderDefinition =-    Nothing--  {-# minimal elmEncoder | elmEncoderDefinition #-}------------------------------------------------------------------------------------ * Derivers---- | Elm code generation options-newtype Options = Options-  { fieldLabelModifier :: String -> String -- ^ Use this function to go from Haskell record field name to Elm record field name.-  }--defaultOptions :: Options-defaultOptions =-  Options-    { fieldLabelModifier = identity-    }---- | Automatically create an Elm definition given a Haskell type.------ This is suitable for use as the 'elmDefinition' in a 'HasElmType' instance:------ @--- instance 'HasElmType' MyType where---   'elmDefinition' =---     'Just' $ 'deriveElmTypeDefinition' \@MyType 'defaultOptions' \"Api.MyType.MyType\"--- @-deriveElmTypeDefinition :: forall a. (HasDatatypeInfo a, All2 HasElmType (Code a)) => Options -> Name.Qualified -> Definition-deriveElmTypeDefinition options name =-  case datatypeInfo (Proxy @a) of-    ADT _mname _tname (Record _cname fields :* Nil) ->-      Definition.Alias name (Type.Record (recordFields fields))--    ADT _mname _tname cs ->-      Definition.Type name (constructors cs)--    Newtype _mname _tname (Record _cname fields) ->-      Definition.Alias name (Type.Record (recordFields fields))--    Newtype _mname _tname c ->-      Definition.Type name (constructors (c :* Nil))-  where-    recordFields :: All HasElmType xs => NP FieldInfo xs -> [(Name.Field, Type v)]-    recordFields Nil = []-    recordFields (f :* fs) = field f : recordFields fs--    field :: forall x v. HasElmType x => FieldInfo x -> (Name.Field, Type v)-    field (FieldInfo fname) =-      (fromString $ fieldLabelModifier options fname, elmType @x)--    constructors :: All2 HasElmType xss => NP ConstructorInfo xss -> [(Name.Constructor, [Type v])]-    constructors Nil = []-    constructors (c :* cs) = constructor c : constructors cs--    constructor :: forall xs v. All HasElmType xs => ConstructorInfo xs -> (Name.Constructor, [Type v])-    constructor (Constructor cname) = (fromString cname, constructorFields $ shape @_ @xs)-    constructor (Infix _ _ _) = panic "Infix constructors are not supported"-    constructor (Record cname fs) = (fromString cname, [Type.Record $ recordFields fs])--    constructorFields :: All HasElmType xs => Shape xs -> [Type v]-    constructorFields ShapeNil = []-    constructorFields s@(ShapeCons _) = go s-      where-        go :: forall x xs v. (HasElmType x, All HasElmType xs) => Shape (x ': xs) -> [Type v]-        go (ShapeCons s') = elmType @x : constructorFields s'---- | Automatically create an Elm JSON decoder definition given a Haskell type.------ This is suitable for use as the 'elmDecoderDefinition' in a--- @'HasElmDecoder' 'Aeson.Value'@ instance:------ @--- instance 'HasElmDecoder' 'Aeson.Value' MyType where---   'elmDecoderDefinition' =---     Just $ 'deriveElmJSONDecoder' \@MyType 'defaultOptions' 'Aeson.defaultOptions' "Api.MyType.decoder"--- @------ Uses the given 'Aeson.Options' to match the JSON format of derived--- 'Aeson.FromJSON' and 'Aeson.ToJSON' instances.-deriveElmJSONDecoder-  :: forall a-  . (HasDatatypeInfo a, HasElmType a, All2 (HasElmDecoder Aeson.Value) (Code a))-  => Options-  -> Aeson.Options-  -> Name.Qualified-  -> Definition-deriveElmJSONDecoder options aesonOptions decoderName =-  Definition.Constant decoderName (Type.App "Json.Decode.Decoder" $ elmType @a) $-  case datatypeInfo (Proxy @a) of-    ADT _mname _tname (Record _cname fields :* Nil) ->-      decodeRecord fields $-      Expression.App "Json.Decode.succeed" $-      case Type.appsView (elmType @a) of-        (Type.Record fieldTypes, _) ->-          explicitRecordConstructor $ fst <$> fieldTypes--        _ ->-          Expression.Global typeName--    ADT _mname _tname cs ->-      decodeConstructors $ constructors cs--    Newtype _mname _tname (Record _cname fields) ->-      decodeRecord fields $-      Expression.App "Json.Decode.succeed" $-      case Type.appsView (elmType @a) of-        (Type.Record fieldTypes, _) ->-          explicitRecordConstructor $ fst <$> fieldTypes--        _ ->-          Expression.Global typeName--    Newtype _mname _tname c ->-      decodeConstructors $ constructors (c :* Nil)-  where-    typeName@(Name.Qualified moduleName_ _) =-      case Type.appsView (elmType @a) of-        (Type.Global tname, _) -> tname--        _ ->-          panic "Can't automatically derive JSON decoder for an anonymous Elm type"--    constructors-      :: All2 (HasElmDecoder Aeson.Value) xss-      => NP ConstructorInfo xss-      -> [(String, [Expression v])]-    constructors Nil = []-    constructors (c :* cs) = constructor c : constructors cs--    constructor-      :: forall xs v-      . All (HasElmDecoder Aeson.Value) xs-      => ConstructorInfo xs-      -> (String, [Expression v])-    constructor (Constructor cname) =-      (cname, constructorFields $ shape @_ @xs)-    constructor (Infix _ _ _) =-      panic "Infix constructors are not supported"-    constructor (Record cname fs) =-      (cname, [decodeRecord fs $ explicitRecordConstructor $ recordFieldNames fs])--    constructorFields-      :: All (HasElmDecoder Aeson.Value) xs-      => Shape xs-      -> [Expression v]-    constructorFields ShapeNil = []-    constructorFields s@(ShapeCons _) = go s-      where-        go-          :: forall x xs v-          . (HasElmDecoder Aeson.Value x, All (HasElmDecoder Aeson.Value) xs)-          => Shape (x ': xs)-          -> [Expression v]-        go (ShapeCons s') = elmDecoder @Aeson.Value @x : constructorFields s'--    explicitRecordConstructor-      :: [Name.Field]-      -> Expression v-    explicitRecordConstructor names =-      go mempty names-      where-        go :: HashMap Name.Field v -> [Name.Field] -> Expression v-        go locals fnames =-          case fnames of-            [] ->-              Expression.Record [(name, Expression.Var $ locals HashMap.! name) | name <- names]--            fname:fnames' ->-              Expression.Lam $ Bound.toScope $ go (HashMap.insert fname (Bound.B ()) $ Bound.F <$> locals) fnames'--    decodeRecord-      :: All (HasElmDecoder Aeson.Value) xs-      => NP FieldInfo xs-      -> Expression v-      -> Expression v-    decodeRecord (f :* Nil)-      | Aeson.unwrapUnaryRecords aesonOptions =-        unwrappedRecordField f-    decodeRecord fs =-      recordFields fs--    recordFields-      :: All (HasElmDecoder Aeson.Value) xs-      => NP FieldInfo xs-      -> Expression v-      -> Expression v-    recordFields Nil e = e-    recordFields (f :* fs) e =-      recordFields fs $ recordField f e--    isMaybe :: forall t. HasElmType t => Bool-    isMaybe =-      case Type.appsView $ elmType @t of-        (Type.Global "Maybe.Maybe", _) -> True-        _ -> False--    recordField-      :: forall x v-      . HasElmDecoder Aeson.Value x-      => FieldInfo x-      -> Expression v-      -> Expression v-    recordField (FieldInfo fname) e-      | Aeson.omitNothingFields aesonOptions && isMaybe @x =-        e Expression.|>-          Expression.apps-            "Json.Decode.Pipeline.optional"-            [ jsonFieldName fname-            , elmDecoder @Aeson.Value @x-            , "Maybe.Nothing"-            ]--      | otherwise =-        e Expression.|>-          Expression.apps-            "Json.Decode.Pipeline.required"-            [ jsonFieldName fname-            , elmDecoder @Aeson.Value @x-            ]--    unwrappedRecordField-      :: forall x v-      . HasElmDecoder Aeson.Value x-      => FieldInfo x-      -> Expression v-      -> Expression v-    unwrappedRecordField (FieldInfo _) e =-      e Expression.|> elmDecoder @Aeson.Value @x--    recordFieldNames-      :: All (HasElmDecoder Aeson.Value) xs-      => NP FieldInfo xs-      -> [Name.Field]-    recordFieldNames Nil = []-    recordFieldNames (FieldInfo fname :* fs) =-      elmField fname : recordFieldNames fs--    constructorJSONName :: String -> Text-    constructorJSONName = toS . Aeson.constructorTagModifier aesonOptions--    jsonFieldName :: String -> Expression v-    jsonFieldName = Expression.String . toS . Aeson.fieldLabelModifier aesonOptions--    elmField :: String -> Name.Field-    elmField = fromString . fieldLabelModifier options--    decodeConstructor :: String -> Expression v -> [Expression v] -> Expression v-    decodeConstructor _ constr [] =-      Expression.App "Json.Decode.succeed" constr--    decodeConstructor contentsName constr [constrField] =-      Expression.App "Json.Decode.succeed" constr Expression.|>-        Expression.apps "Json.Decode.Pipeline.required" [Expression.String (toS contentsName), constrField]--    decodeConstructor contentsName constr constrFields =-      Expression.apps-        "Json.Decode.field"-        [ Expression.String (toS contentsName)-        , foldl'-          (Expression.|>)-          (Expression.App "Json.Decode.succeed" constr)-          [ Expression.App-            "Json.Decode.Pipeline.custom"-            (Expression.apps "Json.Decode.index" [Expression.Int index, field])-          | (index, field) <- zip [0..] constrFields-          ]-        ]--    decodeConstructors :: [(String, [Expression v])] -> Expression v-    decodeConstructors [(constr, constrFields)]-      | not $ Aeson.tagSingleConstructors aesonOptions =-        let-          qualifiedConstr =-            Expression.Global $ Name.Qualified moduleName_ $ toS constr-        in-        case constrFields of-          [constrField] ->-            Expression.apps "Json.Decode.map" [qualifiedConstr, constrField]--          _ ->-            foldl'-              (Expression.|>)-              (Expression.App "Json.Decode.succeed" qualifiedConstr)-              [Expression.App-                "Json.Decode.Pipeline.custom"-                (Expression.apps "Json.Decode.index" [Expression.Int index, field])-              | (index, field) <- zip [0..] constrFields-              ]--    decodeConstructors constrs-      | Aeson.allNullaryToStringTag aesonOptions && allNullary constrs =-        "Json.Decode.string" Expression.|> Expression.App "Json.Decode.andThen" (Expression.Lam-          (Bound.toScope $ Expression.Case (pure $ Bound.B ()) $-            [ ( Pattern.String $ constructorJSONName constr-              , Bound.toScope $ Expression.App "Json.Decode.succeed" qualifiedConstr-              )-            | (constr, _) <- constrs-            , let-                qualifiedConstr =-                  Expression.Global $ Name.Qualified moduleName_ $ toS constr-            ]-            ++-            [ ( Pattern.Wildcard-              , Bound.toScope $ Expression.App "Json.Decode.fail" $ Expression.String "No matching constructor"-              )-            ]-          ))--    decodeConstructors constrs =-      case Aeson.sumEncoding aesonOptions of-        Aeson.TaggedObject tagName contentsName ->-          Expression.apps "Json.Decode.field" [Expression.String $ toS tagName, "Json.Decode.string"] Expression.|>-            Expression.App "Json.Decode.andThen" (Expression.Lam-              (Bound.toScope $ Expression.Case (pure $ Bound.B ()) $-                [ ( Pattern.String $ constructorJSONName constr-                  , Bound.toScope $ decodeConstructor contentsName qualifiedConstr (fmap (Bound.F . Bound.F) <$> fields)-                  )-              | (constr, fields) <- constrs-              , let-                  qualifiedConstr =-                    Expression.Global $ Name.Qualified moduleName_ $ toS constr-              ]-              ++-              [ ( Pattern.Wildcard-                , Bound.toScope $ Expression.App "Json.Decode.fail" $ Expression.String "No matching constructor"-                )-              ]-            ))--        _ -> panic "Only the DataAeson.TaggedObject sumEncoding is currently supported"--    allNullary :: forall c f. [(c, [f])] -> Bool-    allNullary = all (null . snd)---- | Automatically create an Elm JSON encoder definition given a Haskell type.------ This is suitable for use as the 'elmEncoderDefinition' in a @'HasElmEncoder 'Aeson.Value'@ instance:------ @--- instance 'HasElmEncoder' 'Aeson.Value' MyType where---   'elmEncoderDefinition' =---     'Just' $ 'deriveElmJSONEncoder' \@MyType 'defaultOptions' 'Aeson.defaultOptions' "Api.MyType.encoder"--- @------ Uses the given 'Aeson.Options' to match the JSON format of derived--- 'Aeson.FromJSON' and 'Aeson.ToJSON' instances.-deriveElmJSONEncoder-  :: forall a-  . (HasDatatypeInfo a, HasElmType a, All2 (HasElmEncoder Aeson.Value) (Code a))-  => Options-  -> Aeson.Options-  -> Name.Qualified-  -> Definition-deriveElmJSONEncoder options aesonOptions encoderName =-  Definition.Constant encoderName (Type.Fun (elmType @a) "Json.Encode.Value") $-  Expression.Lam $ Bound.toScope $-    case datatypeInfo (Proxy @a) of-      ADT _mname _tname (Record _cname fields :* Nil) ->-        encodeRecord fields $ pure $ Bound.B ()--      ADT _mname _tname cs ->-        encodeConstructors (constructors cs) (pure $ Bound.B ())--      Newtype _mname _tname (Record _cname fields) ->-        encodeRecord fields $ pure $ Bound.B ()--      Newtype _mname _tname c ->-        encodeConstructors (constructors (c :* Nil)) (pure $ Bound.B ())-  where-    (Name.Qualified moduleName_ _) =-      case Type.appsView (elmType @a) of-        (Type.Global tname, _) -> tname--        _ ->-          panic "Can't automatically derive JSON encoder for an anonymous Elm type"--    constructors-      :: All2 (HasElmEncoder Aeson.Value) xss-      => NP ConstructorInfo xss-      -> [(String, [Expression v])]-    constructors Nil = []-    constructors (c :* cs) = constructor c : constructors cs--    constructor-      :: forall xs v-      . All (HasElmEncoder Aeson.Value) xs-      => ConstructorInfo xs-      -> (String, [Expression v])-    constructor (Constructor cname) =-      (cname, constructorFields $ shape @_ @xs)-    constructor Infix {} =-      panic "Infix constructors are not supported"-    constructor (Record cname fs) =-      (cname, [Expression.Lam $ Bound.toScope $ encodeRecord fs (pure $ Bound.B ())])--    constructorFields-      :: All (HasElmEncoder Aeson.Value) xs-      => Shape xs-      -> [Expression v]-    constructorFields ShapeNil = []-    constructorFields s@(ShapeCons _) = go s-      where-        go-          :: forall x xs v-          . (HasElmEncoder Aeson.Value x, All (HasElmEncoder Aeson.Value) xs)-          => Shape (x ': xs)-          -> [Expression v]-        go (ShapeCons s') = elmEncoder @Aeson.Value @x : constructorFields s'--    encodeRecord-      :: All (HasElmEncoder Aeson.Value) xs-      => NP FieldInfo xs-      -> Expression v-      -> Expression v-    encodeRecord (f :* Nil) e-      | Aeson.unwrapUnaryRecords aesonOptions =-        unwrappedRecordField f e-    encodeRecord fs e =-      Expression.App "Json.Encode.object" $-        case recordFields fs e of-          (nonNullable, []) ->-            Expression.List nonNullable--          ([], nullable) ->-            Expression.App "List.concat" $ Expression.List nullable--          (nonNullable, nullable) ->-            Expression.apps "Basics.++"-              [ Expression.List nonNullable-              , Expression.App "List.concat" $ Expression.List nullable-              ]--    recordFields-      :: All (HasElmEncoder Aeson.Value) xs-      => NP FieldInfo xs-      -> Expression v-      -> ([Expression v], [Expression v])-    recordFields Nil _ = mempty-    recordFields (f :* fs) e =-      recordField f e <> recordFields fs e--    isMaybe :: forall t. HasElmType t => Bool-    isMaybe =-      case Type.appsView $ elmType @t of-        (Type.Global "Maybe.Maybe", _) -> True-        _ -> False--    recordField-      :: forall x v-      . HasElmEncoder Aeson.Value x-      => FieldInfo x-      -> Expression v-      -> ([Expression v], [Expression v])-    recordField (FieldInfo fname) e-      | Aeson.omitNothingFields aesonOptions && isMaybe @x =-        ( []-        , [ Expression.Case (Expression.App (Expression.Proj $ elmField fname) e)-            [ ( Pattern.Con "Maybe.Nothing" []-              , Bound.toScope $ Expression.List []-              )-            , ( Pattern.Con "Maybe.Just" [Pattern.Var 0]-              , Bound.toScope $-                Expression.App-                  (elmEncoder @Aeson.Value @x)-                  (Expression.App (Expression.Proj $ elmField fname) (Bound.F <$> e))-              )-            ]-          ]-        )--      | otherwise =-        ( [ Expression.tuple-            (jsonFieldName fname)-            (Expression.App (elmEncoder @Aeson.Value @x) (Expression.App (Expression.Proj $ elmField fname) e))-          ]-        , []-        )--    unwrappedRecordField-      :: forall x v-      . HasElmEncoder Aeson.Value x-      => FieldInfo x-      -> Expression v-      -> Expression v-    unwrappedRecordField (FieldInfo _) =-      Expression.App (elmEncoder @Aeson.Value @x)--    constructorJSONName :: String -> Text-    constructorJSONName = toS . Aeson.constructorTagModifier aesonOptions--    jsonFieldName :: String -> Expression v-    jsonFieldName = Expression.String . toS . Aeson.fieldLabelModifier aesonOptions--    elmField :: String -> Name.Field-    elmField = fromString . fieldLabelModifier options--    elmConstr :: String -> Name.Qualified-    elmConstr = Name.Qualified moduleName_ . fromString--    encodeConstructorFields :: [Expression v] -> Expression (Bound.Var Int v)-    encodeConstructorFields [constrField] =-      Expression.App (Bound.F <$> constrField) (pure $ Bound.B 0)--    encodeConstructorFields constrFields =-      Expression.apps-        "Json.Encode.list"-        [ "Basics.identity"-        , Expression.List-          [ Expression.App (Bound.F <$> field) (pure $ Bound.B index)-          | (index, field) <- zip [0..] constrFields-          ]-        ]--    encodeConstructors :: [(String, [Expression v])] -> Expression v -> Expression v-    encodeConstructors [(constr, constrFields)] expr-      | not $ Aeson.tagSingleConstructors aesonOptions =-        Expression.Case expr-          [ ( Pattern.Con (elmConstr constr) (Pattern.Var . fst <$> zip [0..] constrFields)-            , Bound.toScope $ encodeConstructorFields constrFields-            )-          ]--    encodeConstructors constrs expr-      | Aeson.allNullaryToStringTag aesonOptions && allNullary constrs =-        Expression.Case expr-          [ ( Pattern.Con (elmConstr constr) []-            , Bound.toScope $-              Expression.App "Json.Encode.string" $ Expression.String $ constructorJSONName constr-            )-          | (constr, _) <- constrs-          ]--    encodeConstructors constrs expr =-      case Aeson.sumEncoding aesonOptions of-        Aeson.TaggedObject tagName contentsName ->-          Expression.Case expr-            [ ( Pattern.Con (elmConstr constr) (Pattern.Var . fst <$> zip [0..] constrFields)-              , Bound.toScope $-                Expression.App "Json.Encode.object" $-                Expression.List $-                  Expression.tuple-                    (Expression.String (toS tagName))-                    (Expression.App "Json.Encode.string" $ Expression.String $ constructorJSONName constr)-                  :-                  [ Expression.tuple-                    (Expression.String (toS contentsName))-                    (encodeConstructorFields constrFields)-                  | not $ null constrFields-                  ]-              )-            | (constr, constrFields) <- constrs-            ]--        _ -> panic "Only the DataAeson.TaggedObject sumEncoding is currently supported"--    allNullary :: forall c f. [(c, [f])] -> Bool-    allNullary = all (null . snd)+{-# language AllowAmbiguousTypes #-}+{-# language DataKinds #-}+{-# language DefaultSignatures #-}+{-# language DuplicateRecordFields #-}+{-# language FlexibleContexts #-}+{-# language FlexibleInstances #-}+{-# language GADTs #-}+{-# language KindSignatures #-}+{-# language MultiParamTypeClasses #-}+{-# language OverloadedStrings #-}+{-# language PartialTypeSignatures #-}+{-# language PolyKinds #-}+{-# language ScopedTypeVariables #-}+{-# language TypeApplications #-}+{-# language TypeOperators #-}+{-# language UndecidableInstances #-}+module Language.Haskell.To.Elm where++import qualified Bound+import qualified Data.Aeson as Aeson+import Data.Foldable+import Data.HashMap.Lazy (HashMap)+import qualified Data.HashMap.Lazy as HashMap+import qualified Data.Kind+import Data.Maybe (catMaybes)+import Data.String+import Data.Text (Text)+import Data.Time+import Generics.SOP as SOP+import GHC.TypeLits++import Language.Elm.Definition (Definition)+import qualified Language.Elm.Definition as Definition+import Language.Elm.Expression (Expression)+import qualified Language.Elm.Expression as Expression+import qualified Language.Elm.Name as Name+import qualified Language.Elm.Pattern as Pattern+import Language.Elm.Type (Type)+import qualified Language.Elm.Type as Type++-------------------------------------------------------------------------------+-- * Classes++-- | Represents that the corresponding Elm type for the Haskell type @a@ is @'elmType' \@a@.+class HasElmType a where+  elmType :: Type v+  default elmType :: Type v+  elmType =+    Type.Global $+      maybe+        (error "default-implemented 'elmType' without a definition")+        Definition.name $+          elmDefinition @a++  -- | When 'Just', this represents that we can generate the definition for the+  -- Elm type that corresponds to @a@ using @'elmDefinition' \@a@.+  --+  -- See 'deriveElmTypeDefinition' for a way to automatically derive 'elmDefinition'.+  --+  -- When 'Nothing', it means that the type is an already existing Elm type+  -- that does not need to be generated.+  elmDefinition :: Maybe Definition+  elmDefinition =+    Nothing++  {-# minimal elmType | elmDefinition #-}++-- | Represents that the Elm type that corresponds to @a@ has a decoder from+-- @value@, namely @'elmDecoder' \@value \@a@.+class HasElmType a => HasElmDecoder value a where+  elmDecoder :: Expression v+  default elmDecoder :: Expression v+  elmDecoder =+    Expression.Global $+      maybe+        (error "default-implemented 'elmDecoder' without a definition")+        Definition.name $+          elmDecoderDefinition @value @a++  -- | When 'Just', this represents that we can generate the Elm decoder definition+  -- from @value@ for the Elm type that corresponds to @a@.+  --+  -- See 'deriveElmJSONDecoder' for a way to automatically derive+  -- 'elmDecoderDefinition' when @value = 'Aeson.Value'@.+  elmDecoderDefinition :: Maybe Definition+  elmDecoderDefinition =+    Nothing++  {-# minimal elmDecoder | elmDecoderDefinition #-}++-- | Represents that the Elm type that corresponds to @a@ has an encoder into+-- @value@, namely @'elmEncoder' \@value \@a@.+--+-- This class has a default instance for types that satisfy+-- 'HasElmEncoderDefinition', which refers to the name of that definition.+class HasElmType a => HasElmEncoder value a where+  elmEncoder :: Expression v+  default elmEncoder :: Expression v+  elmEncoder =+    Expression.Global $+      maybe+        (error "default-implemented 'elmEncoder' without a definition")+        Definition.name $+          elmEncoderDefinition @value @a++  -- | When 'Just', this represents that we can generate the Elm encoder+  -- definition into @value@ for the Elm type that corresponds to @a@.+  --+  -- See 'deriveElmJSONEncoder' for a way to automatically derive+  -- 'elmEncoderDefinition' when @value = 'Aeson.Value'@.+  elmEncoderDefinition :: Maybe Definition+  elmEncoderDefinition =+    Nothing++  {-# minimal elmEncoder | elmEncoderDefinition #-}++-------------------------------------------------------------------------------+-- * Derivers++-- | Elm code generation options+newtype Options = Options+  { fieldLabelModifier :: String -> String -- ^ Use this function to go from Haskell record field name to Elm record field name.+  }++defaultOptions :: Options+defaultOptions =+  Options+    { fieldLabelModifier = id+    }++-- ** Type definitions++-- | Automatically create an Elm definition given a Haskell type.+--+-- This is suitable for use as the 'elmDefinition' in a 'HasElmType' instance:+--+-- @+-- instance 'HasElmType' MyType where+--   'elmDefinition' =+--     'Just' $ 'deriveElmTypeDefinition' \@MyType 'defaultOptions' \"Api.MyType.MyType\"+-- @+deriveElmTypeDefinition+  :: forall a+  . DeriveParameterisedElmTypeDefinition 0 a+  => Options+  -> Name.Qualified+  -> Definition+deriveElmTypeDefinition =+  deriveParameterisedElmTypeDefinition @0 @a++class DeriveParameterisedElmTypeDefinition numParams a where+  deriveParameterisedElmTypeDefinition :: Options -> Name.Qualified -> Definition++data Parameter (n :: Nat)++parameterName :: Int -> Name.Qualified+parameterName i =+  Name.Qualified ["Haskell", "To", "Elm"] ("Parameter" <> fromString (show i))++instance KnownNat n => HasElmType (Parameter n) where+  elmType =+    Type.Global $ parameterName $ fromIntegral $ natVal $ Proxy @n++instance (DeriveParameterisedElmTypeDefinition (numParams + 1) (f (Parameter numParams))) => DeriveParameterisedElmTypeDefinition numParams (f :: Data.Kind.Type -> b) where+  deriveParameterisedElmTypeDefinition =+    deriveParameterisedElmTypeDefinition @(numParams + 1) @(f (Parameter numParams))++instance (KnownNat numParams, HasDatatypeInfo a, All2 HasElmType (Code a)) => DeriveParameterisedElmTypeDefinition numParams (a :: Data.Kind.Type) where+  deriveParameterisedElmTypeDefinition options name =+    case constructorInfo $ datatypeInfo (Proxy @a) of+      Record _cname fields :* Nil ->+        Definition.Alias name numParams (bindTypeParameters $ Type.Record (recordFields fields))++      cs ->+        Definition.Type name numParams (fmap (fmap bindTypeParameters) <$> constructors cs)+    where+      typeParameterMap :: HashMap Name.Qualified Int+      typeParameterMap =+        HashMap.fromList [(parameterName i, i) | i <- [0..numParams - 1]]++      bindTypeParameters+        :: Type v+        -> Bound.Scope Int Type v+      bindTypeParameters =+        Bound.Scope .+        Type.bind+          (\n -> maybe (Type.Global n) (pure . Bound.B) (HashMap.lookup n typeParameterMap))+          (pure . pure . pure)++      numParams =+        fromIntegral $ natVal $ Proxy @numParams++      recordFields :: All HasElmType xs => NP FieldInfo xs -> [(Name.Field, Type v)]+      recordFields Nil = []+      recordFields (f :* fs) = field f : recordFields fs++      field :: forall x v. HasElmType x => FieldInfo x -> (Name.Field, Type v)+      field (FieldInfo fname) =+        (fromString $ fieldLabelModifier options fname, elmType @x)++      constructors :: All2 HasElmType xss => NP ConstructorInfo xss -> [(Name.Constructor, [Type v])]+      constructors Nil = []+      constructors (c :* cs) = constructor c : constructors cs++      constructor :: forall xs v. All HasElmType xs => ConstructorInfo xs -> (Name.Constructor, [Type v])+      constructor (Constructor cname) = (fromString cname, constructorFields $ shape @_ @xs)+      constructor (Infix _ _ _) = error "Infix constructors are not supported"+      constructor (Record cname fs) = (fromString cname, [Type.Record $ recordFields fs])++      constructorFields :: All HasElmType xs => Shape xs -> [Type v]+      constructorFields ShapeNil = []+      constructorFields s@(ShapeCons _) = go s+        where+          go :: forall x xs v. (HasElmType x, All HasElmType xs) => Shape (x ': xs) -> [Type v]+          go (ShapeCons s') = elmType @x : constructorFields s'++-- ** JSON decoders++-- | Automatically create an Elm JSON decoder definition given a Haskell type.+--+-- This is suitable for use as the 'elmDecoderDefinition' in a+-- @'HasElmDecoder' 'Aeson.Value'@ instance:+--+-- @+-- instance 'HasElmDecoder' 'Aeson.Value' MyType where+--   'elmDecoderDefinition' =+--     Just $ 'deriveElmJSONDecoder' \@MyType 'defaultOptions' 'Aeson.defaultOptions' "Api.MyType.decoder"+-- @+--+-- Uses the given 'Aeson.Options' to match the JSON format of derived+-- 'Aeson.FromJSON' and 'Aeson.ToJSON' instances.+deriveElmJSONDecoder+  :: forall a+  . DeriveParameterisedElmDecoderDefinition 0 Aeson.Value a+  => Options+  -> Aeson.Options+  -> Name.Qualified+  -> Definition+deriveElmJSONDecoder =+  deriveParameterisedElmDecoderDefinition @0 @Aeson.Value @a++class DeriveParameterisedElmDecoderDefinition numParams value a where+  deriveParameterisedElmDecoderDefinition :: Options -> Aeson.Options -> Name.Qualified -> Definition++instance KnownNat n => HasElmDecoder value (Parameter n) where+  elmDecoder =+    Expression.Global $+      parameterName $ fromIntegral $ natVal $ Proxy @n++instance (DeriveParameterisedElmDecoderDefinition (numParams + 1) value (f (Parameter numParams))) => DeriveParameterisedElmDecoderDefinition numParams value (f :: Data.Kind.Type -> b) where+  deriveParameterisedElmDecoderDefinition =+    deriveParameterisedElmDecoderDefinition @(numParams + 1) @value @(f (Parameter numParams))++instance (HasElmType a, KnownNat numParams, HasDatatypeInfo a, All2 (HasElmDecoder Aeson.Value) (Code a))+  => DeriveParameterisedElmDecoderDefinition numParams Aeson.Value (a :: Data.Kind.Type) where+  deriveParameterisedElmDecoderDefinition options aesonOptions decoderName =+    Definition.Constant decoderName numParams parameterisedType $+      parameteriseBody $+        case constructorInfo $ datatypeInfo (Proxy @a) of+          Record _cname fields :* Nil ->+            decodeRecord fields $+            Expression.App "Json.Decode.succeed" $+            case Type.appsView (elmType @a) of+              (Type.Record fieldTypes, _) ->+                explicitRecordConstructor $ fst <$> fieldTypes++              _ ->+                Expression.Global typeName++          cs ->+            decodeConstructors $ constructors cs+    where+      numParams =+        fromIntegral $ natVal $ Proxy @numParams++      parameterisedType :: Bound.Scope Int Type v+      parameterisedType =+        foldr+          (\i (Bound.Scope rest) ->+            Bound.Scope $ Type.Fun (Type.App "Json.Decode.Decoder" $ pure $ Bound.B i) rest+          )+          (Bound.Scope $ Type.App "Json.Decode.Decoder" $ bindTypeParameters $ elmType @a)+          [0..numParams - 1]++      typeParameterMap :: HashMap Name.Qualified Int+      typeParameterMap =+        HashMap.fromList [(parameterName i, i) | i <- [0..numParams - 1]]++      bindTypeParameters+        :: Type v+        -> Type (Bound.Var Int v)+      bindTypeParameters =+        Type.bind+          (\n -> maybe (Type.Global n) (pure . Bound.B) (HashMap.lookup n typeParameterMap))+          (pure . pure)++      parameteriseBody :: Expression v -> Expression v+      parameteriseBody body =+        foldr+          (\i ->+            Expression.Lam .+              Bound.toScope .+              Expression.bind+                (\global ->+                  if global == parameterName i then+                    pure $ Bound.B ()++                  else+                    Expression.Global global+                )+                (pure . pure)+          )+          body+          [0..numParams - 1]++      typeName@(Name.Qualified moduleName_ _) =+        case Type.appsView (elmType @a) of+          (Type.Global tname, _) -> tname++          _ ->+            error "Can't automatically derive JSON decoder for an anonymous Elm type"++      constructors+        :: All2 (HasElmDecoder Aeson.Value) xss+        => NP ConstructorInfo xss+        -> [(String, [Expression v])]+      constructors Nil = []+      constructors (c :* cs) = constructor c : constructors cs++      constructor+        :: forall xs v+        . All (HasElmDecoder Aeson.Value) xs+        => ConstructorInfo xs+        -> (String, [Expression v])+      constructor (Constructor cname) =+        (cname, constructorFields $ shape @_ @xs)+      constructor (Infix _ _ _) =+        error "Infix constructors are not supported"+      constructor (Record cname fs) =+        (cname, [decodeRecord fs $ explicitRecordConstructor $ recordFieldNames fs])++      constructorFields+        :: All (HasElmDecoder Aeson.Value) xs+        => Shape xs+        -> [Expression v]+      constructorFields ShapeNil = []+      constructorFields s@(ShapeCons _) = go s+        where+          go+            :: forall x xs v+            . (HasElmDecoder Aeson.Value x, All (HasElmDecoder Aeson.Value) xs)+            => Shape (x ': xs)+            -> [Expression v]+          go (ShapeCons s') = elmDecoder @Aeson.Value @x : constructorFields s'++      explicitRecordConstructor+        :: [Name.Field]+        -> Expression v+      explicitRecordConstructor names =+        go mempty names+        where+          go :: HashMap Name.Field v -> [Name.Field] -> Expression v+          go locals fnames =+            case fnames of+              [] ->+                Expression.Record [(name, Expression.Var $ locals HashMap.! name) | name <- names]++              fname:fnames' ->+                Expression.Lam $ Bound.toScope $ go (HashMap.insert fname (Bound.B ()) $ Bound.F <$> locals) fnames'++      decodeRecord+        :: All (HasElmDecoder Aeson.Value) xs+        => NP FieldInfo xs+        -> Expression v+        -> Expression v+      decodeRecord (f :* Nil)+        | Aeson.unwrapUnaryRecords aesonOptions =+          unwrappedRecordField f+      decodeRecord fs =+        recordFields fs++      recordFields+        :: All (HasElmDecoder Aeson.Value) xs+        => NP FieldInfo xs+        -> Expression v+        -> Expression v+      recordFields Nil e = e+      recordFields (f :* fs) e =+        recordFields fs $ recordField f e++      isMaybe :: forall t. HasElmType t => Bool+      isMaybe =+        case Type.appsView $ elmType @t of+          (Type.Global "Maybe.Maybe", _) -> True+          _ -> False++      recordField+        :: forall x v+        . HasElmDecoder Aeson.Value x+        => FieldInfo x+        -> Expression v+        -> Expression v+      recordField (FieldInfo fname) e+        | Aeson.omitNothingFields aesonOptions && isMaybe @x =+          e Expression.|>+            Expression.apps+              "Json.Decode.Pipeline.optional"+              [ jsonFieldName fname+              , elmDecoder @Aeson.Value @x+              , "Maybe.Nothing"+              ]++        | otherwise =+          e Expression.|>+            Expression.apps+              "Json.Decode.Pipeline.required"+              [ jsonFieldName fname+              , elmDecoder @Aeson.Value @x+              ]++      unwrappedRecordField+        :: forall x v+        . HasElmDecoder Aeson.Value x+        => FieldInfo x+        -> Expression v+        -> Expression v+      unwrappedRecordField (FieldInfo _) e =+        e Expression.|> elmDecoder @Aeson.Value @x++      recordFieldNames+        :: All (HasElmDecoder Aeson.Value) xs+        => NP FieldInfo xs+        -> [Name.Field]+      recordFieldNames Nil = []+      recordFieldNames (FieldInfo fname :* fs) =+        elmField fname : recordFieldNames fs++      constructorJSONName :: String -> Text+      constructorJSONName = fromString . Aeson.constructorTagModifier aesonOptions++      jsonFieldName :: String -> Expression v+      jsonFieldName = Expression.String . fromString . Aeson.fieldLabelModifier aesonOptions++      elmField :: String -> Name.Field+      elmField = fromString . fieldLabelModifier options++      decodeConstructor :: String -> Expression v -> [Expression v] -> Expression v+      decodeConstructor _ constr [] =+        Expression.App "Json.Decode.succeed" constr++      decodeConstructor contentsName constr [constrField] =+        Expression.App "Json.Decode.succeed" constr Expression.|>+          Expression.apps "Json.Decode.Pipeline.required" [Expression.String (fromString contentsName), constrField]++      decodeConstructor contentsName constr constrFields =+        Expression.apps+          "Json.Decode.field"+          [ Expression.String (fromString contentsName)+          , foldl'+            (Expression.|>)+            (Expression.App "Json.Decode.succeed" constr)+            [ Expression.App+              "Json.Decode.Pipeline.custom"+              (Expression.apps "Json.Decode.index" [Expression.Int index, field])+            | (index, field) <- zip [0..] constrFields+            ]+          ]++      decodeConstructors :: [(String, [Expression v])] -> Expression v+      decodeConstructors [(constr, constrFields)]+        | not $ Aeson.tagSingleConstructors aesonOptions =+          let+            qualifiedConstr =+              Expression.Global $ Name.Qualified moduleName_ $ fromString constr+          in+          case constrFields of+            [constrField] ->+              Expression.apps "Json.Decode.map" [qualifiedConstr, constrField]++            _ ->+              foldl'+                (Expression.|>)+                (Expression.App "Json.Decode.succeed" qualifiedConstr)+                [Expression.App+                  "Json.Decode.Pipeline.custom"+                  (Expression.apps "Json.Decode.index" [Expression.Int index, field])+                | (index, field) <- zip [0..] constrFields+                ]++      decodeConstructors constrs+        | Aeson.allNullaryToStringTag aesonOptions && allNullary constrs =+          "Json.Decode.string" Expression.|> Expression.App "Json.Decode.andThen" (Expression.Lam+            (Bound.toScope $ Expression.Case (pure $ Bound.B ()) $+              [ ( Pattern.String $ constructorJSONName constr+                , Bound.toScope $ Expression.App "Json.Decode.succeed" qualifiedConstr+                )+              | (constr, _) <- constrs+              , let+                  qualifiedConstr =+                    Expression.Global $ Name.Qualified moduleName_ $ fromString constr+              ]+              +++              [ ( Pattern.Wildcard+                , Bound.toScope $ Expression.App "Json.Decode.fail" $ Expression.String "No matching constructor"+                )+              ]+            ))++      decodeConstructors constrs =+        case Aeson.sumEncoding aesonOptions of+          Aeson.TaggedObject tagName contentsName ->+            Expression.apps "Json.Decode.field" [Expression.String $ fromString tagName, "Json.Decode.string"] Expression.|>+              Expression.App "Json.Decode.andThen" (Expression.Lam+                (Bound.toScope $ Expression.Case (pure $ Bound.B ()) $+                  [ ( Pattern.String $ constructorJSONName constr+                    , Bound.toScope $ decodeConstructor contentsName qualifiedConstr (fmap (Bound.F . Bound.F) <$> fields)+                    )+                | (constr, fields) <- constrs+                , let+                    qualifiedConstr =+                      Expression.Global $ Name.Qualified moduleName_ $ fromString constr+                ]+                +++                [ ( Pattern.Wildcard+                  , Bound.toScope $ Expression.App "Json.Decode.fail" $ Expression.String "No matching constructor"+                  )+                ]+              ))++          _ -> error "Only the DataAeson.TaggedObject sumEncoding is currently supported"++      allNullary :: forall c f. [(c, [f])] -> Bool+      allNullary = all (null . snd)++-- ** JSON encoders++-- | Automatically create an Elm JSON encoder definition given a Haskell type.+--+-- This is suitable for use as the 'elmEncoderDefinition' in a @'HasElmEncoder 'Aeson.Value'@ instance:+--+-- @+-- instance 'HasElmEncoder' 'Aeson.Value' MyType where+--   'elmEncoderDefinition' =+--     'Just' $ 'deriveElmJSONEncoder' \@MyType 'defaultOptions' 'Aeson.defaultOptions' "Api.MyType.encoder"+-- @+--+-- Uses the given 'Aeson.Options' to match the JSON format of derived+-- 'Aeson.FromJSON' and 'Aeson.ToJSON' instances.+deriveElmJSONEncoder+  :: forall a+  . DeriveParameterisedElmEncoderDefinition 0 Aeson.Value a+  => Options+  -> Aeson.Options+  -> Name.Qualified+  -> Definition+deriveElmJSONEncoder =+  deriveParameterisedElmEncoderDefinition @0 @Aeson.Value @a++class DeriveParameterisedElmEncoderDefinition numParams value a where+  deriveParameterisedElmEncoderDefinition :: Options -> Aeson.Options -> Name.Qualified -> Definition++instance KnownNat n => HasElmEncoder value (Parameter n) where+  elmEncoder =+    Expression.Global $+      parameterName $ fromIntegral $ natVal $ Proxy @n++instance (DeriveParameterisedElmEncoderDefinition (numParams + 1) value (f (Parameter numParams))) => DeriveParameterisedElmEncoderDefinition numParams value (f :: Data.Kind.Type -> b) where+  deriveParameterisedElmEncoderDefinition =+    deriveParameterisedElmEncoderDefinition @(numParams + 1) @value @(f (Parameter numParams))++instance (HasElmType a, KnownNat numParams, HasDatatypeInfo a, All2 (HasElmEncoder Aeson.Value) (Code a))+  => DeriveParameterisedElmEncoderDefinition numParams Aeson.Value (a :: Data.Kind.Type) where+  deriveParameterisedElmEncoderDefinition options aesonOptions encoderName =+    Definition.Constant encoderName numParams parameterisedType $+      parameteriseBody $+        Expression.Lam $ Bound.toScope $+          case constructorInfo $ datatypeInfo (Proxy @a) of+            Record _cname fields :* Nil ->+              encodeRecord fields $ pure $ Bound.B ()++            cs ->+              encodeConstructors (constructors cs) (pure $ Bound.B ())+    where+      numParams =+        fromIntegral $ natVal $ Proxy @numParams++      parameterisedType :: Bound.Scope Int Type v+      parameterisedType =+        foldr+          (\i (Bound.Scope rest) ->+            Bound.Scope $ Type.Fun (Type.Fun (pure $ Bound.B i) "Json.Encode.Value") rest+          )+          (Bound.Scope $ Type.Fun (bindTypeParameters $ elmType @a) "Json.Encode.Value")+          [0..numParams - 1]++      typeParameterMap :: HashMap Name.Qualified Int+      typeParameterMap =+        HashMap.fromList [(parameterName i, i) | i <- [0..numParams - 1]]++      bindTypeParameters+        :: Type v+        -> Type (Bound.Var Int v)+      bindTypeParameters =+        Type.bind+          (\n -> maybe (Type.Global n) (pure . Bound.B) (HashMap.lookup n typeParameterMap))+          (pure . pure)++      parameteriseBody :: Expression v -> Expression v+      parameteriseBody body =+        foldr+          (\i ->+            Expression.Lam .+              Bound.toScope .+              Expression.bind+                (\global ->+                  if global == parameterName i then+                    pure $ Bound.B ()++                  else+                    Expression.Global global+                )+                (pure . pure)+          )+          body+          [0..numParams - 1]++      (Name.Qualified moduleName_ _) =+        case Type.appsView (elmType @a) of+          (Type.Global tname, _) -> tname++          _ ->+            error "Can't automatically derive JSON encoder for an anonymous Elm type"++      constructors+        :: All2 (HasElmEncoder Aeson.Value) xss+        => NP ConstructorInfo xss+        -> [(String, [Expression v])]+      constructors Nil = []+      constructors (c :* cs) = constructor c : constructors cs++      constructor+        :: forall xs v+        . All (HasElmEncoder Aeson.Value) xs+        => ConstructorInfo xs+        -> (String, [Expression v])+      constructor (Constructor cname) =+        (cname, constructorFields $ shape @_ @xs)+      constructor Infix {} =+        error "Infix constructors are not supported"+      constructor (Record cname fs) =+        (cname, [Expression.Lam $ Bound.toScope $ encodeRecord fs (pure $ Bound.B ())])++      constructorFields+        :: All (HasElmEncoder Aeson.Value) xs+        => Shape xs+        -> [Expression v]+      constructorFields ShapeNil = []+      constructorFields s@(ShapeCons _) = go s+        where+          go+            :: forall x xs v+            . (HasElmEncoder Aeson.Value x, All (HasElmEncoder Aeson.Value) xs)+            => Shape (x ': xs)+            -> [Expression v]+          go (ShapeCons s') = elmEncoder @Aeson.Value @x : constructorFields s'++      encodeRecord+        :: All (HasElmEncoder Aeson.Value) xs+        => NP FieldInfo xs+        -> Expression v+        -> Expression v+      encodeRecord (f :* Nil) e+        | Aeson.unwrapUnaryRecords aesonOptions =+          unwrappedRecordField f e+      encodeRecord fs e =+        Expression.App "Json.Encode.object" $+          case recordFields fs e of+            (nonNullable, []) ->+              Expression.List nonNullable++            ([], nullable) ->+              Expression.App "List.concat" $ Expression.List nullable++            (nonNullable, nullable) ->+              Expression.apps "Basics.++"+                [ Expression.List nonNullable+                , Expression.App "List.concat" $ Expression.List nullable+                ]++      recordFields+        :: All (HasElmEncoder Aeson.Value) xs+        => NP FieldInfo xs+        -> Expression v+        -> ([Expression v], [Expression v])+      recordFields Nil _ = mempty+      recordFields (f :* fs) e =+        recordField f e <> recordFields fs e++      isMaybe :: forall t. HasElmType t => Bool+      isMaybe =+        case Type.appsView $ elmType @t of+          (Type.Global "Maybe.Maybe", _) -> True+          _ -> False++      recordField+        :: forall x v+        . HasElmEncoder Aeson.Value x+        => FieldInfo x+        -> Expression v+        -> ([Expression v], [Expression v])+      recordField (FieldInfo fname) e+        | Aeson.omitNothingFields aesonOptions && isMaybe @x =+          ( []+          , [ Expression.Case (Expression.App (Expression.Proj $ elmField fname) e)+              [ ( Pattern.Con "Maybe.Nothing" []+                , Bound.toScope $ Expression.List []+                )+              , ( Pattern.Con "Maybe.Just" [Pattern.Var 0]+                , Bound.toScope $+                  Expression.App+                    (elmEncoder @Aeson.Value @x)+                    (Expression.App (Expression.Proj $ elmField fname) (Bound.F <$> e))+                )+              ]+            ]+          )++        | otherwise =+          ( [ Expression.tuple+              (jsonFieldName fname)+              (Expression.App (elmEncoder @Aeson.Value @x) (Expression.App (Expression.Proj $ elmField fname) e))+            ]+          , []+          )++      unwrappedRecordField+        :: forall x v+        . HasElmEncoder Aeson.Value x+        => FieldInfo x+        -> Expression v+        -> Expression v+      unwrappedRecordField (FieldInfo _) =+        Expression.App (elmEncoder @Aeson.Value @x)++      constructorJSONName :: String -> Text+      constructorJSONName = fromString . Aeson.constructorTagModifier aesonOptions++      jsonFieldName :: String -> Expression v+      jsonFieldName = Expression.String . fromString . Aeson.fieldLabelModifier aesonOptions++      elmField :: String -> Name.Field+      elmField = fromString . fieldLabelModifier options++      elmConstr :: String -> Name.Qualified+      elmConstr = Name.Qualified moduleName_ . fromString++      encodeConstructorFields :: [Expression v] -> Expression (Bound.Var Int v)+      encodeConstructorFields [constrField] =+        Expression.App (Bound.F <$> constrField) (pure $ Bound.B 0)++      encodeConstructorFields constrFields =+        Expression.apps+          "Json.Encode.list"+          [ "Basics.identity"+          , Expression.List+            [ Expression.App (Bound.F <$> field) (pure $ Bound.B index)+            | (index, field) <- zip [0..] constrFields+            ]+          ]++      encodeConstructors :: [(String, [Expression v])] -> Expression v -> Expression v+      encodeConstructors [(constr, constrFields)] expr+        | not $ Aeson.tagSingleConstructors aesonOptions =+          Expression.Case expr+            [ ( Pattern.Con (elmConstr constr) (Pattern.Var . fst <$> zip [0..] constrFields)+              , Bound.toScope $ encodeConstructorFields constrFields+              )+            ]++      encodeConstructors constrs expr+        | Aeson.allNullaryToStringTag aesonOptions && allNullary constrs =+          Expression.Case expr+            [ ( Pattern.Con (elmConstr constr) []+              , Bound.toScope $+                Expression.App "Json.Encode.string" $ Expression.String $ constructorJSONName constr+              )+            | (constr, _) <- constrs+            ]++      encodeConstructors constrs expr =+        case Aeson.sumEncoding aesonOptions of+          Aeson.TaggedObject tagName contentsName ->+            Expression.Case expr+              [ ( Pattern.Con (elmConstr constr) (Pattern.Var . fst <$> zip [0..] constrFields)+                , Bound.toScope $+                  Expression.App "Json.Encode.object" $+                  Expression.List $+                    Expression.tuple+                      (Expression.String (fromString tagName))+                      (Expression.App "Json.Encode.string" $ Expression.String $ constructorJSONName constr)+                    :+                    [ Expression.tuple+                      (Expression.String (fromString contentsName))+                      (encodeConstructorFields constrFields)+                    | not $ null constrFields+                    ]+                )+              | (constr, constrFields) <- constrs+              ]++          _ -> error "Only the DataAeson.TaggedObject sumEncoding is currently supported"++      allNullary :: forall c f. [(c, [f])] -> Bool+      allNullary = all (null . snd)  -------------