Создание складки, которая позволяет типу изменять после каждого повторного вызова функции, чтобы вызвать функцию n раз без рекурсии
Я пытаюсь использовать dfold, определенный здесь
dfold
:: KnownNat k
=> Proxy (p :: TyFun Nat * -> *)
-> (forall l. SNat l -> a -> (p @@ l) -> p @@ (l + 1))
-> (p @@ 0)
-> Vec k a
-> p @@ k
В основном это сводка, которая позволяет вам возвращать новый тип после каждого цикла.
Я пытаюсь обобщить bitonicSort, определенный в этом проекте:
https://github.com/adamwalker/clash-utils/blob/master/src/CLaSH/Sort.hs
Я две функции, важные для типов, которые dfold сгенерируют:
bitonicSort
:: forall n a. (KnownNat n, Ord a)
=> (Vec n a -> Vec n a) -- ^ The recursive step
-> (Vec (2 * n) a -> Vec (2 * n) a) -- ^ Merge step
-> Vec (2 * n) a -- ^ Input vector
-> Vec (2 * n) a -- ^ Output vector
bitonicMerge
:: forall n a. (Ord a , KnownNat n)
=> (Vec n a -> Vec n a) -- ^ The recursive step
-> Vec (2 * n) a -- ^ Input vector
-> Vec (2 * n) a -- ^ Output vector
Пример, использованный в вышеупомянутом проекте:
bitonicSorterExample
:: forall a. (Ord a)
=> Vec 16 a -- ^ Input vector
-> Vec 16 a -- ^ Sorted output vector
bitonicSorterExample = sort16
where
sort16 = bitonicSort sort8 merge16
merge16 = bitonicMerge merge8
sort8 = bitonicSort sort4 merge8
merge8 = bitonicMerge merge4
sort4 = bitonicSort sort2 merge4
merge4 = bitonicMerge merge2
sort2 = bitonicSort id merge2
merge2 = bitonicMerge id
Я пошел дальше и сделал более общую версию.
genBitonic :: (Ord a, KnownNat n) =>
(Vec n a -> Vec n a, Vec (2 * n) a -> Vec (2 * n) a)
-> (Vec (2 * n) a -> Vec (2 * n) a, Vec (4 * n) a -> Vec (4 * n) a)
genBitonic (bSort,bMerge) = (bitonicSort bSort bMerge, bitonicMerge bMerge)
bitonicBase :: Ord a => (Vec 1 a -> Vec 1 a, Vec 2 a -> Vec 2 a)
bitonicBase = (id, bitonicMerge id)
С этой версией я могу быстро создавать новые битонические сортировки, например:
bSort16 :: Ord a => Vec 16 a -> Vec 16 a
bSort16 = fst $ genBitonic $ genBitonic $ genBitonic $ genBitonic bitonicBase
bSort8 :: Ord a => Vec 8 a -> Vec 8 a
bSort8 = fst $ genBitonic $ genBitonic $ genBitonic bitonicBase
bSort4 :: Ord a => Vec 4 a -> Vec 4 a
bSort4 = fst $ genBitonic $ genBitonic bitonicBase
bSort2 :: Ord a => Vec 2 a -> Vec 2 a
bSort2 = fst $ genBitonic bitonicBase
Каждый Сортировка с работой с вектором указанного размера.
testVec16 :: Num a => Vec 16 a
testVec16 = 9 :> 2 :> 8 :> 6 :> 3 :> 7 :> 0 :> 1 :> 4 :> 5 :> 2 :> 8 :> 6 :> 3 :> 7 :> 0 :> Nil
testVec8 :: Num a => Vec 8 a
testVec8 = 9 :> 2 :> 8 :> 6 :> 3 :> 7 :> 0 :> 1 :> Nil
testVec4 :: Num a => Vec 4 a
testVec4 = 9 :> 2 :> 8 :> 6 :> Nil
testVec2 :: Num a => Vec 2 a
testVec2 = 2 :> 9 :> Nil
Быстрые примечания:
-
Я пытаюсь применить "genBitonic" к "bitonicBase" t раз.
-
Я использую CLaSH для синтеза этого в VHDL, поэтому я не могу использовать рекурсию для применения t раз
-
Мы всегда будем сортировать размер vec 2 ^ t в vec того же размера
-
"Vec n a" - это вектор размера n и тип a
Я хотел бы создать функцию, которая генерирует функцию для данного Vec. Я считаю, что использование dfold или dtfold - правильный путь здесь.
Я хотел сделать сложение с чем-то вроде функции genBitonic
.
Затем используйте fst
, чтобы получить нужную мне функцию для сортировки.
У меня было два возможных варианта:
Один. Сложите композицию, чтобы получить функцию, которая берет базу.
bSort8 :: Ord a => Vec 8 a -> Vec 8 a
bSort8 = fst $ genBitonic.genBitonic.genBitonic $ bitonicBase
До того, как база была ответила, это привело бы к чему-то вроде
**If composition was performed three times**
foo3 ::
(Ord a, KnownNat n) =>
(Vec n a -> Vec n a, Vec (2 * n) a -> Vec (2 * n) a)
-> (Vec (2 * (2 * (2 * n))) a -> Vec (2 * (2 * (2 * n))) a,
Vec (4 * (2 * (2 * n))) a -> Vec (4 * (2 * (2 * n))) a)
Два:
Вторая идея заключалась в том, чтобы использовать bitonicBase как значение b, чтобы начать накопление. Это привело бы непосредственно к форме, в которой я нуждаюсь, прежде чем применить fst
.
Edit
vecAcum
предназначен только для создания значения внутри dfold
.
В примере dfold они складываются с использованием :>
, который является просто векторной формой оператора списка :
>>> :t (:>)
(:>) :: a -> Vec n a -> Vec (n + 1) a
Что я хочу сделать, это взять кортеж из двух функций, таких как:
genBitonic :: (Ord a, KnownNat n) =>
(Vec n a -> Vec n a, Vec (2 * n) a -> Vec (2 * n) a)
-> (Vec (2 * n) a -> Vec (2 * n) a, Vec (4 * n) a -> Vec (4 * n) a)
И составите их.
Итак, genBitonic . genBitonic
будет иметь тип:
(Vec n a -> Vec n a, Vec (2 * n) a -> Vec (2 * n) a)
-> (Vec (2 * (2 * n)) a -> Vec (2 * (2 * n)) a, Vec (4 * (2 * n)) a -> Vec (4 * (2 * n)) a)
Итак, базовая функция будет тем, что затвердевает типы.
например.
bitonicBase :: Ord a => (Vec 1 a -> Vec 1 a, Vec 2 a -> Vec 2 a)
bitonicBase = (id, bitonicMerge id)
bSort4 :: Ord a => Vec 4 a -> Vec 4 a
bSort4 = fst $ genBitonic $ genBitonic bitonicBase
Я использую dfold для построения функции для векторов длины n, которая эквивалентна выполнению рекурсии на векторе длины n.
Я пробовал:
Я попытался выполнить пример, указанный в dfold
data SplitHalf (a :: *) (f :: TyFun Nat *) :: *
type instance Apply (SplitHalf a) l = (Vec (2^l) a -> Vec (2^l) a, Vec (2 ^ (l + 1)) a -> Vec (2 ^ (l + 1)) a)
generateBitonicSortN2 :: forall k a . (Ord a, KnownNat k) => SNat k -> Vec (2^k) a -> Vec (2^k) a
generateBitonicSortN2 k = fst $ dfold (Proxy :: Proxy (SplitHalf a)) vecAcum base vecMath
where
vecMath = operationList k
vecAcum :: (KnownNat l, KnownNat gl, Ord a) => SNat l
-> (SNat gl -> SplitHalf a @@ gl -> SplitHalf a @@ (gl+1))
-> SplitHalf a @@ l
-> SplitHalf a @@ (l+1)
vecAcum l0 f acc = undefined -- (f l0) acc
base :: (Ord a) => SplitHalf a @@ 0
base = (id,id)
general :: (KnownNat l, Ord a)
=> SNat l
-> SplitHalf a @@ l
-> SplitHalf a @@ (l+1)
general _ (x,y) = (bitonicSort x y, bitonicMerge y )
operationList :: (KnownNat k, KnownNat l, Ord a)
=> SNat k
-> Vec k
(SNat l
-> SplitHalf a @@ l
-> SplitHalf a @@ (l+1))
operationList k0 = replicate k0 general
Я использую расширения, которые использует исходный код dfold
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE Trustworthy #-}
Сообщения об ошибках:
Sort.hs:182:71: error:
* Could not deduce (KnownNat l) arising from a use of `vecAcum'
from the context: (Ord a, KnownNat k)
bound by the type signature for:
generateBitonicSortN2 :: (Ord a, KnownNat k) =>
SNat k -> Vec (2 ^ k) a -> Vec (2 ^ k) a
at Sort.hs:181:1-98
Possible fix:
add (KnownNat l) to the context of
a type expected by the context:
SNat l
-> (SNat l0
-> (Vec (2 ^ l0) a -> Vec (2 ^ l0) a,
Vec (2 ^ (l0 + 1)) a -> Vec (2 ^ (l0 + 1)) a)
-> (Vec (2 ^ (l0 + 1)) a -> Vec (2 ^ (l0 + 1)) a,
Vec (2 ^ ((l0 + 1) + 1)) a -> Vec (2 ^ ((l0 + 1) + 1)) a))
-> SplitHalf a @@ l
-> SplitHalf a @@ (l + 1)
* In the second argument of `dfold', namely `vecAcum'
In the second argument of `($)', namely
`dfold (Proxy :: Proxy (SplitHalf a)) vecAcum base vecMath'
In the expression:
fst $ dfold (Proxy :: Proxy (SplitHalf a)) vecAcum base vecMath
Sort.hs:182:84: error:
* Could not deduce (KnownNat l0) arising from a use of `vecMath'
from the context: (Ord a, KnownNat k)
bound by the type signature for:
generateBitonicSortN2 :: (Ord a, KnownNat k) =>
SNat k -> Vec (2 ^ k) a -> Vec (2 ^ k) a
at Sort.hs:181:1-98
The type variable `l0' is ambiguous
* In the fourth argument of `dfold', namely `vecMath'
In the second argument of `($)', namely
`dfold (Proxy :: Proxy (SplitHalf a)) vecAcum base vecMath'
In the expression:
fst $ dfold (Proxy :: Proxy (SplitHalf a)) vecAcum base vecMath
Failed, modules loaded: none.
** РЕДАКТИРОВАТЬ **
Добавлено гораздо больше деталей.
Ответы
Ответ 1
Ваш случай base
был неправильным; это должно быть
base :: (Ord a) => SplitHalf a @@ 0
base = (id, bitonicMerge id)
Объединяя все вместе, здесь полная рабочая версия, протестированная на GHC 8.0.2 (но она должна работать все равно на CLASH на основе GHC 8.0.2 по модулю Prelude
). Оказывается, вещь operationList
не используется, кроме ее позвоночника, поэтому мы можем использовать вместо нее Vec n ()
.
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
{-# LANGUAGE Rank2Types, ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies, TypeOperators, UndecidableInstances #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise -fplugin GHC.TypeLits.KnownNat.Solver #-}
{-# OPTIONS_GHC -fno-warn-incomplete-patterns -fno-warn-redundant-constraints #-}
{-# LANGUAGE NoImplicitPrelude #-}
import Prelude (Integer, (+), Num, ($), undefined, id, fst, Int, otherwise)
import CLaSH.Sized.Vector
import CLaSH.Promoted.Nat
import Data.Singletons
import GHC.TypeLits
import Data.Ord
type ExpVec k a = Vec (2 ^ k) a
data SplitHalf (a :: *) (f :: TyFun Nat *) :: *
type instance Apply (SplitHalf a) k = (ExpVec k a -> ExpVec k a, ExpVec (k + 1) a -> ExpVec (k + 1) a)
generateBitonicSortN2 :: forall k a . (Ord a, KnownNat k) => SNat k -> ExpVec k a -> ExpVec k a
generateBitonicSortN2 k = fst $ dfold (Proxy :: Proxy (SplitHalf a)) step base (replicate k ())
where
step :: SNat l -> () -> SplitHalf a @@ l -> SplitHalf a @@ (l+1)
step SNat _ (sort, merge) = (bitonicSort sort merge, bitonicMerge merge)
base = (id, bitonicMerge id)
Это работает как ожидалось, например:
*Main> generateBitonicSortN2 (snatProxy Proxy) testVec2
<9,2>
*Main> generateBitonicSortN2 (snatProxy Proxy) testVec4
<9,8,6,2>
*Main> generateBitonicSortN2 (snatProxy Proxy) testVec8
<9,8,7,6,3,2,1,0>
*Main> generateBitonicSortN2 (snatProxy Proxy) testVec16
<9,8,8,7,7,6,6,5,4,3,3,2,2,1,0,0>
*Main>
Ответ 2
Я использую CLaSH для синтеза этого в VHDL, поэтому я не могу использовать рекурсию для применения t раз
Я не понимаю этого предложения, но кроме этого:
{-# LANGUAGE GADTs, DataKinds, TypeFamilies, UndecidableInstances,
FlexibleInstances, FlexibleContexts, ConstraintKinds,
UndecidableSuperClasses, TypeOperators #-}
import GHC.TypeLits
import GHC.Exts (Constraint)
import Data.Proxy
data Peano = Z | S Peano
data SPeano n where
SZ :: SPeano Z
SS :: SPeano n -> SPeano (S n)
type family PowerOfTwo p where
PowerOfTwo Z = 1
PowerOfTwo (S p) = 2 * PowerOfTwo p
type family KnownPowersOfTwo p :: Constraint where
KnownPowersOfTwo Z = ()
KnownPowersOfTwo (S p) = (KnownNat (PowerOfTwo p), KnownPowersOfTwo p)
data Vec (n :: Nat) a -- abstract
type OnVec n a = Vec n a -> Vec n a
type GenBitonic n a = (OnVec n a, OnVec (2 * n) a)
genBitonic :: (Ord a, KnownNat n) => GenBitonic n a -> GenBitonic (2 * n) a
genBitonic = undefined
bitonicBase :: Ord a => GenBitonic 1 a
bitonicBase = undefined
genBitonicN :: (Ord a, KnownPowersOfTwo p) => SPeano p -> GenBitonic (PowerOfTwo p) a
genBitonicN SZ = bitonicBase
genBitonicN (SS p) = genBitonic (genBitonicN p)
genBitonicN
определяется рекурсией по числу peano, представляющему степень. На каждом рекурсивном шаге появляется новый KnownNat (PowerOfTwo p)
(через семейство типов KnownPowersOfTwo
). На уровне значения genBitonicN
тривиально и просто делает то, что вы хотите. Однако для определения удобного bSortN
:
нам нужны дополнительные механизмы,
type family Lit n where
Lit 0 = Z
Lit n = S (Lit (n - 1))
class IPeano n where
speano :: SPeano n
instance IPeano Z where
speano = SZ
instance IPeano n => IPeano (S n) where
speano = SS speano
class (n ~ PowerOfTwo (PowerOf n), KnownPowersOfTwo (PowerOf n)) =>
IsPowerOfTwo n where
type PowerOf n :: Peano
getPower :: SPeano (PowerOf n)
instance IsPowerOfTwo 1 where
type PowerOf 1 = Lit 0
getPower = speano
instance IsPowerOfTwo 2 where
type PowerOf 2 = Lit 1
getPower = speano
instance IsPowerOfTwo 4 where
type PowerOf 4 = Lit 2
getPower = speano
instance IsPowerOfTwo 8 where
type PowerOf 8 = Lit 3
getPower = speano
instance IsPowerOfTwo 16 where
type PowerOf 16 = Lit 4
getPower = speano
-- more powers go here
bSortN :: (IsPowerOfTwo n, Ord a) => OnVec n a
bSortN = fst $ genBitonicN getPower
Вот несколько примеров:
bSort1 :: Ord a => OnVec 1 a
bSort1 = bSortN
bSort2 :: Ord a => OnVec 2 a
bSort2 = bSortN
bSort4 :: Ord a => OnVec 4 a
bSort4 = bSortN
bSort8 :: Ord a => OnVec 8 a
bSort8 = bSortN
bSort16 :: Ord a => OnVec 16 a
bSort16 = bSortN
Я не знаю, можем ли мы избежать определения IsPowerOfTwo
для каждой степени из двух.
Обновление: вот еще один вариант bSortN
:
pnatToSPeano :: IPeano (Lit n) => proxy n -> SPeano (Lit n)
pnatToSPeano _ = speano
bSortNP :: (IPeano (Lit p), KnownPowersOfTwo (Lit p), Ord a)
=> proxy p -> OnVec (PowerOfTwo (Lit p)) a
bSortNP = fst . genBitonicN . pnatToSPeano
Пример:
bSort16 :: Ord a => OnVec 16 a
bSort16 = bSortNP (Proxy :: Proxy 4)