7

语境

如果我们有

data Foo = Foo { x :: Maybe Int, y :: Maybe Text }

我们已经可以在 Applicative 上下文(这里是 IO)中构建它的 applicative-style

myfoo :: IO Foo
myfoo = Foo <$> getEnvInt "someX" <*> getEnvText "someY"

问题

如果一个人更喜欢明确写出记录字段名称怎么办?如:

myfoo = Foo { x = getEnvInt "someX", y = getEnvText "someY" }

这不会进行类型检查。一种解决方案是

{-# LANGUAGE RecordWildCards #-}
myfoo = do
    x <- getEnvInt "someX"
    y <- getEnvText "someY"
    return $ Foo {..}

这还不错。但我想知道(此时仅出于自身考虑)以下是否可行:

data FooC f = FooC { x :: f Int, y :: f Text }
type Foo = FooC Maybe

myfoo :: IO Foo
myfoo = genericsMagic $ FooC
    { x = someEnvInt "someX"
    , y = someEnvText "someY"
    }

我相信它可以通过裸GHC.Generics模式匹配来完成,但这不具有类型安全性,所以我正在寻找一种更强大的方法。我遇到过generics-sop,将记录转换为异构列表,并附带一个看似方便的hsequence操作。

我被卡住的地方

generics-sopI将 Applicative 的类型存储在其异构列表的单独类型参数中,并且在使用生成的转换时始终为(Identity)。所以我需要映射 hlist 并I从元素中删除 ,这将有效地将 Applicative 移动I到提到的类型参数(它将是Comp IO Maybe),所以我可以使用hsequence,最后加回Is 以便我可以隐蔽回记录.

但是我不知道如何为I删除/添加函数编写类型签名,该函数表明各个 hlist 元素的类型通过丢失/获得外部类型而不断变化。这甚至可能吗?

4

2 回答 2

0

泛型的问题是你的FooC类型有那种类型(* -> *) -> *,据我所知,不可能自动GHC.Generics为这种类型派生一个实例。如果您对使用 Template Haskell 的解决方案持开放态度,那么编写自动处理任何记录类型所需的 TH 代码相对容易。

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TemplateHaskell #-}

module AppCon where

import Control.Applicative
import Control.Compose ((:.), unO)
import Language.Haskell.TH

class AppCon t where
  appCon :: Applicative f => t (f :. g) -> f (t g)

deriveAppCon :: Name -> Q [Dec]
deriveAppCon name = do
  (TyConI (DataD _ _ _ _ [RecC con fields] _)) <- reify name

  let names = [mkName (nameBase n) | (n,_,_) <- fields]
      apps = go [|pure $(conE con)|] [[|unO $(varE n)|] | n <- names] where
        go l [] = l
        go l (r:rs) = go [|$l <*> $r|] rs

  [d|instance AppCon $(conT name) where
      appCon ($(conP con (map varP names))) = $apps
    |]

我使用TypeCompose包中的类型组合运算符来定义一个类型类,该类型类可以从记录类型中“解包”单个应用层。即如果你有一个FooC (IO :. Maybe)你可以把它变成一个IO (FooC Maybe)

允许您自动派生任何基本记录类型的deriveAppCon实例。

{-# LANGUAGE TemplateHaskell #-}

import Control.Compose ((:.)(..))

import AppCon

data FooC f = FooC { x :: f Int, y :: f Text }
type Foo = FooC Maybe

deriveAppCon ''FooC

myfoo :: IO Foo
myfoo = appCon $ FooC
    { x = O $ someEnvInt "someX"
    , y = O $ someEnvText "someY"
    }

构造O函数 fromTypeCompose用于将函数结果包装IO (Maybe a)到组合((IO .: Maybe) a)中。

于 2016-10-25T14:10:33.097 回答
0

但是我不知道如何为 I 删除/添加函数编写类型签名,该函数表明各个 hlist 元素的类型通过丢失/获得外部类型而不断变化。这甚至可能吗?

我也不知道该怎么做。一种可能的解决方法(以一些样板为代价)是使用记录模式同义词直接构建产品总和表示,同时仍然能够使用命名字段:

{-# language DeriveGeneric #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
{-# language PatternSynonyms #-}

import Data.Text
import qualified GHC.Generics as GHC
import Generics.SOP
import Text.Read

data Foo = Foo { x :: Int, y :: Text } deriving (Show, GHC.Generic)

instance Generic Foo

pattern Foo' :: t Int -> t Text -> SOP t (Code Foo)
pattern Foo' {x', y'} = SOP (Z (x' :* y' :* Nil))

readFooMaybe :: SOP (IO :.: Maybe) (Code Foo)
readFooMaybe = Foo'
             {
                x' = Comp (fmap readMaybe getLine)
             ,  y' = Comp (fmap readMaybe getLine)
             }

在 ghci 上测试它:

ghci> hsequence' readFooMaybe >>= print
12
"foo"
SOP (Z (Just 12 :* (Just "foo" :* Nil)))
于 2016-10-24T21:16:51.297 回答