Pisanie cojoin lub cobind dla typu siatki n-wymiarowej
Używając typowej definicji naturalności na poziomie typu, zdefiniowałem siatkę n-wymiarową.
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
data Nat = Z | S Nat
data U (n :: Nat) x where
Point :: x -> U Z x
Dimension :: [U n x] -> U n x -> [U n x] -> U (S n) x
dmap :: (U n x -> U m r) -> U (S n) x -> U (S m) r
dmap f (Dimension ls mid rs) = Dimension (map f ls) (f mid) (map f rs)
instance Functor (U n) where
fmap f (Point x) = Point (f x)
fmap f d@Dimension{} = dmap (fmap f) d
Teraz chcę zrobić z tego przykład Comonad, ale nie mogę tego ogarnąć.
class Functor w => Comonad w where
(=>>) :: w a -> (w a -> b) -> w b
coreturn :: w a -> a
cojoin :: w a -> w (w a)
x =>> f = fmap f (cojoin x)
cojoin xx = xx =>> id
instance Comonad (U n) where
coreturn (Point x) = x
coreturn (Dimension _ mid _) = coreturn mid
-- cojoin :: U Z x -> U Z (U Z x)
cojoin (Point x) = Point (Point x)
-- cojoin ::U (S n) x -> U (S n) (U (S n) x)
cojoin d@Dimension{} = undefined
-- =>> :: U Z x -> (U Z x -> r) -> U Z r
p@Point{} =>> f = Point (f p)
-- =>> :: U (S n) x -> (U (S n) x -> r) -> U (S n) r
d@Dimension{} =>> f = undefined
Użycie cojoin
na siatce n-wymiarowej wytworzy siatkę n-wymiarową siatek N-wymiarowych. Chciałbym podać instancję o takim samym pomyśle jak ta , czyli że wartość z współjoinowanej siatki na (x,y,z) powinna być original grid focused on (x,y,z). Aby dostosować ten kod, wydaje się, że musimy reify n
w celu wykonania n
" fmaps "i n
"rolls". Nie musisz tego robić w ten sposób, ale jeśli to pomoże, to proszę bardzo.
3 answers
Jagger / Richards: nie zawsze możesz dostać to, czego chcesz, ale jeśli kiedyś spróbujesz, może się okazać, że dostaniesz to, czego potrzebujesz.
Kursory na listach
Pozwól mi przebudować komponenty Twojej struktury za pomocą snoc-i cons-list, aby zachować właściwości przestrzenne jasne. Definiuję
data Bwd x = B0 | Bwd x :< x deriving (Functor, Foldable, Traversable, Show)
data Fwd x = F0 | x :> Fwd x deriving (Functor, Foldable, Traversable, Show)
infixl 5 :<
infixr 5 :>
data Cursor x = Cur (Bwd x) x (Fwd x) deriving (Functor, Foldable, Traversable, Show)
Let ' s have comonads
class Functor f => Comonad f where
counit :: f x -> x
cojoin :: f x -> f (f x)
I upewnijmy się, że kursory są komonadami
instance Comonad Cursor where
counit (Cur _ x _) = x
cojoin c = Cur (lefts c) c (rights c) where
lefts (Cur B0 _ _) = B0
lefts (Cur (xz :< x) y ys) = lefts c :< c where c = Cur xz x (y :> ys)
rights (Cur _ _ F0) = F0
rights (Cur xz x (y :> ys)) = c :> rights c where c = Cur (xz :< x) y ys
Jeśli jesteś włączony do tego rodzaju rzeczy, można zauważyć, że Cursor
jest przestrzennie przyjemnym wariantem InContext []
InContext f x = (x, ∂f x)
Gdzie ∂ przyjmuje formalną pochodną funktora, dając jego pojęcie kontekstu jednowierszowego. InContext f
jest zawsze Comonad
, Jak wspomniano w ta odpowiedź , a to, co mamy tutaj, to po prostu Comonad
indukowane przez strukturę różniczkową, gdzie {28]} wydobywa element w centrum uwagi i {29]} dekoruje każdy element swoim własnym kontekstem, skutecznie dając kontekst pełen zmienionych kursorów i z niewzruszonym kursorem na jego miejscu. skup się. Weźmy przykład.
> cojoin (Cur (B0 :< 1) 2 (3 :> 4 :> F0))
Cur (B0 :< Cur B0 1 (2 :> 3 :> 4 :> F0))
(Cur (B0 :< 1) 2 (3 :> 4 :> F0))
( Cur (B0 :< 1 :< 2) 3 (4 :> F0)
:> Cur (B0 :< 1 :< 2 :< 3) 4 F0
:> F0)
Widzisz? Po lewej stronie mamy listę kursora-at-1, po prawej listę kursora-at-3 i kursora-at-4.
Komponowanie Kursorów, Transponowanie Kursorów?
Struktura, o którą prosisz, to n-składowa Cursor
. Let ' s have
newtype (:.:) f g x = C {unC :: f (g x)} deriving Show
Aby przekonać komonady f
i g
do komponowania, counit
s komponują starannie, ale ty potrzeba "prawa dystrybucyjnego"
transpose :: f (g x) -> g (f x)
Więc możesz zrobić kompozyt cojoin
w ten sposób
f (g x)
-(fmap cojoin)->
f (g (g x))
-cojoin->
f (f (g (g x)))
-(fmap transpose)->
f (g (f (g x)))
Jakie prawa powinny spełniać? Prawdopodobnie coś w stylu
counit . transpose = fmap counit
cojoin . transpose = fmap transpose . transpose . fmap cojoin
Lub cokolwiek innego, aby upewnić się, że dowolne dwa sposoby shootgle pewnej sekwencji f I g z jednego rozkazu do drugiego dają ten sam wynik.
Czy możemy zdefiniować transpose
dla Cursor
z samym sobą? Jednym ze sposobów na tanią transpozycję jest zwrócenie uwagi, że Bwd
i Fwd
są zippily aplikacyjny, stąd tak jest Cursor
.
instance Applicative Bwd where
pure x = pure x :< x
(fz :< f) <*> (sz :< s) = (fz <*> sz) :< f s
_ <*> _ = B0
instance Applicative Fwd where
pure x = x :> pure x
(f :> fs) <*> (s :> ss) = f s :> (fs <*> ss)
_ <*> _ = F0
instance Applicative Cursor where
pure x = Cur (pure x) x (pure x)
Cur fz f fs <*> Cur sz s ss = Cur (fz <*> sz) (f s) (fs <*> ss)
I tutaj powinieneś zacząć wąchać szczura. Niedopasowanie kształtu powoduje obcięcie , A to złamie oczywiście pożądaną właściwość, że SELF-transpose jest SELF-inverse. Każdy rodzaj szarpaniny nie przetrwa. Otrzymujemy operator transpozycyjny: sequenceA
, A Dla całkowicie regularnych danych, wszystko jest jasne i piękne.
> regularMatrixCursor
Cur (B0 :< Cur (B0 :< 1) 2 (3 :> F0))
(Cur (B0 :< 4) 5 (6 :> F0))
(Cur (B0 :< 7) 8 (9 :> F0) :> F0)
> sequenceA regularMatrixCursor
Cur (B0 :< Cur (B0 :< 1) 4 (7 :> F0))
(Cur (B0 :< 2) 5 (8 :> F0))
(Cur (B0 :< 3) 6 (9 :> F0) :> F0)
Ale nawet jeśli po prostu przesunę jeden z wewnętrznych kursorów poza wyrównanie (nieważne co rozmiary poszarpane), wszystko idzie nie tak.
> raggedyMatrixCursor
Cur (B0 :< Cur ((B0 :< 1) :< 2) 3 F0)
(Cur (B0 :< 4) 5 (6 :> F0))
(Cur (B0 :< 7) 8 (9 :> F0) :> F0)
> sequenceA raggedyMatrixCursor
Cur (B0 :< Cur (B0 :< 2) 4 (7 :> F0))
(Cur (B0 :< 3) 5 (8 :> F0))
F0
Gdy masz jedną zewnętrzną pozycję kursora i wiele wewnętrznych pozycji kursora, nie ma transpozycji, która zachowałaby się dobrze. Samo-komponowanie Cursor
pozwala na postrzępienie wewnętrznych struktur względem siebie, więc nie transpose
, Nie cojoin
. Możesz, a ja zdefiniowałem
instance (Comonad f, Traversable f, Comonad g, Applicative g) =>
Comonad (f :.: g) where
counit = counit . counit . unC
cojoin = C . fmap (fmap C . sequenceA) . cojoin . fmap cojoin . unC
Ale naszym obowiązkiem jest dbanie o regularność wewnętrznych struktur. Jeśli jesteś gotów przyjąć ten ciężar, to możesz to zrobić, ponieważ Applicative
i Traversable
są łatwo zamknięte pod kompozycją. Tutaj są bity i kawałki
instance (Functor f, Functor g) => Functor (f :.: g) where
fmap h (C fgx) = C (fmap (fmap h) fgx)
instance (Applicative f, Applicative g) => Applicative (f :.: g) where
pure = C . pure . pure
C f <*> C s = C (pure (<*>) <*> f <*> s)
instance (Functor f, Foldable f, Foldable g) => Foldable (f :.: g) where
fold = fold . fmap fold . unC
instance (Traversable f, Traversable g) => Traversable (f :.: g) where
traverse h (C fgx) = C <$> traverse (traverse h) fgx
Edit: dla kompletności, oto co robi, gdy wszystko jest regularne,
> cojoin (C regularMatrixCursor)
C {unC = Cur (B0 :< Cur (B0 :<
C {unC = Cur B0 (Cur B0 1 (2 :> (3 :> F0))) (Cur B0 4 (5 :> (6 :> F0)) :> (Cur B0 7 (8 :> (9 :> F0)) :> F0))})
(C {unC = Cur B0 (Cur (B0 :< 1) 2 (3 :> F0)) (Cur (B0 :< 4) 5 (6 :> F0) :> (Cur (B0 :< 7) 8 (9 :> F0) :> F0))})
(C {unC = Cur B0 (Cur ((B0 :< 1) :< 2) 3 F0) (Cur ((B0 :< 4) :< 5) 6 F0 :> (Cur ((B0 :< 7) :< 8) 9 F0 :> F0))} :> F0))
(Cur (B0 :<
C {unC = Cur (B0 :< Cur B0 1 (2 :> (3 :> F0))) (Cur B0 4 (5 :> (6 :> F0))) (Cur B0 7 (8 :> (9 :> F0)) :> F0)})
(C {unC = Cur (B0 :< Cur (B0 :< 1) 2 (3 :> F0)) (Cur (B0 :< 4) 5 (6 :> F0)) (Cur (B0 :< 7) 8 (9 :> F0) :> F0)})
(C {unC = Cur (B0 :< Cur ((B0 :< 1) :< 2) 3 F0) (Cur ((B0 :< 4) :< 5) 6 F0) (Cur ((B0 :< 7) :< 8) 9 F0 :> F0)} :> F0))
(Cur (B0 :<
C {unC = Cur ((B0 :< Cur B0 1 (2 :> (3 :> F0))) :< Cur B0 4 (5 :> (6 :> F0))) (Cur B0 7 (8 :> (9 :> F0))) F0})
(C {unC = Cur ((B0 :< Cur (B0 :< 1) 2 (3 :> F0)) :< Cur (B0 :< 4) 5 (6 :> F0)) (Cur (B0 :< 7) 8 (9 :> F0)) F0})
(C {unC = Cur ((B0 :< Cur ((B0 :< 1) :< 2) 3 F0) :< Cur ((B0 :< 4) :< 5) 6 F0) (Cur ((B0 :< 7) :< 8) 9 F0) F0} :> F0)
:> F0)}
Iloczyn tensorowy Hancocka
Dla regularności, potrzebujesz czegoś mocniejszego niż kompozycja. Musisz być w stanie uchwycić pojęcie "f-struktura g-struktury-wszystko-ten-sam-kształt". To jest to, co nieocenionym Peter Hancock nazywa "iloczyn tensorowy" , który napiszę f :><: g
: istnieje jeden "zewnętrzny" kształt f i jeden "wewnętrzny" kształt g wspólny dla wszystkich wewnętrznych struktur g, więc transpozycja jest łatwo definiowalna i zawsze odwrotna do siebie. Tensor Hancocka nie jest wygodnie definiowalny w Haskell, ale w zależności od typowania łatwo jest sformułować pojęcie "kontenera", który ma ten tensor.
Aby dać ci pomysł, rozważ zdegenerowane pojęcie kontenera
data (:<|) s p x = s :<| (p -> x)
Gdzie mówimy s
jest typem "kształtów" i p
typem "pozycji". A wartość składa się z wyboru kształtu i przechowywania x
w każdej pozycji. W zależności od przypadku, rodzaj pozycji może zależeć od wyboru kształtu (np. dla list, kształt jest liczbą (długość), i masz tyle pozycji). Pojemniki te mają iloczyn tensorowy
(s :<| p) :><: (s' :<| p') = (s, s') :<| (p, p')
Która jest jak macierz uogólniona: para kształtów daje wymiary, a następnie masz element w każdej parze pozycji. Możesz to zrobić doskonale, gdy typy p
i p'
zależą od wartości w s
i s'
i to jest dokładnie definicja iloczynu tensorowego kontenerów.
InContext dla iloczynów Tensorowych
Teraz, jak być może nauczyłeś się w liceum, ∂(s :<| p) = (s, p) :<| (p-1)
gdzie p-1
jest typem z jednym pierwiastkiem mniej niż p
. Jak ∂(S x^p) = (sp) * x^(p-1). Zaznaczasz jedną pozycję (zapisujesz ją w kształcie) i usuwasz. Problem polega na tym, że p-1
jest trudny do zdobycia bez zależnych typów. Ale InContext
wybiera pozycję bez jej usuwania .
InContext (s :<| p) ~= (s, p) :<| p
To działa równie dobrze w przypadku zależnym, a my radośnie zdobywamy]}
InContext (f :><: g) ~= InContext f :><: InContext g
Teraz wiemy, że InContext f
jest zawsze Comonad
, a to nam mówi, że iloczyn tensorowy InContext
s są komonadyczne, ponieważ same są {60]}s. to znaczy, wybieramy jedną pozycję na wymiar (a to daje dokładnie jedną pozycję w całej rzeczy), gdzie wcześniej mieliśmy jedną pozycję zewnętrzną i wiele pozycji wewnętrznych. Z produkt tensor zastępujący kompozycję, wszystko działa słodko.
Naperian Functors
Ale istnieje podklasa Functor
, dla której iloczyn tensorowy i skład są zbieżne. Są to Functor
s f
, dla których f () ~ ()
: tzn. i tak jest tylko jeden kształt, więc w kompozycjach nie ma wartości. Te Functor
S są izomorficzne do (p ->)
dla pewnego zbioru pozycji p
, który możemy traktować jako logarytm (wykładnik do którego należy podnieść x
, aby dać f x
). Hancock nazywa tych [74] funktorami po Johnie Napierze (którego duch nawiedza część Edynburga, w której mieszka Hancock).
class Applicative f => Naperian f where
type Log f
project :: f x -> Log f -> x
positions :: f (Log f)
--- project positions = id
A Naperian
funktor ma logarytm, indukujący project
funkcję jonową odwzorowującą pozycje na Znalezione tam pierwiastki. Naperian
funktory są wszystkie zippily Applicative
, Z pure
i <*>
odpowiadającymi kombinatorom K I S dla projekcji. Możliwe jest również skonstruowanie wartości, gdzie przy każdym pozycja jest przechowywana, że właśnie reprezentacji pozycji. Prawa logarytmów, które możesz zapamiętać, pojawiają się przyjemnie.
newtype Id x = Id {unId :: x} deriving Show
instance Naperian Id where
type Log Id = ()
project (Id x) () = x
positions = Id ()
newtype (:*:) f g x = Pr (f x, g x) deriving Show
instance (Naperian f, Naperian g) => Naperian (f :*: g) where
type Log (f :*: g) = Either (Log f) (Log g)
project (Pr (fx, gx)) (Left p) = project fx p
project (Pr (fx, gx)) (Right p) = project gx p
positions = Pr (fmap Left positions, fmap Right positions)
Zauważ, że tablica o stałej wielkości (awektor ) jest dana przez (Id :*: Id :*: ... :*: Id :*: One)
, gdzie One
jest funkcją jednostki stałej, której logarytm wynosi Void
. Więc tablica to Naperian
. Teraz mamy również
instance (Naperian f, Naperian g) => Naperian (f :.: g) where
type Log (f :.: g) = (Log f, Log g)
project (C fgx) (p, q) = project (project fgx p) q
positions = C $ fmap (\ p -> fmap (p ,) positions) positions
Co oznacza, że tablice wielowymiarowe są Naperian
.
Aby skonstruować wersję InContext f
dla Naperian f
, wystarczy wskazać pozycji.
data Focused f x = f x :@ Log f
instance Functor f => Functor (Focused f) where
fmap h (fx :@ p) = fmap h fx :@ p
instance Naperian f => Comonad (Focused f) where
counit (fx :@ p) = project fx p
cojoin (fx :@ p) = fmap (fx :@) positions :@ p
Więc, w szczególności, Focused
N-wymiarowa tablica będzie rzeczywiście komonad. Zbiór wektorów jest iloczynem tensorowym n wektorów, ponieważ wektory są Naperian
. Ale Focused
N-wymiarowa tablica będzie N-krotnym iloczynem tensorowym, nie składem N Focused
wektorów, które wyznaczają jego wymiary. Aby wyrazić to comonad w kategoriach iloczynu tensorowego, musimy wyrazić je w postaci umożliwiającej skonstruowanie iloczynu tensorowego. Ja zostaw to jako ćwiczenie na przyszłość.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:24:58
[[18]}Jeszcze jedna próba, zainspirowana postem pigworkerów i http://hackage.haskell.org/packages/archive/representable-functors/3.0.0.1/doc/html/Data-Functor-Representable.html.
Reprezentowalny funktor (lub Naperian) jest comonadem, jeśli klucz (lub log) jest monoidem! Następnie coreturn
pobiera wartość na pozycji mempty
. Oraz cojoin
mappend
s dwa dostępne klucze. (Podobnie jak instancja comonad dla (p ->)
.)
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
import Data.List (genericIndex)
import Data.Monoid
import Data.Key
import Data.Functor.Representable
data Nat = Z | S Nat
data U (n :: Nat) x where
Point :: x -> U Z x
Dimension :: [U n x] -> U n x -> [U n x] -> U (S n) x
dmap :: (U n x -> U m r) -> U (S n) x -> U (S m) r
dmap f (Dimension ls mid rs) = Dimension (map f ls) (f mid) (map f rs)
instance Functor (U n) where
fmap f (Point x) = Point (f x)
fmap f d@Dimension{} = dmap (fmap f) d
class Functor w => Comonad w where
(=>>) :: w a -> (w a -> b) -> w b
coreturn :: w a -> a
cojoin :: w a -> w (w a)
x =>> f = fmap f (cojoin x)
cojoin xx = xx =>> id
U
jest reprezentowalna, jeśli listy są nieskończone długa. Więc jest tylko jeden kształt. Klucz U n
jest wektorem n liczb całkowitych.
type instance Key (U n) = UKey n
data UKey (n :: Nat) where
P :: UKey Z
D :: Integer -> UKey n -> UKey (S n)
instance Lookup (U n) where lookup = lookupDefault
instance Indexable (U n) where
index (Point x) P = x
index (Dimension ls mid rs) (D i k)
| i < 0 = index (ls `genericIndex` (-i - 1)) k
| i > 0 = index (rs `genericIndex` ( i - 1)) k
| otherwise = index mid k
Musimy podzielić instancję Representable
na dwa przypadki, jeden dla Z
i jeden dla S
, ponieważ nie mamy wartości typu U n
do dopasowania wzorca.
instance Representable (U Z) where
tabulate f = Point (f P)
instance Representable (U n) => Representable (U (S n)) where
tabulate f = Dimension
(map (\i -> tabulate (f . D (-i))) [1..])
(tabulate (f . D 0))
(map (\i -> tabulate (f . D i)) [1..])
instance Monoid (UKey Z) where
mempty = P
mappend P P = P
instance Monoid (UKey n) => Monoid (UKey (S n)) where
mempty = D 0 mempty
mappend (D il kl) (D ir kr) = D (il + ir) (mappend kl kr)
I klucz U n
jest rzeczywiście monoidem, więc możemy przekształcić U n
w comonad, używając domyślnych implementacji z pakietu representable-functor.
instance (Monoid (UKey n), Representable (U n)) => Comonad (U n) where
coreturn = extractRep
cojoin = duplicateRep
(=>>) = flip extendRep
Tym razem zrobiłem kilka testów.
testVal :: U (S (S Z)) Int
testVal = Dimension
(repeat (Dimension (repeat (Point 1)) (Point 2) (repeat (Point 3))))
(Dimension (repeat (Point 4)) (Point 5) (repeat (Point 6)))
(repeat (Dimension (repeat (Point 7)) (Point 8) (repeat (Point 9))))
-- Hacky Eq instance, just for testing
instance Eq x => Eq (U n x) where
Point a == Point b = a == b
Dimension la a ra == Dimension lb b rb = take 3 la == take 3 lb && a == b && take 3 ra == take 3 rb
instance Show x => Show (U n x) where
show (Point x) = "(Point " ++ show x ++ ")"
show (Dimension l a r) = "(Dimension " ++ show (take 2 l) ++ " " ++ show a ++ " " ++ show (take 2 r) ++ ")"
test =
coreturn (cojoin testVal) == testVal &&
fmap coreturn (cojoin testVal) == testVal &&
cojoin (cojoin testVal) == fmap cojoin (cojoin testVal)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-27 16:44:15
Więc to okazuje się być złe. Zostawię to tutaj na wypadek, gdyby ktoś chciał spróbować to naprawić.
Ta implementacja jest tak, jak sugerował mi @pigworker. Kompiluje, ale nie testowałem go. (Wziąłem implementację cojoin1
z http://blog.sigfpe.com/2006/12/evaluating-cellular-automata-is.html )
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
data Nat = Z | S Nat
data U (n :: Nat) x where
Point :: x -> U Z x
Dimension :: [U n x] -> U n x -> [U n x] -> U (S n) x
unPoint :: U Z x -> x
unPoint (Point x) = x
dmap :: (U n x -> U m r) -> U (S n) x -> U (S m) r
dmap f (Dimension ls mid rs) = Dimension (map f ls) (f mid) (map f rs)
right, left :: U (S n) x -> U (S n) x
right (Dimension a b (c:cs)) = Dimension (b:a) c cs
left (Dimension (a:as) b c) = Dimension as a (b:c)
instance Functor (U n) where
fmap f (Point x) = Point (f x)
fmap f d@Dimension{} = dmap (fmap f) d
class Functor w => Comonad w where
(=>>) :: w a -> (w a -> b) -> w b
coreturn :: w a -> a
cojoin :: w a -> w (w a)
x =>> f = fmap f (cojoin x)
cojoin xx = xx =>> id
instance Comonad (U n) where
coreturn (Point x) = x
coreturn (Dimension _ mid _) = coreturn mid
cojoin (Point x) = Point (Point x)
cojoin d@Dimension{} = fmap unlayer . unlayer . fmap dist . cojoin1 . fmap cojoin . layer $ d
dist :: U (S Z) (U n x) -> U n (U (S Z) x)
dist = layerUnder . unlayer
layerUnder :: U (S n) x -> U n (U (S Z) x)
layerUnder d@(Dimension _ Point{} _) = Point d
layerUnder d@(Dimension _ Dimension{} _) = dmap layerUnder d
unlayer :: U (S Z) (U n x) -> U (S n) x
unlayer = dmap unPoint
layer :: U (S n) x -> U (S Z) (U n x)
layer = dmap Point
cojoin1 :: U (S Z) x -> U (S Z) (U (S Z) x)
cojoin1 a = layer $ Dimension (tail $ iterate left a) a (tail $ iterate right a)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-27 15:55:40