我正在研究基于单元的数据库模型的类型级表示,它将查询作为Haskell类型。我试图从更复杂的查询类型中提取值时遇到困难。
让我先向您展示有效的代码:
-- a model with datapoints defined by a list of "aspects"
-- every aspect has a "dimension" and a list of dimensional values
type Model = Double $|$ Aspect "currency" '["eur", "usd"]
|$ Aspect "flowtype" '["stock", "flow"]
|$ Nil
-- extract the aspects from the query type
class GetAspectsSingle a where
getAspectsSingle :: Proxy a -> [(Dimension, DimValue)]
instance (KnownSymbol d, KnownSymbol v, GetAspectsSingle as)
=> GetAspectsSingle (Aspect d v |$ as) where
getAspectsSingle _ = (symbolText (Proxy :: Proxy d),
symbolText (Proxy :: Proxy v))
: (getAspectsSingle (Proxy :: Proxy as))
instance GetAspectsSingle Nil where
getAspectsSingle _ = []
-- a dummy for the execution of a type-safe query
-- where CellTypeSingle is a type function that evaluates to the expected type
save :: (MonadIO m, GetAspectsSingle q)
=> Proxy model -> Proxy q -> CellTypeSingle model q -> m ()
-- just print aspects instead of actual query
save Proxy query _ = liftIO . print $ getAspectsSingle query
-- an example query
query :: Proxy (Aspect "currency" "eur" |$ Aspect "flowtype" "stock" |$ Nil)
query = Proxy
test :: IO ()
test = save (Proxy :: Proxy Model) query 3.3
关键的想法是,类型函数CellTypeSingle
的计算结果为Double
,因此只有当3.3
的类型为Double
时,上述代码才会编译。
我想要查询允许选择几个(相同类型)的值,如下所示:
query :: Proxy (Aspect "currency" '["eur", "usd"] |$ Aspect "flowtype" '["stock"] |$ Nil)
query = Proxy
我设法实现了相应的类型函数CellTypeList
,在上述情况下评估为[Double]
。然而,为了获得方面,我必须首先“爆炸”查询。即上述查询成为查询列表。
这就是我的尝试。
saveList :: (MonadIO m, GetAspectsList q)
=> Proxy model -> Proxy q -> CellTypeList model q -> m ()
-- just print aspects instead of actual query
save Proxy query _ = liftIO . print $ getAspectsList query
class GetAspectsList query where
type GetAspectsListType (query :: Type) :: Type
getAspectsList :: Proxy query -> GetAspectsListType query -> [[(Dimension, DimValue)]]
instance (GetAspectsList as)
=> GetAspectsList (a |$ as) where
type GetAspectsListType (a |$ as) = GetAspectsListType (ExplodeQuery (a |$ as))
getAspectsList = ???
现在我卡住了:ExplodeQuery
评估为'[ '[ Aspect "currency" "eur", Aspect "flowtype" "stock" ], '[ Aspect "currency" "usd", Aspect "flowtype" "stock"] ]
,这是类型级别的列表列表。
我不知道如何从那里提取维度和维度值。
答案 0 :(得分:1)
我真的不明白你想要做什么,但我会这样说。类型主要用于对值进行分类。构建大量的类型级信息,使用Proxy
在值级别上擦除它,然后尝试使用类来恢复它,以便在类型上进行模式匹配,从而导致代码复杂化(因为您已经在安全性或简洁性方面并没有给你带来任何好处。
保持简单。我的建议是更仔细地考虑您的API客户提前知道哪些信息 - 这是类型级的东西 - 以及客户想要动态构建的内容。使用类型级别的东西来对值级别的东西进行分类。
在这种情况下,您的用户会事先知道他们的架构 - 模型的各个方面 - 但他们通常不会知道他们会在哪些维度上查看哪些视图查询。
这是一个草图,它不一定能直接帮助你,但至少应该指向正确的方向。注意我如何使用类型来对值进行分类,而不仅仅是编译时数据的无意义位。这允许我使用类系统以类型导向的方式生成代码,从而在不牺牲安全性的情况下实现简洁的API。另外,如果您愿意放弃TypeOperators
和PatternSynonyms
,此解决方案完全是Haskell 98.
这就是图书馆的API:
data Currency = EUR | USD deriving Show
data FlowType = Stock | FlowType deriving Show
-- this class just wraps up knowledge of the type's name.
-- You could generate these instances using Template Haskell
instance Aspect Currency where
aspectName = const "Currency"
instance Aspect FlowType where
aspectName = const "FlowType"
-- queries contain a currency and a flowtype
type Model = () :&: Currency :&: FlowType
myQuery :: Q Model
myQuery = () :&: EUR :&: Stock :@ 3.3
用户定义自己的方面类型,如Currency
和FlowType
,并为每个方面写入Aspect
的实例。然后,他们使用:&:
将方面组合成更大的类型,使用I
终止列表。然后,当构建查询时,客户端必须以正确的顺序为各个方面提供值。
以下是它的实施方式。使用:&:
类型组合子构建的模型将自动成为以下Query
类的实例。
class Query a where
showQuery :: a -> String
我将使用:&:
构建的模型表示为嵌套元组。这允许我构建和递归任意大小的元组。 Q
只是将Model
与Double
值配对,A
只是方面的标记新类型。
infixl 5 :&:
type (m :&: a) = (m, A a)
pattern m :&: a = (m, A a)
newtype A a = A a
infixl 3 :@
data Q m = m :@ Double
Query
的实例通过嵌套元组上的结构递归将查询编译为字符串。 (如果我们使用扁平元组,我们必须写Query
instance Query a => Query (Q a) where
showQuery (a :@ x) = showQuery a ++ "@" ++ show x
instance (Query a, Query b) => Query (a, b) where
showQuery (x, y) = showQuery x ++ ", " ++ showQuery y
instance Query () where
showQuery = const ""
instance Aspect a => Query (A a) where
showQuery (A x) = aspectName (proxy x) ++ ": " ++ show x
where proxy :: a -> Proxy a
proxy = const Proxy
- 每个大小的元组一个 - 尽管它会稍微改善性能,因为解包元组总是O(1)。)
Aspect
class Show c => Aspect c where
aspectName :: Proxy c -> String
类只包含类型名称的静态知识,以便我们可以在编译后的字符串中使用它。
ghci> showQuery myQuery
", Currency: EUR, FlowType: Stock@3.3" -- the leading comma is fixable. You get the idea
布丁的证据就在吃:
ID
答案 1 :(得分:0)
这要归功于Kosmikus。
{-# LANGUAGE TypeOperators, DataKinds, PolyKinds, ScopedTypeVariables, TypeInType #-}
{-# LANGUAGE TypeFamilies, FlexibleInstances, GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}
module IsList where
import Data.Proxy
import GHC.TypeLits hiding (Nat)
import GHC.Types (Type)
type family Extract (k :: Type) :: Type where
Extract Symbol = String
Extract [a] = [Extract a]
class Extractable (a :: k) where
extract :: Proxy (a :: k) -> Extract k
instance KnownSymbol a => Extractable (a :: Symbol) where
extract p = symbolVal p
instance Extractable ('[] :: [a]) where
extract _ = []
instance (Extractable x, Extractable xs) => Extractable (x ': xs) where
extract _ = extract (Proxy :: Proxy x) : extract (Proxy :: Proxy xs)
它甚至不复杂,但我没有直接嵌套。此解决方案适用于嵌套到任意深度的列表列表。
type family
可能应该是Extractable
类的关联类型系列。