Haskell类型算术:如何访问类型级列表

时间:2016-08-24 09:25:25

标签: haskell ghc typeclass type-level-computation

我正在研究基于单元的数据库模型的类型级表示,它将查询作为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"] ],这是类型级别的列表列表。

我不知道如何从那里提取维度维度值

2 个答案:

答案 0 :(得分:1)

我真的不明白你想要做什么,但我会这样说。类型主要用于对值进行分类。构建大量的类型级信息,使用Proxy在值级别上擦除它,然后尝试使用类来恢复它,以便在类型上进行模式匹配,从而导致代码复杂化(因为您已经在安全性或简洁性方面并没有给你带来任何好处。

保持简单。我的建议是更仔细地考虑您的API客户提前知道哪些信息 - 这是类型级的东西 - 以及客户想要动态构建的内容。使用类型级别的东西来对值级别的东西进行分类。

在这种情况下,您的用户会事先知道他们的架构 - 模型的各个方面 - 但他们通常不会知道他们会在哪些维度上查看哪些视图查询。

这是一个草图,它不一定能直接帮助你,但至少应该指向正确的方向。注意我如何使用类型来对值进行分类,而不仅仅是编译时数据的无意义位。这允许我使用类系统以类型导向的方式生成代码,从而在不牺牲安全性的情况下实现简洁的API。另外,如果您愿意放弃TypeOperatorsPatternSynonyms,此解决方案完全是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

用户定义自己的方面类型,如CurrencyFlowType,并为每个方面写入Aspect的实例。然后,他们使用:&:将方面组合成更大的类型,使用I终止列表。然后,当构建查询时,客户端必须以正确的顺序为各个方面提供值。

以下是它的实施方式。使用:&:类型组合子构建的模型将自动成为以下Query类的实例。

class Query a where
    showQuery :: a -> String

我将使用:&:构建的模型表示为嵌套元组。这允许我构建和递归任意大小的元组。 Q只是将ModelDouble值配对,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类的关联类型系列。