Pure parsing of Dhall files

I use Dhall for simple application configuration, so there are no imports - which means in theory I should be able to parse config.dhall without IO.

In #354, Gabriel provided some hints, so I attempted the following very-rough implementation to get something working:

parsePure :: Text -> Config
parsePure (mergeWithDefault -> cfgText) =
  either (error . show) id
    $ validationToEither
    $ Dhall.extract @Config Dhall.auto
    $ Dhall.Core.normalize
    $ either (error . show) id
    $ Dhall.TypeCheck.typeOf
    $ flip Dhall.Substitution.substitute Dhall.Substitution.empty
    $ fromMaybe (error "imports")
    $ traverse (\_ -> Nothing)
    $ either (error . show) id
    $ Dhall.Parser.exprFromText "neuron.dhall" cfgText

(Full diff here: https://github.com/srid/neuron/pull/267/files )

However, this leads to the following flurry of errors:

Multiple errors were encountered during extraction:

Error: Invalid Dhall.Decoder

Every Decoder must provide an extract function that succeeds if an expression
matches the expected type.  You provided a Decoder that disobeys this contract

The Decoder provided has the expected dhall type:

↳ { aliases : List Text, author : Optional Text }

and it couldn't extract a value from the well-typed expression:

↳ { aliases : List Text
  , author : Optional Text
  , editUrl : Optional Text
  , mathJaxSupport : Bool
  , minVersion : Text
  , siteBaseUrl : Optional Text
  , siteTitle : Text
  , theme : Text
  }


Error: Invalid Dhall.Decoder

Every Decoder must provide an extract function that succeeds if an expression
matches the expected type.  You provided a Decoder that disobeys this contract

The Decoder provided has the expected dhall type:

↳ { editUrl : Optional Text, mathJaxSupport : Bool }

and it couldn't extract a value from the well-typed expression:

↳ { aliases : List Text
  , author : Optional Text
  , editUrl : Optional Text
  , mathJaxSupport : Bool
  , minVersion : Text
  , siteBaseUrl : Optional Text
  , siteTitle : Text
  , theme : Text
  }


Error: Invalid Dhall.Decoder

Every Decoder must provide an extract function that succeeds if an expression
matches the expected type.  You provided a Decoder that disobeys this contract

The Decoder provided has the expected dhall type:

↳ { minVersion : Text, siteBaseUrl : Optional Text }

and it couldn't extract a value from the well-typed expression:

↳ { aliases : List Text
  , author : Optional Text
  , editUrl : Optional Text
  , mathJaxSupport : Bool
  , minVersion : Text
  , siteBaseUrl : Optional Text
  , siteTitle : Text
  , theme : Text
  }


Error: Invalid Dhall.Decoder

Every Decoder must provide an extract function that succeeds if an expression
matches the expected type.  You provided a Decoder that disobeys this contract

The Decoder provided has the expected dhall type:

↳ { siteTitle : Text, theme : Text }

and it couldn't extract a value from the well-typed expression:

↳ { aliases : List Text
  , author : Optional Text
  , editUrl : Optional Text
  , mathJaxSupport : Bool
  , minVersion : Text
  , siteBaseUrl : Optional Text
  , siteTitle : Text
  , theme : Text
  }

I checked the implementation of inputHelper which more or less does the same thing. Does anyone have any idea of what I might be missing?

Overall, it would be good to have an example of how to purely parse a simple Dhall file (no imports).

1 Like

@srid: To answer this for sure, I’d need to know the following things:

  • Is the FromDhall instance for Config derived or manually defined?
    • If it was derived, what is the definition of the Config type?
    • If it was manually written, what is the code for the FromDhall instance?
  • What was the input you supplied to the parsePure function?

I suspect that the parsePure function is fine (albeit with more steps than necessary). I think the issue is specific to your Config type

It is derived like this:

deriving instance FromDhall Config

The Config type:

data Config = Config
  { aliases :: [Text],
    author :: Maybe Text,
    editUrl :: Maybe Text,
    mathJaxSupport :: Bool,
    minVersion :: Text,
    siteBaseUrl :: Maybe Text,
    siteTitle :: Text,
    theme :: Text
  }
  deriving (Eq, Show, Generic, FromJSON, ToJSON)

The exact text passed to parsePure:

{ siteTitle =   "My Zettelkasten" , author =   None Text, siteBaseUrl =   None Text, editUrl =   None Text, theme =   "blue", aliases =   [] : List Text, mathJaxSupport =   True, minVersion =   "0.5" } // { siteTitle =
    "Neuron Zettelkasten"
, siteBaseUrl =
    Some "https://neuron.zettel.page"
, editUrl =
    Some "https://github.com/srid/neuron/edit/master/guide/"
}

Note that parsing works if I replace pure . parsePure with liftIO . Dhall.input Dhall.auto. So I must conclude that the input text (as shown above) is correct.

Repro:

git clone https://github.com/srid/neuron.git -b pure-config 
cd neuron 
cachix use srid 
bin/run -d $(pwd)/guide rib

I don’t think you want to use typeOf like that. You want to discard the result of typeOf, and pass through the original value, which is what you want normalized. So what you actually want is (\a -> a <$ Dhall.TypeCheck.typeOf a), or point-free (<$) <*> Dhall.TypeCheck.typeOf. (All it is doing is ensuring that it is safe to normalize the expression.)

I’m not quite sure if that’s what’s leading to your errors, but I think it should help.

2 Likes

@MonoidMusician That did the trick!

I’ll refactor this code and publish an example for others.

3 Likes

https://www.srid.ca/0db55da8.html