dataframe-expr-serializer 1.2.1.0 → 1.3.0.0
raw patch · 3 files changed
+89/−200 lines, 3 filesdep ~dataframe-coredep ~dataframe-operations
Dependency ranges changed: dataframe-core, dataframe-operations
Files
- dataframe-expr-serializer.cabal +3/−3
- src/DataFrame/Expr/Serialize.hs +7/−29
- src/DataFrame/IR/ExprJson.hs +79/−168
dataframe-expr-serializer.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: dataframe-expr-serializer-version: 1.2.1.0+version: 1.3.0.0 synopsis: Serialize and deserialize dataframe expressions and pipelines. description: Save fitted @Expr@ values and whole pipelines (ordered @[NamedExpr]@) to a@@ -39,8 +39,8 @@ build-depends: base >= 4 && < 5, aeson >= 0.11.0.0 && < 3, bytestring >= 0.11 && < 0.14,- dataframe-core >= 2.5 && < 2.6,- dataframe-operations >= 2.5 && < 2.6,+ dataframe-core >= 2.6 && < 2.7,+ dataframe-operations >= 2.6 && < 2.7, text >= 2.1 && < 3, vector >= 0.13 && < 0.15 hs-source-dirs: src
src/DataFrame/Expr/Serialize.hs view
@@ -5,25 +5,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -{- | Persist dataframe expressions and pipelines, and load them back for-inference in another process (or from Python — see "DataFrame.IR.ExprJson" for-the shared wire format).-- * A single 'Expr' round-trips via 'encodeExpr' / 'decodeExprAny' (or the- type-asserting 'decodeExprAt') and the file helpers below.- * A /pipeline/ is an ordered @['NamedExpr']@ — the same shape a fitted- @Transform@ carries. 'encodeNamedExprs' / 'decodeNamedExprs' wrap it in a- versioned envelope; replay a decoded pipeline for inference with- @deriveMany@ / @applyTransform@.--All functions are pure 'Either'; the @save@ helpers report an encode failure-before touching the file (no partial write), and the @load@ helpers turn a-missing/unreadable file into a 'Left' rather than throwing.--Limitations inherited from the wire format: @CastWith@ / @CastExprWith@ (they-hold closures) and @collect@ (its list-typed output has no type tag) cannot be-serialized.--}+-- | Save and load dataframe expressions and pipelines for inference elsewhere. module DataFrame.Expr.Serialize ( -- * Single expressions (re-exported wire codec) SomeExpr (..),@@ -64,22 +46,20 @@ parseSomeExpr, ) import DataFrame.Internal.Column (Columnable)-import DataFrame.Internal.Expression (Expr, NamedExpr, UExpr (..))+import DataFrame.Internal.Expression (Expr, NamedExpr) -- | Decode a single expression from a strict JSON 'BS.ByteString'. decodeExprFromBytes :: BS.ByteString -> Either String SomeExpr decodeExprFromBytes bs = Aeson.eitherDecodeStrict bs >>= decodeExprAny -{- | Encode an ordered pipeline (named output expressions) into the versioned-envelope @{ "version": 1, "outputs": [ { "name", "expr" }, … ] }@.--}+-- | Encode an ordered pipeline of named expressions into a versioned envelope. encodeNamedExprs :: [NamedExpr] -> Either String Aeson.Value encodeNamedExprs nes = do outs <- traverse encodeOne nes Right $ object ["version" .= (1 :: Int), "outputs" .= outs] where encodeOne :: NamedExpr -> Either String Aeson.Value- encodeOne (name, UExpr e) = do+ encodeOne (name, SomeExpr e) = do ev <- encodeExpr e Right $ object ["name" .= name, "expr" .= ev] @@ -98,8 +78,8 @@ parseOne = Aeson.withObject "PipelineOutput" $ \o -> do name <- o .: "name" :: Aeson.Parser T.Text rawExpr <- o .: "expr"- SomeExpr _ e <- parseSomeExpr rawExpr- return (name, UExpr e)+ SomeExpr e <- parseSomeExpr rawExpr+ return (name, SomeExpr e) -- | Encode an expression and write it to a file. No file is written on failure. saveExprToFile :: (Columnable a) => FilePath -> Expr a -> IO (Either String ())@@ -111,9 +91,7 @@ loadExprFromFile :: FilePath -> IO (Either String SomeExpr) loadExprFromFile fp = (>>= decodeExprFromBytes) <$> readBytes fp -{- | Load an expression from a file, asserting its output type. Fails with-'Left' if the stored expression has a different output type.--}+-- | Load an expression from a file, failing if its output type differs. loadExprAtFromFile :: forall a. (Columnable a) => FilePath -> IO (Either String (Expr a)) loadExprAtFromFile fp = (>>= decode) <$> readBytes fp
src/DataFrame/IR/ExprJson.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}@@ -7,29 +8,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -{- | JSON wire format for 'Expr' values.-- The Python bindings build expression trees as JSON; this module decodes them- back into 'Expr' GADTs (typed) and encodes them in the reverse direction- (used for shipping fitted decision trees back to Python).-- Encoded shape:-- > { "node": "col" , "out_type": "double", "name": "x" }- > { "node": "lit" , "out_type": "int" , "value": 42 }- > { "node": "unary" , "out_type": "double", "op": "toDouble"- > , "arg_type": "int" , "arg": <expr> }- > { "node": "binary", "out_type": "bool" , "op": "leq"- > , "arg_type": "double", "lhs": <expr>, "rhs": <expr> }- > { "node": "if" , "out_type": "text" , "cond": <expr>- > , "then": <expr>, "else": <expr> }-- Operator names follow 'binaryName' / 'unaryName' from- "DataFrame.Internal.Expression". Nullable variants ("nulladd", "nullor", …)- are accepted on decode and treated as their non-null equivalents — Python- data crosses the FFI boundary as non-null Arrow buffers, so this is lossless- for the supported workflow.--}+-- | JSON wire format for 'Expr' values, decoding and encoding. module DataFrame.IR.ExprJson ( SomeExpr (..), encodeExpr,@@ -57,7 +36,7 @@ import Data.Type.Equality (TestEquality (testEquality), type (:~:) (Refl)) import qualified Data.Vector.Unboxed as VU import Data.Word (Word16, Word32, Word64, Word8)-import Type.Reflection (TypeRep, Typeable, typeRep)+import Type.Reflection (Typeable, typeRep) import DataFrame.Expression.Operators ( ifThenElse,@@ -76,15 +55,20 @@ AggStrategy (..), BinaryOp (binaryName), Expr (..),+ SomeExpr (..), UnaryOp (unaryName), ) import qualified DataFrame.Internal.Expression as F--{- | Existential wrapper around a typed expression decoded from JSON.-TODO: mchavinda - Maybe consolidate with UExpr from the main package.--}-data SomeExpr where- SomeExpr :: (Columnable a) => TypeRep a -> Expr a -> SomeExpr+import DataFrame.Internal.Expression.Reflect (+ Dict (..),+ RealUnbox,+ floatingDict,+ fracDict,+ numDict,+ ordDict,+ realDict,+ realUnboxDict,+ ) -- | Map a Haskell type to its wire-format tag string. typeTagOf :: forall a. (Typeable a) => Maybe T.Text@@ -133,131 +117,63 @@ "string" -> k (Proxy @String) _ -> fail $ "DataFrame.IR.ExprJson: unknown type tag: " <> T.unpack t --- | Subset that supports 'Ord' (for comparison ops).+-- | A tag whose type has the class evidence @dict@ provides.+withClassTag ::+ forall c r.+ (forall a. (Columnable a) => Maybe (Dict (c a))) ->+ String ->+ T.Text ->+ (forall a. (Columnable a, c a) => Proxy a -> Aeson.Parser r) ->+ Aeson.Parser r+withClassTag dict what t k = withTypeTag t $ \(p :: Proxy a) -> case dict @a of+ Just Dict -> k p+ Nothing ->+ fail $+ "DataFrame.IR.ExprJson: type does not support " <> what <> ": " <> T.unpack t++-- | Types that support 'Ord' (comparison ops). withOrdTypeTag :: T.Text -> (forall a. (Columnable a, Ord a) => Proxy a -> Aeson.Parser r) -> Aeson.Parser r-withOrdTypeTag t k = case t of- "int" -> k (Proxy @Int)- "int8" -> k (Proxy @Int8)- "int16" -> k (Proxy @Int16)- "int32" -> k (Proxy @Int32)- "int64" -> k (Proxy @Int64)- "word" -> k (Proxy @Word)- "word8" -> k (Proxy @Word8)- "word16" -> k (Proxy @Word16)- "word32" -> k (Proxy @Word32)- "word64" -> k (Proxy @Word64)- "integer" -> k (Proxy @Integer)- "double" -> k (Proxy @Double)- "float" -> k (Proxy @Float)- "bool" -> k (Proxy @Bool)- "char" -> k (Proxy @Char)- "text" -> k (Proxy @T.Text)- "string" -> k (Proxy @String)- _ ->- fail $ "DataFrame.IR.ExprJson: type does not support ordering: " <> T.unpack t+withOrdTypeTag = withClassTag @Ord ordDict "ordering" --- | Subset that supports 'Num' (arithmetic).+-- | Types that support 'Num' (arithmetic). withNumTypeTag :: T.Text -> (forall a. (Columnable a, Num a) => Proxy a -> Aeson.Parser r) -> Aeson.Parser r-withNumTypeTag t k = case t of- "int" -> k (Proxy @Int)- "int8" -> k (Proxy @Int8)- "int16" -> k (Proxy @Int16)- "int32" -> k (Proxy @Int32)- "int64" -> k (Proxy @Int64)- "word" -> k (Proxy @Word)- "word8" -> k (Proxy @Word8)- "word16" -> k (Proxy @Word16)- "word32" -> k (Proxy @Word32)- "word64" -> k (Proxy @Word64)- "integer" -> k (Proxy @Integer)- "double" -> k (Proxy @Double)- "float" -> k (Proxy @Float)- _ ->- fail $ "DataFrame.IR.ExprJson: type does not support arithmetic: " <> T.unpack t+withNumTypeTag = withClassTag @Num numDict "arithmetic" --- | Subset that supports 'Fractional' (division).+-- | Types that support 'Fractional' (division). withFracTypeTag :: T.Text -> (forall a. (Columnable a, Fractional a) => Proxy a -> Aeson.Parser r) -> Aeson.Parser r-withFracTypeTag t k = case t of- "double" -> k (Proxy @Double)- "float" -> k (Proxy @Float)- _ ->- fail $- "DataFrame.IR.ExprJson: type does not support fractional division: "- <> T.unpack t+withFracTypeTag = withClassTag @Fractional fracDict "fractional division" --- | Subset that supports 'Real' (used by toDouble source types).+-- | Types that support 'Real' (toDouble sources). withRealTypeTag :: T.Text -> (forall a. (Columnable a, Real a) => Proxy a -> Aeson.Parser r) -> Aeson.Parser r-withRealTypeTag t k = case t of- "int" -> k (Proxy @Int)- "int8" -> k (Proxy @Int8)- "int16" -> k (Proxy @Int16)- "int32" -> k (Proxy @Int32)- "int64" -> k (Proxy @Int64)- "word" -> k (Proxy @Word)- "word8" -> k (Proxy @Word8)- "word16" -> k (Proxy @Word16)- "word32" -> k (Proxy @Word32)- "word64" -> k (Proxy @Word64)- "integer" -> k (Proxy @Integer)- "double" -> k (Proxy @Double)- "float" -> k (Proxy @Float)- _ ->- fail $- "DataFrame.IR.ExprJson: type does not support Real (toDouble source): "- <> T.unpack t+withRealTypeTag = withClassTag @Real realDict "Real (toDouble source)" --- | Subset that supports 'Floating'.+-- | Types that support 'Floating'. withFloatingTypeTag :: T.Text -> (forall a. (Columnable a, Floating a) => Proxy a -> Aeson.Parser r) -> Aeson.Parser r-withFloatingTypeTag t k = case t of- "double" -> k (Proxy @Double)- "float" -> k (Proxy @Float)- _ ->- fail $ "DataFrame.IR.ExprJson: type does not support Floating: " <> T.unpack t+withFloatingTypeTag = withClassTag @Floating floatingDict "Floating" -{- | Subset that supports both 'Real' and 'VU.Unbox' (used by variance/median).-'Integer' is deliberately omitted — it is 'Real' but not 'Unbox'.--}+-- | Types that support 'Real' and 'VU.Unbox' (variance, median). withRealUnboxTypeTag :: T.Text -> (forall a. (Columnable a, Real a, VU.Unbox a) => Proxy a -> Aeson.Parser r) -> Aeson.Parser r-withRealUnboxTypeTag t k = case t of- "int" -> k (Proxy @Int)- "int8" -> k (Proxy @Int8)- "int16" -> k (Proxy @Int16)- "int32" -> k (Proxy @Int32)- "int64" -> k (Proxy @Int64)- "word" -> k (Proxy @Word)- "word8" -> k (Proxy @Word8)- "word16" -> k (Proxy @Word16)- "word32" -> k (Proxy @Word32)- "word64" -> k (Proxy @Word64)- "double" -> k (Proxy @Double)- "float" -> k (Proxy @Float)- _ ->- fail $- "DataFrame.IR.ExprJson: type does not support Real+Unbox (variance/median): "- <> T.unpack t+withRealUnboxTypeTag = withClassTag @RealUnbox realUnboxDict "Real+Unbox (variance/median)" -{- | Encode an 'Expr' to a JSON value. Returns 'Left' on unsupported-constructors (Agg, Over, CastWith, CastExprWith) or unsupported operator-names (binaryUdf, unaryUdf, …).--}+-- | Encode an 'Expr' to JSON, failing on unsupported constructors or operators. encodeExpr :: forall a. (Columnable a) => Expr a -> Either String Aeson.Value encodeExpr expr = case expr of Col name -> do@@ -405,7 +321,7 @@ Left $ "DataFrame.IR.ExprJson.encodeLit: unsupported type: " <> show (typeRep @a) --- | Map a 'binaryName' string to a wire-format op name. Errors on opaque UDF names.+-- | Map a 'binaryName' string to a wire-format op name. recognizeBinary :: T.Text -> Either String T.Text recognizeBinary n = case n of "add" -> Right "add"@@ -437,7 +353,7 @@ "DataFrame.IR.ExprJson: unsupported binary op (cannot serialize): " <> T.unpack other --- | Map a 'unaryName' string to a wire-format op name. Errors on opaque UDF names.+-- | Map a 'unaryName' string to a wire-format op name. recognizeUnary :: T.Text -> Either String T.Text recognizeUnary n = case n of "not" -> Right "not"@@ -464,9 +380,7 @@ "DataFrame.IR.ExprJson: unsupported unary op (cannot serialize): " <> T.unpack other -{- | Decode a JSON value into a 'SomeExpr'. The output type comes from the-@out_type@ field on the root node.--}+-- | Decode a JSON value into a 'SomeExpr' using the root @out_type@. decodeExprAny :: Aeson.Value -> Either String SomeExpr decodeExprAny = Aeson.parseEither parseSomeExpr @@ -474,15 +388,15 @@ decodeExprAt :: forall a. (Columnable a) => Aeson.Value -> Either String (Expr a) decodeExprAt v = do- SomeExpr trep expr <- decodeExprAny v- case testEquality trep (typeRep @a) of+ SomeExpr (expr :: Expr b) <- decodeExprAny v+ case testEquality (typeRep @b) (typeRep @a) of Just Refl -> Right expr Nothing -> Left $ "DataFrame.IR.ExprJson.decodeExprAt: expected " <> show (typeRep @a) <> " but got "- <> show trep+ <> show (typeRep @b) -- | The Aeson.Parser entry point — useful when composing with bigger parsers. parseSomeExpr :: Aeson.Value -> Aeson.Parser SomeExpr@@ -493,12 +407,12 @@ "col" -> do name <- o .: "name" :: Aeson.Parser T.Text withTypeTag outType $ \(_ :: Proxy a) ->- return $ SomeExpr (typeRep @a) (Col @a name)+ return $ SomeExpr (Col @a name) "lit" -> do rawVal <- o .: "value" withTypeTag outType $ \(_ :: Proxy a) -> do litVal <- decodeLit @a rawVal- return $ SomeExpr (typeRep @a) (Lit @a litVal)+ return $ SomeExpr (Lit @a litVal) "if" -> do rawCond <- o .: "cond" rawThen <- o .: "then"@@ -507,7 +421,7 @@ withTypeTag outType $ \(_ :: Proxy a) -> do thenE <- parseExprAt @a rawThen elseE <- parseExprAt @a rawElse- return $ SomeExpr (typeRep @a) (ifThenElse cond thenE elseE)+ return $ SomeExpr (ifThenElse cond thenE elseE) "unary" -> do op <- o .: "op" :: Aeson.Parser T.Text argType <- o .: "arg_type" :: Aeson.Parser T.Text@@ -529,20 +443,20 @@ rawArg <- o .: "arg" withTypeTag outType $ \(_ :: Proxy a) -> do inner <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @a) (F.over names inner)+ return $ SomeExpr (F.over names inner) other -> fail $ "DataFrame.IR.ExprJson: unknown node kind: " <> T.unpack other parseExprAt :: forall a. (Columnable a) => Aeson.Value -> Aeson.Parser (Expr a) parseExprAt v = do- SomeExpr trep expr <- parseSomeExpr v- case testEquality trep (typeRep @a) of+ SomeExpr (expr :: Expr b) <- parseSomeExpr v+ case testEquality (typeRep @b) (typeRep @a) of Just Refl -> return expr Nothing -> fail $ "DataFrame.IR.ExprJson: expected " <> show (typeRep @a) <> " but got "- <> show trep+ <> show (typeRep @b) decodeLit :: forall a. (Columnable a) => Aeson.Value -> Aeson.Parser a decodeLit v@@ -606,24 +520,24 @@ requireTag outType "bool" requireTag argType "bool" arg <- parseExprAt @Bool rawArg- return $ SomeExpr (typeRep @Bool) (F.not arg)+ return $ SomeExpr (F.not arg) "negate" -> withNumTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @a) (negate arg)+ return $ SomeExpr (negate arg) "abs" -> withNumTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @a) (abs arg)+ return $ SomeExpr (abs arg) "signum" -> withNumTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @a) (signum arg)+ return $ SomeExpr (signum arg) "toDouble" -> do requireTag outType "double" withRealTypeTag argType $ \(_ :: Proxy a) -> do arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @Double) (F.toDouble arg)+ return $ SomeExpr (F.toDouble arg) "exp" -> floatingUnary outType argType rawArg exp "sqrt" -> floatingUnary outType argType rawArg sqrt "log" -> floatingUnary outType argType rawArg log@@ -649,7 +563,7 @@ floatingUnary outType argType rawArg f = withFloatingTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @a) (f arg)+ return $ SomeExpr (f arg) parseBinary :: T.Text ->@@ -664,72 +578,72 @@ withTypeTag argType $ \(_ :: Proxy a) -> do l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @Bool) (l .==. r)+ return $ SomeExpr (l .==. r) "neq" -> do requireTag outType "bool" withTypeTag argType $ \(_ :: Proxy a) -> do l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @Bool) (l ./=. r)+ return $ SomeExpr (l ./=. r) "lt" -> do requireTag outType "bool" withOrdTypeTag argType $ \(_ :: Proxy a) -> do l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @Bool) (l .<. r)+ return $ SomeExpr (l .<. r) "leq" -> do requireTag outType "bool" withOrdTypeTag argType $ \(_ :: Proxy a) -> do l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @Bool) (l .<=. r)+ return $ SomeExpr (l .<=. r) "gt" -> do requireTag outType "bool" withOrdTypeTag argType $ \(_ :: Proxy a) -> do l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @Bool) (l .>. r)+ return $ SomeExpr (l .>. r) "geq" -> do requireTag outType "bool" withOrdTypeTag argType $ \(_ :: Proxy a) -> do l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @Bool) (l .>=. r)+ return $ SomeExpr (l .>=. r) "and" -> do requireTag outType "bool" l <- parseExprAt @Bool rawLhs r <- parseExprAt @Bool rawRhs- return $ SomeExpr (typeRep @Bool) (l .&&. r)+ return $ SomeExpr (l .&&. r) "or" -> do requireTag outType "bool" l <- parseExprAt @Bool rawLhs r <- parseExprAt @Bool rawRhs- return $ SomeExpr (typeRep @Bool) (l .||. r)+ return $ SomeExpr (l .||. r) "add" -> withNumTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @a) (l + r)+ return $ SomeExpr (l + r) "sub" -> withNumTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @a) (l - r)+ return $ SomeExpr (l - r) "mult" -> withNumTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @a) (l * r)+ return $ SomeExpr (l * r) "divide" -> withFracTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @a) (l / r)+ return $ SomeExpr (l / r) "exponentiate" -> withFloatingTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType l <- parseExprAt @a rawLhs r <- parseExprAt @a rawRhs- return $ SomeExpr (typeRep @a) (l ** r)+ return $ SomeExpr (l ** r) "nulladd" -> parseBinary "add" outType argType rawLhs rawRhs "nullsub" -> parseBinary "sub" outType argType rawLhs rawRhs "nullmul" -> parseBinary "mult" outType argType rawLhs rawRhs@@ -738,21 +652,18 @@ "nullor" -> parseBinary "or" outType argType rawLhs rawRhs other -> fail $ "DataFrame.IR.ExprJson: unsupported binary op: " <> T.unpack other -{- | Decode an aggregation node. Dispatches on the stored aggregation name to the-matching 'DataFrame.Functions' builder, picking the type-tag dispatcher demanded-by that builder's constraints.--}+-- | Decode an aggregation node by dispatching on its stored aggregation name. parseAgg :: T.Text -> T.Text -> T.Text -> Aeson.Value -> Aeson.Parser SomeExpr parseAgg aggName outType argType rawArg = case aggName of "count" -> do requireTag outType "int" withTypeTag argType $ \(_ :: Proxy a) -> do arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @Int) (F.count arg)+ return $ SomeExpr (F.count arg) "sum" -> withNumTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @a) (F.sum arg)+ return $ SomeExpr (F.sum arg) "minimum" -> ordAgg F.minimum "maximum" -> ordAgg F.maximum "mode" -> ordAgg F.mode@@ -760,7 +671,7 @@ requireTag outType "double" withRealTypeTag argType $ \(_ :: Proxy a) -> do arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @Double) (F.mean arg)+ return $ SomeExpr (F.mean arg) "variance" -> realUnboxAgg F.variance "median" -> realUnboxAgg F.median "collect" ->@@ -775,7 +686,7 @@ ordAgg op = withOrdTypeTag outType $ \(_ :: Proxy a) -> do requireSame outType argType arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @a) (op arg)+ return $ SomeExpr (op arg) realUnboxAgg :: (forall a. (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double) -> Aeson.Parser SomeExpr@@ -783,4 +694,4 @@ requireTag outType "double" withRealUnboxTypeTag argType $ \(_ :: Proxy a) -> do arg <- parseExprAt @a rawArg- return $ SomeExpr (typeRep @Double) (op arg)+ return $ SomeExpr (op arg)