12

キーと値のペアの関係の種類に特定の制約を加えた、Data.Map 用の特別なスマート コンストラクターを作成したいと考えています。これは私が表現しようとした制約です:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, DataKinds #-}

data Field = Speed | Name | ID
data Value = VFloat Float | VString ByteString | VInt Int

class Pair f b | f -> b where
    toPair :: f -> b -> (f, b)
    toPair = (,)

instance Pair Speed (VFloat f) 
instance Pair ID (VInt i)

各フィールドには、関連付ける値のタイプが 1 つだけあります。Speed私の場合、フィールドを にマップするのは意味がありませんByteStringSpeedフィールドは一意にマップする必要がありますFloat

しかし、次のタイプのエラーが発生します。

Kind mis-match
The first argument of `Pair' should have kind `*',
but `VInt' has kind `Value'
In the instance declaration for `Pair Speed (VFloat f)'

使用-XKindSignatures:

class Pair (f :: Field) (b :: Value) | f -> b where
    toPair :: f -> b -> (f, b)
    toPair = (,)

Kind mis-match
Expected kind `OpenKind', but `f' has kind `Field'
In the type `f -> b -> (f, b)'
In the class declaration for `Pair'

Kind の不一致が発生する理由は理解していますが、toPair一致Fieldしない and で使用するコンパイル時の型チェッカー エラーになるように、この制約をどのように表現できますかValue

#haskell から a を使用するように提案されGADTましたが、まだ理解できていません。

これの目標は、書くことができるようになることです

type Record = Map Field Value

mkRecord :: [Field] -> [Value] -> Record
mkRecord = (fromList .) . zipWith toPair

Mapキー/値の不変条件が尊重される安全な s を作成できるようにします。

したがって、これは型チェックする必要があります

test1 = mkRecord [Speed, ID] [VFloat 1.0, VInt 2]

しかし、これはコンパイル時エラーであるはずです

test2 = mkRecord [Speed] [VInt 1]

編集:

私の特定の要件は不可能だと思い始めています。私の元の例を使用して

data Foo = FooInt | FooFloat
data Bar = BarInt Int | BarFloat Float

Fooとに制約を課すには、タイプ レベルで とをBar区別する何らかの方法が必要です。したがって、代わりに2つのGADTが必要ですFooIntFooFloatBar

data Foo :: * -> * where
    FooInt   :: Foo Int
    FooFloat :: Foo Float

data Bar :: * -> * where
    BarInt   :: Int   -> Bar Int
    BarFloat :: Float -> Bar Float

との両方が同じタイプでタグ付けされてPairいる場合にのみ保持されるのインスタンスを作成できるようになりましたFooBar

instance Pair (Foo a) (Bar a)

そして、私は私が望むプロパティを持っています

test1 = toPair FooInt (BarInt 1)   -- type-checks
test2 = toPair FooInt (BarFloat 1) -- no instance for Pair (Foo Int) (Bar Float)

xs = [FooInt, FooFloat]しかし、それには異種のリストが必要になるため、書く能力を失います。Mapさらに、シノニムを作成しようとすると、タイプのみまたはタイプのみのどちらかでtype FooBar = Map (Foo ?) (Bar ?)立ち往生しています。これは私が望んでいるものではありません。私が気付いていない強力な型クラスの魔法がない限り、それはかなり絶望的に見えます。MapIntFloat

4

4 に答える 4

5

次のように GADT を使用できます。

data Bar :: * -> * where
   BarInt   :: Int -> Bar Int
   BarFloat :: Float -> Bar Float

これで、2 つの異なるタイプのバー (Bar Int) と (Bar Float) が利用可能になりました。そうしない理由がない限り、Foo を 2 つのタイプに分割できます。

data FooInt 
data FooFloat

class Pair f b c| f b -> c where
    toPair :: f -> b -> c

instance Pair FooInt (Bar Int) (FooInt,Int) where
    toPair a (BarInt b)= (a,b) 

これは不器用な例ですが、GADT を使用して型を特殊化する方法を示しています。アイデアは、彼らが「ファントムタイプ」を運ぶということです. このページとこのページの DataKindsでかなり詳しく説明されています。

編集:

Foo GADT と Bar GADT の両方を作成すると、ここで説明されている型またはデータ ファミリを使用できます。したがって、この組み合わせにより、キーの種類に基づいて Map の種類を設定できます。これを達成するためのもっと簡単な方法が他にもあるように感じますが、2 つの素晴らしい GHC 拡張機能を紹介しています!

data Foo :: * -> * where
   FooInt   :: Int   -> Foo Int
   FooFloat :: Float -> Foo Float

data Bar :: * -> * where
   BarInt   :: Int   -> Bar Int
   BarFloat :: Float -> Bar Float

class Pair f b c| f b -> c where
    toPair :: f -> b -> c

instance Pair (Foo Int) (Bar Int) ((Foo Int),Int) where
   toPair a (BarInt b)= (a,b)    


type family FooMap k :: *

type instance FooMap (Foo Int) = Map (Foo Int) (Bar Int)
于 2013-02-28T05:08:19.560 に答える
4

Dynamic と Typeable と FunDeps を使用したオールドスクール バージョン。SM安全に保つために、コンストラクターや型クラスなどの抽象化を破るものをエクスポートしない必要がありますSMKey

{-# LANGUAGE DeriveDataTypeable, MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances, FlexibleInstances #-}
module Main where
import qualified Data.Map as M
import Data.Dynamic
import Data.Typeable

data SpecialMap = SM (M.Map String Dynamic)

emptySM = SM (M.empty)

class (Typeable a, Typeable b) => SMKey a b | a -> b

data Speed = Speed deriving Typeable
data Name = Name deriving Typeable
data ID = ID deriving Typeable

instance SMKey Speed Float
instance SMKey Name String
instance SMKey ID Int

insertSM :: SMKey k v => k -> v -> SpecialMap -> SpecialMap
insertSM k v (SM m) = SM (M.insert (show $ typeOf k) (toDyn v) m)

lookupSM :: SMKey k v => k -> SpecialMap -> Maybe v
lookupSM k (SM m) =  fromDynamic =<< M.lookup (show $ typeOf k) m

-- and now lists

newtype SMPair = SMPair {unSMPair :: (String, Dynamic)}
toSMPair :: SMKey k v => k -> v -> SMPair
toSMPair k v = SMPair (show $ typeOf k, toDyn v)

fromPairList :: [SMPair] -> SpecialMap
fromPairList = SM . M.fromList . map unSMPair

{-
*Main> let x = fromPairList [toSMPair Speed 1.2, toSMPair ID 34]
*Main> lookupSM Speed x
Just 1.2
-}
于 2013-03-07T16:28:47.687 に答える
2

これを最初に読んだとき、必要な場合に強制的にコンパイル エラーが発生するという問題を解決しようとしましたが、何かがおかしいように思えました。次に、複数のマップとリフティング関数を使用したアプローチを試みましたが、まだ何かが私を悩ませていました。しかし、本質的にあなたがやろうとしているのは何らかの形の拡張可能なレコードを作成することだと気づいたとき、私は数ヶ月前に気づいた非常にクールなパッケージを思い出しました: Vinyl パッケージ( Hackage で入手可能)。これはまさにあなたが求めていた効果であるかもしれませんし、そうでないかもしれません.GHC 7.6が必要ですが、readmeから改作された例を次に示します:

{-# LANGUAGE DataKinds, TypeOperators #-}
{-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-}

import Data.Vinyl

speed = Field :: "speed" ::: Float
name  = Field :: "name"  ::: String
iD    = Field :: "id"    ::: Int

これで、これらのフィールドをいくつでも含むレコードを作成できます。

test1 = speed =: 0.2

test2 = speed =: 0.2 
    <+> name  =: "Ted"
    <+> iD    =: 1

これらは型が異なるため、特定の関数で間違った量の情報を渡そうとすると、コンパイル エラーが発生します。型シノニムはこれを使いやすくしますが、型注釈は必須ではありません。

type Entity = Rec ["speed" ::: Float, "name" ::: String, "id" ::: Int]
test2 :: Entity

このライブラリは、テンプレート Haskell を必要としないこれらの型の自動レンズと、サブタイプを簡単に処理できるキャスト関数を提供します。例えば:

test2Casted :: Rec '["speed" ::: Float]
test2Casted = cast test2

(単一フィールド レコードの種類を正しく取得するには、追加のティックが必要です)。

これは、あなたが望んでいた正確なタイプを許可しませんがmkRecord、拡張可能なレコードの静的チェックの要件をキャプチャしているようです。これがうまくいかない場合でも、Vinyl ソースにある巧妙なタイプ手法を使用して、目的の場所に到達できる可能性があります。

于 2013-03-03T13:57:10.210 に答える
-1

いくつかのゲッターとセッターを使用して不透明なデータ型を作成します。

module Rec (Rec, getSpeed, getName, getID, setSpeed, setName, setID, blank) where

data Rec = R { speed :: Maybe Double; name :: Maybe String; id :: Maybe Int }

getSpeed :: Rec -> Maybe Double
getSpeed = speed

setSpeed :: Double -> Rec -> Rec
setSpeed s r = r { speed = s }

blank = R { speed = Nothing, name = Nothing, id = Nothing }
于 2013-03-07T19:20:00.897 に答える