Skip to content

Commit

Permalink
Merge pull request #20 from nomeata/joachim/subtype
Browse files Browse the repository at this point in the history
Implement new Coercion rules
  • Loading branch information
nomeata authored Dec 5, 2022
2 parents 193c191 + 2c102c1 commit 87a4f01
Show file tree
Hide file tree
Showing 18 changed files with 816 additions and 640 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
with:
repository: 'dfinity/candid'
path: candid
ref: ab15036543c10066d0b5c266de2108e9e0133e0a
ref: 6edd82f928b6242c596003ced00ffc8e099ee04e

- uses: haskell/actions/setup@v1
with:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Revision history for haskell-candid

## 0.4-- 2022-11-05

* Fix did file parsing bug: Allow underscores in unicode escapes
* Implement the new subtyping rules from spec version 0.1.4
https://github.com/dfinity/candid/pull/311

## 0.3.2.1 -- 2022-12-01

* GHC-9.2 compatibility
Expand Down
4 changes: 3 additions & 1 deletion candid.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: >=1.10
name: candid
version: 0.3.2.1
version: 0.4
license: Apache
license-file: LICENSE
maintainer: [email protected]
Expand Down Expand Up @@ -41,6 +41,7 @@ library
Codec.Candid.EncodeTextual
Codec.Candid.Encode
Codec.Candid.Infer
Codec.Candid.Subtype
Codec.Candid.Coerce

default-language: Haskell2010
Expand Down Expand Up @@ -89,6 +90,7 @@ test-suite test
hs-source-dirs: test
other-modules:
SpecTests
Tests
THTests

default-language: Haskell2010
Expand Down
30 changes: 23 additions & 7 deletions src/Codec/Candid.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{-|
This module provides preliminary Haskell supprot for decoding and encoding the __Candid__ data format. See <https://github.com/dfinity/candid/blob/master/spec/Candid.md> for the official Candid specification.
__Warning:__ The interface of this library is still in flux, as we are yet learning the best idioms around Candid and Haskell.
This module provides Haskell support for decoding and encoding the __Candid__ data format. See <https://github.com/dfinity/candid/blob/master/spec/Candid.md> for the official Candid specification.
-}

Expand Down Expand Up @@ -49,7 +47,6 @@ Candid is inherently typed, so before encoding or decoding, you have to indicate
{- |
* Generating interface descriptions (.did files) from Haskell functions
* Future types
* Parsing the textual representation dynamically against an expected type
-}
Expand Down Expand Up @@ -118,6 +115,7 @@ Candid is inherently typed, so before encoding or decoding, you have to indicate
, unescapeFieldName
, candidHash
, Value(..)
, isSubtypeOf

-- ** Dynamic use

Expand Down Expand Up @@ -150,15 +148,18 @@ import Codec.Candid.TypTable
import Codec.Candid.Decode
import Codec.Candid.Encode
import Codec.Candid.EncodeTextual
import Codec.Candid.Subtype

-- $setup
-- >>> :set -dppr-cols=200
-- >>> import Data.Text (Text)
-- >>> import qualified Data.Text as T
-- >>> import Data.Void (Void)
-- >>> import Data.Void (Void, vacuous)
-- >>> import Prettyprinter (pretty)
-- >>> import qualified Data.ByteString.Lazy.Char8 as BS
-- >>> import Numeric.Natural
-- >>> :set -XScopedTypeVariables
-- >>> :set -XTypeApplications

{- $haskell_types
Expand All @@ -169,7 +170,7 @@ The easiest way is to use this library is to use the canonical Haskell types. An
>>> decode (encode ([True, False], Just 100)) == Right ([True, False], Just 100)
True
Here, no type annotations are needed, the library can infer them from the types of the Haskell values. You can see the Candid types used using `typeDesc` and `seqDesc`:
Here, no type annotations are needed, the library can infer them from the types of the Haskell values. You can see the Candid types used using `seqDesc` (with `tieKnot`) for an argument sequence, or `typeDesc` for a single type:
>>> :type +d ([True, False], Just 100)
([True, False], Just 100) :: ([Bool], Maybe Integer)
Expand All @@ -188,6 +189,8 @@ records directly:
>>> pretty (typeDesc @(Rec ("bar" .== Maybe Integer .+ "foo" .== [Bool])))
record {bar : opt int; foo : vec bool}
NB: `typeDesc` cannot work with recursive types, but see `seqDesc` together with `tieKnot`.
-}

{- $own_type
Expand Down Expand Up @@ -375,7 +378,7 @@ If this encounters a Candid type definition, it will just inline them. This mean
Sometimes one needs to interact with Candid in a dynamic way, without static type information.
This library allows the parsing and pretty-printing of candid values. The binary value was copied from above:
This library allows the parsing and pretty-printing of candid values:
>>> import Data.Row
>>> :set -XDataKinds -XTypeOperators
Expand All @@ -402,5 +405,18 @@ Right (#bar .== Just 100 .+ #foo .== [True,False])
This function does not support the full textual format yet; in particular type annotations can only be used around number literals.
Related to dynamic use is the ability to perform a subtype check, using 'isSubtypeOf' (but you have to set up the arguments correctly first):
>>> isSubtypeOf (vacuous $ typeDesc @Natural) (vacuous $ typeDesc @Integer)
Right ()
>>> isSubtypeOf (vacuous $ typeDesc @Integer) (vacuous $ typeDesc @Natural)
Left "Type int is not a subtype of nat"
>>> isSubtypeOf (vacuous $ typeDesc @(Rec ("foo" .== [Bool]))) (vacuous $ typeDesc @(Rec ("bar" .== Maybe Integer .+ "foo" .== Maybe [Bool])))
Right ()
>>> isSubtypeOf (vacuous $ typeDesc @(Rec ("bar" .== Maybe Integer .+ "foo" .== Maybe [Bool]))) (vacuous $ typeDesc @(Rec ("foo" .== [Bool])))
Left "Type opt vec bool is not a subtype of vec bool"
>>> isSubtypeOf (vacuous $ typeDesc @(Rec ("bar" .== Integer))) (vacuous $ typeDesc @(Rec ("foo" .== Integer)))
Left "Missing record field foo of type int"
-}

8 changes: 5 additions & 3 deletions src/Codec/Candid/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ decode b = do
-- Decode
(ts, vs) <- decodeVals b
-- Coerce to expected type
c <- coerceSeqDesc ts (buildSeqDesc (asTypes @(AsTuple a)))
vs' <- c vs
vs' <- coerceSeqDesc vs ts (buildSeqDesc (asTypes @(AsTuple a)))
fromCandidVals vs'

-- | Decode (dynamic) values to Haskell type
Expand Down Expand Up @@ -106,9 +105,12 @@ class CandidSeq a where
seqDesc :: forall a. CandidArg a => SeqDesc
seqDesc = buildSeqDesc (asTypes @(AsTuple a))

typeGraph :: forall a. Candid a => Type (Ref TypeRep Type)
typeGraph = asType @(AsCandid a)

-- | NB: This will loop with recursive types!
typeDesc :: forall a. Candid a => Type Void
typeDesc = asType @(AsCandid a) >>= go
typeDesc = typeGraph @a >>= go
where go (Ref _ t) = t >>= go

instance Pretty TypeRep where
Expand Down
Loading

0 comments on commit 87a4f01

Please sign in to comment.