一般的な方法は、このようなより多くの型付き一つにない - 非常に型指定された表現をラップすることです:。
寸法の大きさと
m
のリストは、このリストの長さである(同様に促進及びファントム) -
data Nat = Z | S Nat
newtype Vec (n :: Nat) a = Vec [a]
newtype Sized m (ns :: [Nat]) a = Sized { getArray :: Array (Vec m Int) a }
ここns
が促進(参照)ファントム(Motivation behind Phantom Types?参照)の値です。したがって、Sized
ラッパーの下にある配列は、次元を表すns
の多次元行列とみなされます。 SingI
ものはsingletonsライブラリからのものであることを
instance (SingI m, SingI ns, Monoid a) => Monoid (Sized m ns a) where
mempty = listSized $ repeat mempty
Sized as `mappend` Sized bs = listSized $ zipWith mappend (elems as) (elems bs)
:Monoid
インスタンスは、このようになります。シングルトンは値を型レベルに持ち上げることができるので、従属型をちょっとエミュレートすることができ、SingI
はfromSing
関数を介して値レベルで持ち上げられた値を取り戻すことができます。 listSized
は本質的にはlistArray
ですが、静的に既知の次元を持つ配列の場合には、したがって、それらのすべてが範囲内にあることが必要です。
toInt :: Nat -> Int
toInt = go 0 where
go !a Z = a
go a (S n) = go (1 + a) n
vecBounds :: forall m (ns :: [Nat]). (SingI m) => Sing ns -> (Vec m Int, Vec m Int)
vecBounds singNs = (Vec $ replicate m 0, Vec ns') where
m = toInt $ fromSing (sing :: Sing m)
ns' = map (pred . toInt) $ fromSing singNs
listSized :: forall m (ns :: [Nat]) a. (SingI m, SingI ns) => [a] -> Sized m ns a
listSized = Sized . listArray (vecBounds (sing :: Sing ns))
vecBounds
は寸法の大きさの与えられた昇格リストの境界を計算します。ここではその定義です。これは、第1の成分が、常に、[0,0..0]
という形式の低関心の指標であるタプルを返します(次元数が0の場合は、m
)。 2番目の要素が最大のインデックスです。 [2, 1, 3]
([S (S Z), S Z, S (S (S Z))]
と表示されます)のようなサイズのリストがある場合、最大インデックスは[1, 0, 2]
です。
instance Ix a => Ix (Vec n a) where
range (Vec ns, Vec ms) = map Vec . sequence $ zipWith (curry range) ns ms
index (Vec ns, Vec ms) (Vec ps) = foldr (\(i, r) a -> i + r * a) 0 $
zipWith3 (\n m p -> (index (n, m) p, rangeSize (n, m))) ns ms ps
inRange (Vec ns, Vec ms) (Vec ps) = and $ zipWith3 (curry inRange) ns ms ps
そして、我々はいくつかのテストを書くことができます:
それだけthe product instancesの直接の一般化であるVec n a
ためIx
インスタンスを提供するために、残って
type M = S (S (S Z))
type Ns = [S (S Z), S Z, S (S (S Z))]
arr1 :: Sized M Ns (Sum Int)
arr1 = listSized $ map Sum [5,3,6,7,1,4]
arr2 :: Sized M Ns (Sum Int)
arr2 = listSized $ map Sum [8,2,9,7,3,6]
main = mapM_ (print . getArray) $ [arr1, arr2, arr1 `mappend` arr2 `mappend` mempty]
これは
array (Vec [0,0,0],Vec [1,0,2]) [(Vec [0,0,0],Sum {getSum = 5}),(Vec [0,0,1],Sum {getSum = 6}),(Vec [0,0,2],Sum {getSum = 1}),(Vec [1,0,0],Sum {getSum = 3}),(Vec [1,0,1],Sum {getSum = 7}),(Vec [1,0,2],Sum {getSum = 4})]
array (Vec [0,0,0],Vec [1,0,2]) [(Vec [0,0,0],Sum {getSum = 8}),(Vec [0,0,1],Sum {getSum = 9}),(Vec [0,0,2],Sum {getSum = 3}),(Vec [1,0,0],Sum {getSum = 2}),(Vec [1,0,1],Sum {getSum = 7}),(Vec [1,0,2],Sum {getSum = 6})]
array (Vec [0,0,0],Vec [1,0,2]) [(Vec [0,0,0],Sum {getSum = 13}),(Vec [0,0,1],Sum {getSum = 15}),(Vec [0,0,2],Sum {getSum = 4}),(Vec [1,0,0],Sum {getSum = 5}),(Vec [1,0,1],Sum {getSum = 14}),(Vec [1,0,2],Sum {getSum = 10})]
を印刷します
必要に応じて要素を点単位で合計した。
type Ns = [S (S Z), S Z, S (S (S Z))]
type Ns' = [S (S (S Z)), S Z, S (S Z)]
arr1 :: Sized M Ns (Sum Int)
arr1 = listSized $ map Sum [5,3,6,7,1,4]
arr2 :: Sized M Ns' (Sum Int)
arr2 = listSized $ map Sum [8,2,9,7,3,6]
main = print . getArray $ arr1 `mappend` arr2
-- Couldn't match type 'S 'Z with 'Z …
-- Expected type: Sized M Ns (Sum Int)
-- Actual type: Sized M Ns' (Sum Int)
-- In the second argument of `mappend', namely `arr2'
-- In the first argument of `mappend', namely `arr1 `mappend` arr2'
Full code:あなたが誤って異なる次元での配列を合計しようと、あなたは型エラーを取得します。 user3237465さん@
うわー:D!あなたの詳細な説明とあなたのすべての仕事をありがとう!正直言って、私は建設を完全に理解する前に多くのことを読まなければならないのではないかと心配しています(私はファントムもプロモーションも熟知していません)。私はあなたのギターサイドで、あなたもAgdaを使っているのを見ました。このようなことはAgdaのような従属型の言語ではるかに簡単にできますか?再度ありがとう! – dmw64
@ dmw64、よろしいですか? Agdaは完全な依存型を持っているので、シングルトンを介して型レベルに値を持ち上げる必要はなく、そのまま直接使うことができるので、いくらかの定型文を保存できますが、それ以外は非常に似ていますAFAIK Agdaには組み込み配列がないため、FFIと同じファントムタイプを使用する必要があります)。 – user3237465