Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record fields list #117

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/src/Core/Decoder.elm
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,7 @@ decodeOneType = D.succeed T.OneType
|> required "guests" (D.list decodeGuest)
|> required "userRequest" decodeUserRequest
|> required "nonEmpty" (elmStreetDecodeNonEmpty decodeMyUnit)
|> required "recordUnit" decodeRecordUnit

decodeRecordUnit : Decoder T.RecordUnit
decodeRecordUnit = D.succeed {}
Comment on lines +104 to +105
Copy link
Member Author

@turboMaCk turboMaCk Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toJSON $ RecordUnit
Array []

in theory we can fail here unless json contents empty array. Not if there will be any advantage though.

4 changes: 4 additions & 0 deletions frontend/src/Core/Encoder.elm
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,8 @@ encodeOneType x = E.object
, ("guests", (E.list encodeGuest) x.guests)
, ("userRequest", encodeUserRequest x.userRequest)
, ("nonEmpty", (elmStreetEncodeNonEmpty encodeMyUnit) x.nonEmpty)
, ("recordUnit", encodeRecordUnit x.recordUnit)
]

encodeRecordUnit : T.RecordUnit -> Value
encodeRecordUnit x = list (\_ -> null) []
Comment on lines +98 to +100
Copy link
Member Author

@turboMaCk turboMaCk Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeStrict @RecordUnit "[]" 
Just RecordUnit

4 changes: 4 additions & 0 deletions frontend/src/Core/Types.elm
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,8 @@ type alias OneType =
, guests : List Guest
, userRequest : UserRequest
, nonEmpty : (MyUnit, List MyUnit)
, recordUnit : RecordUnit
}

type alias RecordUnit =
{ }
1 change: 1 addition & 0 deletions frontend/tests/Tests.elm
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ defaultOneType =
, example = Just (R.Ok Blocked)
}
, nonEmpty = (MyUnit (), [MyUnit ()])
, recordUnit = {}
}
3 changes: 2 additions & 1 deletion frontend/tests/Tests/Golden.elm
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ goldenOneTypeJson =
"tag": "MyUnit",
"contents": []
}
]
],
"recordUnit": []
}
"""
2 changes: 1 addition & 1 deletion src/Elm/Ast.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ data ElmDefinition
-- | AST for @record type alias@ in Elm.
data ElmRecord = ElmRecord
{ elmRecordName :: !Text -- ^ Name of the record
, elmRecordFields :: !(NonEmpty ElmRecordField) -- ^ List of fields
, elmRecordFields :: ![ElmRecordField] -- ^ List of fields
, elmRecordIsNewtype :: !Bool -- ^ 'True' if Haskell type is a @newtype@
} deriving (Show)

Expand Down
10 changes: 7 additions & 3 deletions src/Elm/Generic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ __instance__ Elm (Id a) __where__
elmNewtype :: forall a . Elm a => Text -> Text -> ElmDefinition
elmNewtype typeName fieldName = DefRecord $ ElmRecord
{ elmRecordName = typeName
, elmRecordFields = ElmRecordField (elmRef @a) fieldName :| []
, elmRecordFields = ElmRecordField (elmRef @a) fieldName : []
, elmRecordIsNewtype = True
}

Expand Down Expand Up @@ -213,10 +213,14 @@ data GenericConstructor = GenericConstructor
2. All fields have names: record constructor.
3. Not all fields have names: plain constructor.
-}
toElmConstructor :: GenericConstructor -> Either (NonEmpty ElmRecordField) ElmConstructor
toElmConstructor :: GenericConstructor -> Either [ElmRecordField] ElmConstructor
toElmConstructor GenericConstructor{..} = case genericConstructorFields of
-- Even though elm supports records with no field {} we don't ever derive these
-- Haskell records are in fact just type field accessors so there is ambiguity for zero fields constructors
-- Usually it makes more sense to derive Single constructor type for the Elm side so that's what we do
-- However it's possible to manually define instance of Elm class that would produce empty records (the other unit) type on Elm side
Comment on lines +218 to +221
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important comment

[] -> Right $ ElmConstructor genericConstructorName []
f:fs -> case traverse toRecordField (f :| fs) of
f:fs -> case traverse toRecordField (f : fs) of
Nothing -> Right $ ElmConstructor genericConstructorName $ map fst genericConstructorFields
Just fields -> Left fields
where
Expand Down
21 changes: 17 additions & 4 deletions src/Elm/Print/Decoder.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Elm.Ast (ElmConstructor (..), ElmDefinition (..), ElmPrim (..), ElmRecord
import Elm.Print.Common (arrow, mkQualified, qualifiedTypeWithVarsDoc, showDoc, wrapParens)

import qualified Data.List.NonEmpty as NE
import qualified Data.Maybe as Maybe
import qualified Data.Text as T


Expand Down Expand Up @@ -78,19 +79,31 @@ recordDecoderDoc :: ElmRecord -> Doc ann
recordDecoderDoc ElmRecord{..} =
decoderDef elmRecordName []
<> line
<> if elmRecordIsNewtype
<> if isEmptyRecord
then emptyRecordDecoder
else if elmRecordIsNewtype
Comment on lines +82 to +84
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really excited about this. Any idea?

then newtypeDecoder
else recordDecoder
where
isEmptyRecord :: Bool
isEmptyRecord = null elmRecordFields

emptyRecordDecoder :: Doc ann
emptyRecordDecoder = name <+> "D.succeed {}"

newtypeDecoder :: Doc ann
newtypeDecoder = name <+> "D.map" <+> qualifiedRecordName
<+> wrapParens (typeRefDecoder $ elmRecordFieldType $ NE.head elmRecordFields)
newtypeDecoder =
case typeRefDecoder <$> elmRecordFieldType <$> Maybe.listToMaybe elmRecordFields of
Just field ->
name <+> "D.map" <+> qualifiedRecordName
<+> wrapParens field
Nothing -> emptyRecordDecoder

recordDecoder :: Doc ann
recordDecoder = nest 4
$ vsep
$ (name <+> "D.succeed" <+> qualifiedRecordName)
: map fieldDecode (toList elmRecordFields)
: map fieldDecode elmRecordFields

name :: Doc ann
name = decoderName elmRecordName <+> equals
Expand Down
28 changes: 20 additions & 8 deletions src/Elm/Print/Encoder.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ module Elm.Print.Encoder
, encodeNonEmpty
) where

import Data.List.NonEmpty (NonEmpty, toList)
import Data.Text (Text)
import Data.Text.Prettyprint.Doc (Doc, brackets, colon, comma, concatWith, dquotes, emptyDoc,
equals, lbracket, line, nest, parens, pretty, rbracket, surround,
Expand All @@ -24,6 +23,7 @@ import Elm.Ast (ElmConstructor (..), ElmDefinition (..), ElmPrim (..), ElmRecord
import Elm.Print.Common (arrow, mkQualified, qualifiedTypeWithVarsDoc, showDoc, wrapParens)

import qualified Data.List.NonEmpty as NE
import qualified Data.Maybe as Maybe
import qualified Data.Text as T


Expand Down Expand Up @@ -76,7 +76,7 @@ typeEncoderDoc t@ElmType{..} =
sumEncoder = nest 4
$ vsep
$ (name <+> "x" <+> equals <+> "E.object <| case x of")
: map mkCase (toList elmTypeConstructors)
: map mkCase (NE.toList elmTypeConstructors)

-- | Encoder function name
name :: Doc ann
Expand Down Expand Up @@ -119,12 +119,24 @@ recordEncoderDoc :: ElmRecord -> Doc ann
recordEncoderDoc ElmRecord{..} =
encoderDef elmRecordName []
<> line
<> if elmRecordIsNewtype
then newtypeEncoder
else recordEncoder
<>
if isEmptyRecord
then emptyRecordEncoder
else if elmRecordIsNewtype
then newtypeEncoder
else recordEncoder
where
isEmptyRecord :: Bool
isEmptyRecord = null elmRecordFields

newtypeEncoder :: Doc ann
newtypeEncoder = leftPart <+> fieldEncoderDoc (NE.head elmRecordFields)
newtypeEncoder =
case fieldEncoderDoc <$> (Maybe.listToMaybe elmRecordFields) of
Just rightPart -> leftPart <+> rightPart
Nothing -> emptyRecordEncoder

emptyRecordEncoder :: Doc ann
emptyRecordEncoder = leftPart <+> "list (\\_ -> null) []"

recordEncoder :: Doc ann
recordEncoder = nest 4
Expand All @@ -135,10 +147,10 @@ recordEncoderDoc ElmRecord{..} =
leftPart :: Doc ann
leftPart = encoderName elmRecordName <+> "x" <+> equals

fieldsEncode :: NonEmpty ElmRecordField -> [Doc ann]
fieldsEncode :: [ElmRecordField] -> [Doc ann]
fieldsEncode fields =
lbracket <+> mkTag elmRecordName
: map ((comma <+>) . recordFieldDoc) (NE.toList fields)
: map ((comma <+>) . recordFieldDoc) fields
++ [rbracket]

recordFieldDoc :: ElmRecordField -> Doc ann
Expand Down
12 changes: 7 additions & 5 deletions src/Elm/Print/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,13 @@ elmRecordDoc ElmRecord{..} = nest 4 $
vsep $ ("type alias" <+> pretty elmRecordName <+> equals)
: fieldsDoc elmRecordFields
where
fieldsDoc :: NonEmpty ElmRecordField -> [Doc ann]
fieldsDoc (fstR :| rest) =
lbrace <+> recordFieldDoc fstR
: map ((comma <+>) . recordFieldDoc) rest
++ [rbrace]
fieldsDoc :: [ElmRecordField] -> [Doc ann]
fieldsDoc = \case
[] -> [lbrace <+> rbrace]
fstR : rest ->
lbrace <+> recordFieldDoc fstR
: map ((comma <+>) . recordFieldDoc) rest
++ [rbrace]

recordFieldDoc :: ElmRecordField -> Doc ann
recordFieldDoc ElmRecordField{..} =
Expand Down
5 changes: 3 additions & 2 deletions test/golden/oneType.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"objectField": {},
"arrayField": [1,2,3],
"nullField": null
}
}
},
"myUnit": {
"tag": "MyUnit",
Expand Down Expand Up @@ -98,5 +98,6 @@
"tag": "MyUnit",
"contents": []
}
]
],
"recordUnit": []
}
26 changes: 21 additions & 5 deletions types/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ module Types
, UserRequest (..)
) where

import Data.Aeson (FromJSON (..), ToJSON (..), Value(..), object, (.=))
import Data.List.NonEmpty (NonEmpty(..))
import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), object, (.=))
import Data.List.NonEmpty (NonEmpty (..))
import Data.Text (Text)
import Data.Time.Calendar (fromGregorian)
import Data.Time.Clock (UTCTime (..))
import Data.Word (Word32)
import Elm (Elm (..), ElmStreet (..), elmNewtype, elmStreetParseJson, elmStreetToJson)
import Elm (Elm (..), ElmDefinition (..), ElmRecord (..), ElmStreet (..), elmNewtype,
elmStreetParseJson, elmStreetToJson)
import GHC.Generics (Generic)


Expand All @@ -45,7 +46,7 @@ data Prims = Prims
, primsFloat :: !Double
, primsText :: !Text
, primsTime :: !UTCTime
, primsValue :: !Value
, primsValue :: !Value
, primsMaybe :: !(Maybe Word)
, primsResult :: !(Either Int Text)
, primsPair :: !(Char, Bool)
Expand Down Expand Up @@ -150,12 +151,24 @@ instance FromJSON MyUnit where parseJSON = elmStreetParseJson
data MyResult
= Ok
| Err Text
deriving (Generic, Eq, Show)
deriving stock (Generic, Eq, Show)
deriving anyclass (Elm)

instance ToJSON MyResult where toJSON = elmStreetToJson
instance FromJSON MyResult where parseJSON = elmStreetParseJson

data RecordUnit = RecordUnit
deriving stock (Generic, Eq, Show)
deriving anyclass (FromJSON, ToJSON)

instance Elm RecordUnit where
toElmDefinition _ = DefRecord $ ElmRecord
{ elmRecordName = "RecordUnit"
, elmRecordFields = []
, elmRecordIsNewtype = False
}

Comment on lines +160 to +170
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manual instance implementation of record with no fields.


-- | All test types together in one type to play with.
data OneType = OneType
{ oneTypePrims :: !Prims
Expand All @@ -171,6 +184,7 @@ data OneType = OneType
, oneTypeGuests :: ![Guest]
, oneTypeUserRequest :: !UserRequest
, oneTypeNonEmpty :: !(NonEmpty MyUnit)
, oneTypeRecordUnit :: !RecordUnit
} deriving (Generic, Eq, Show)
deriving anyclass (Elm)

Expand All @@ -192,6 +206,7 @@ type Types =
, Guest
, UserRequest
, OneType
, RecordUnit
]


Expand All @@ -210,6 +225,7 @@ defaultOneType = OneType
, oneTypeGuests = [guestRegular, guestVisitor, guestBlocked]
, oneTypeUserRequest = defaultUserRequest
, oneTypeNonEmpty = MyUnit () :| [ MyUnit () ]
, oneTypeRecordUnit = RecordUnit
}
where
defaultPrims :: Prims
Expand Down