7

環境

私たちが持っている場合

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

Applicative コンテキスト (ここでは IO) で Applicative スタイルを構築することができます。

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

問題

レコード フィールド名を明示的に書き出すことで構築したい場合はどうすればよいでしょうか? そのような:

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

これは型チェックしません。1つの解決策は

{-# 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-sopApplicative の型を異種リストの別の型パラメーターに格納しますI。生成された変換を使用する場合、それは常に (Identity) です。したがって、hlist をマップし、エレメントから を削除する必要がありますI。これにより、ApplicativeIが上記の型パラメーター ( になりますComp IO Maybe) に効果的に移動します。 .hsequenceI

しかし、削除/追加関数の型署名を記述する方法がわかりませんI。これは、それぞれの hlist 要素の型が外側の型を失う/獲得することによって一貫して変化することを伝えます。これは可能ですか?

4

2 に答える 2

0

Generics の問題は、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コンストラクター fromは、関数の結果をコンポジットTypeComposeにラップするために使用されます。IO (Maybe a)((IO .: Maybe) a)

于 2016-10-25T14:10:33.097 に答える
0

しかし、外側の型を失う/獲得することによって、それぞれの hlist 要素の型が一貫して変化することを伝える I 削除/追加関数の型署名を記述する方法がわかりません。これは可能ですか?

その方法もわかりません。考えられる回避策 (いくつかの定型文を犠牲にして) は、レコード パターンのシノニムを使用して積和表現を直接構築することですが、名前付きフィールドを使用することもできます。

{-# 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 に答える