Commit 88d49807 authored by Sven Keidel's avatar Sven Keidel

move bottom element into `Fix` arrow

parent 34e3e4b3
......@@ -36,7 +36,7 @@ import Text.Printf
-- address has an binding in the store.
newtype Environment var addr val c x y = Environment ( Reader (Env var addr,Store addr val) c x y )
runEnvironment :: (Show var, Identifiable var, Identifiable addr, Complete val, ArrowChoice c, ArrowFail String c, ArrowAlloc var addr val c,LowerBounded (c () val))
runEnvironment :: (Show var, Identifiable var, Identifiable addr, Complete val, ArrowChoice c, ArrowFail String c, ArrowAlloc var addr val c)
=> Environment var addr val c x y -> c ([(var,val)],x) y
runEnvironment f =
let Environment (Reader f') = proc (bs,x) -> do
......@@ -48,13 +48,12 @@ runEnvironment f =
instance ArrowLift (Environment var addr val) where
lift f = Environment (lift f)
instance (Show var, Identifiable var, Identifiable addr, Complete val, ArrowChoice c, ArrowFail String c, ArrowAlloc var addr val c, LowerBounded (c () val)) =>
instance (Show var, Identifiable var, Identifiable addr, Complete val, ArrowChoice c, ArrowFail String c, ArrowAlloc var addr val c) =>
ArrowEnv var val (Env var addr,Store addr val) (Environment var addr val c) where
lookup = Environment $ Reader $ proc ((env,store),x) -> do
case do {addr <- E.lookup x env; S.lookup addr store} of
Success v -> returnA -< v
Fail _ -> failA -< printf "Variable %s not bound" (show x)
Bot -> bottom -< ()
getEnv = Environment askA
extendEnv = proc (x,y,(env,store)) -> do
addr <- lift alloc -< (x,env,store)
......
......@@ -33,12 +33,11 @@ newtype Environment var val c x y = Environment (Reader (Env var val) c x y)
runEnvironment :: (Arrow c, Eq var, Hashable var) => Environment var val c x y -> c ([(var,val)],x) y
runEnvironment (Environment (Reader f)) = first E.fromList ^>> f
instance (Show var, Identifiable var, ArrowChoice c, ArrowFail String c, LowerBounded (c () val)) => ArrowEnv var val (Env var val) (Environment var val c) where
instance (Show var, Identifiable var, ArrowChoice c, ArrowFail String c) => ArrowEnv var val (Env var val) (Environment var val c) where
lookup = Environment $ Reader $ proc (env,x) -> do
case E.lookup x env of
Success y -> returnA -< y
Fail _ -> failA -< printf "Variable %s not bound" (show x)
Bot -> bottom -< ()
getEnv = Environment askA
extendEnv = arr $ \(x,y,env) -> E.insert x y env
localEnv (Environment f) = Environment (localA f)
......
......@@ -19,7 +19,7 @@ import Control.Arrow.State
import Data.Abstract.Error
import Data.Order
import Data.Utils
import Data.Monoidal
newtype Except e c x y = Except { runExcept :: c x (Error e y) }
......@@ -31,7 +31,6 @@ instance ArrowChoice c => Category (Except r c) where
Except f . Except g = Except $ proc x -> do
ey <- g -< x
case ey of
Bot -> returnA -< Bot
Fail e -> returnA -< Fail e
Success y -> f -< y
......@@ -41,8 +40,9 @@ instance ArrowChoice c => Arrow (Except r c) where
second (Except f) = Except $ second f >>^ strength2
instance ArrowChoice c => ArrowChoice (Except r c) where
left (Except f) = Except $ left f >>^ costrength1
right (Except f) = Except $ right f >>^ costrength2
left (Except f) = Except $ left f >>^ strength1
right (Except f) = Except $ right f >>^ strength2
Except f ||| Except g = Except (f ||| g)
instance (ArrowChoice c, ArrowApply c) => ArrowApply (Except e c) where
app = Except $ first runExcept ^>> app
......
......@@ -15,12 +15,13 @@ import Data.Function (fix)
import Control.Arrow
import Control.Arrow.Fix
import Control.Arrow.Utils
import Control.Category
import Data.Abstract.Terminating
import Data.Order
import Data.Identifiable
import Data.Monoidal
import Data.Abstract.Error
import Data.Abstract.Store (Store)
import qualified Data.Abstract.Store as S
......@@ -33,31 +34,39 @@ import Text.Printf
-- The main idea of this fixpoint caching algorithm is due to David Darais et. al., Abstract Definitional Interpreters (Functional Pearl), ICFP' 17
-- We made some changes to the algorithm to simplify it.
newtype Fix a b x y = Fix (((Store a b,Store a b),x) -> (Store a b,y))
newtype Fix a b x y = Fix (((Store a (Terminating b), Store a (Terminating b)),x) -> (Store a (Terminating b), Terminating y))
runFix :: Fix a b x y -> (x -> y)
runFix :: Fix a b x y -> (x -> Terminating y)
runFix f = runFix' f >>^ snd
runFix' :: Fix a b x y -> (x -> (Store a b,y))
runFix' :: Fix a b x y -> (x -> (Store a (Terminating b), Terminating y))
runFix' (Fix f) = (\x -> ((S.empty,S.empty),x)) ^>> f
liftFix :: (x -> y) -> Fix a b x y
liftFix f = Fix ((\((_,o),x) -> (o,x)) ^>> second f)
liftFix f = Fix ((\((_,o),x) -> (o,x)) ^>> second (f ^>> Terminating))
instance Category (Fix i o) where
id = liftFix id
Fix f . Fix g = Fix $ proc ((i,o),x) -> do
(o',y) <- g -< ((i,o),x)
f -< ((i,o'),y)
case y of
NonTerminating -> returnA -< (o,NonTerminating)
Terminating y' -> f -< ((i,o'),y')
instance Arrow (Fix i o) where
arr f = liftFix (arr f)
first (Fix f) = Fix $ (\((i,o),(x,y)) -> (((i,o),x),y)) ^>> first f >>^ (\((o,x'),y) -> (o,(x',y)))
second (Fix f) = Fix $ (\((i,o),(x,y)) -> (x,((i,o),y))) ^>> second f >>^ (\(x,(o,y')) -> (o,(x,y')))
first (Fix f) = Fix $ to assoc ^>> first f >>^ (\((o,x'),y) -> (o,strength1 (x',y)))
instance ArrowChoice (Fix i o) where
left (Fix f) = Fix $ (\((i,o),e) -> injectRight (o,injectLeft ((i,o),e))) ^>> left f >>^ eject
right (Fix f) = Fix $ (\((i,o),e) -> injectRight ((i,o),injectLeft (o,e))) ^>> right f >>^ eject
left (Fix f) = Fix $ \((i,o),e) -> case e of
Left x -> second (fmap Left) (f ((i,o),x))
Right y -> (o,return (Right y))
right (Fix f) = Fix $ \((i,o),e) -> case e of
Left x -> (o,return (Left x))
Right y -> second (fmap Right) (f ((i,o),y))
Fix f ||| Fix g = Fix $ \((i,o),e) -> case e of
Left x -> f ((i,o),x)
Right y -> g ((i,o),y)
instance ArrowApply (Fix i o) where
app = Fix $ (\(io,(Fix f,x)) -> (f,(io,x))) ^>> app
......@@ -90,11 +99,8 @@ memoize f = proc x -> do
returnA -< trace (printf "\t%s <- f -< %s\n" (show y) (show x) ++
printf "\tout(%s) := %s ▽ %s = %s\n" (show x) (show yCached) (show y) (show yNew) ++
printf "\t%s <- memoize -< %s" (show y) (show x)) y
Bot -> bottom -< ()
#else
instance (Identifiable x, LowerBounded y, Widening y)
instance (Identifiable x, Widening y)
=> ArrowFix x y (Fix x y) where
fixA f = proc x -> do
old <- getOutCache -< ()
......@@ -105,7 +111,7 @@ instance (Identifiable x, LowerBounded y, Widening y)
then returnA -< y
else fixA f -< x
memoize :: (Identifiable x, LowerBounded y, Widening y) => Fix x y x y -> Fix x y x y
memoize :: (Identifiable x, Widening y) => Fix x y x y -> Fix x y x y
memoize f = proc x -> do
m <- lookupOutCache -< x
case m of
......@@ -113,36 +119,41 @@ memoize f = proc x -> do
returnA -< y
Fail _ -> do
yOld <- lookupInCache -< x
writeOutCache -< (x, fromError bottom yOld)
y <- f -< x
writeOutCache -< (x, yOld)
y <- catch f -< x
updateOutCache -< (x, y)
returnA -< y
Bot -> bottom -< ()
throw -< y
where
catch :: Fix x y a b -> Fix x y a (Terminating b)
catch (Fix g) = Fix (g >>^ second Terminating)
throw :: Fix x y (Terminating a) a
throw = Fix (arr (\((_,o),x) -> (o,x)))
#endif
lookupOutCache :: Identifiable x => Fix x y x (Error () y)
lookupOutCache = Fix $ \((_,o),x) -> (o,S.lookup x o)
lookupOutCache = Fix $ \((_,o),x) -> (o,strength2 $ S.lookup x o)
lookupInCache :: Identifiable x => Fix x y x (Error () y)
lookupInCache = Fix $ \((i,o),x) -> (o,S.lookup x i)
lookupInCache :: (Identifiable x, PreOrd y) => Fix x y x (Terminating y)
lookupInCache = Fix $ \((i,o),x) -> (o, return $ fromError bottom $ S.lookup x i)
writeOutCache :: Identifiable x => Fix x y (x,y) ()
writeOutCache = Fix $ \((_,o),(x,y)) -> (S.insert x y o,())
writeOutCache :: Identifiable x => Fix x y (x,Terminating y) ()
writeOutCache = Fix $ \((_,o),(x,y)) -> (S.insert x y o,return ())
getOutCache :: Fix x y () (Store x y)
getOutCache = Fix $ (\((_,o),()) -> (o,o))
getOutCache :: Fix x y () (Store x (Terminating y))
getOutCache = Fix $ (\((_,o),()) -> (o,return o))
setOutCache :: Fix x y (Store x y) ()
setOutCache = Fix $ (\((_,_),o) -> (o,()))
setOutCache :: Fix x y (Store x (Terminating y)) ()
setOutCache = Fix $ (\((_,_),o) -> (o,return ()))
localInCache :: Fix x y x y -> Fix x y (Store x y,x) y
localInCache :: Fix x y x y -> Fix x y (Store x (Terminating y),x) y
localInCache (Fix f) = Fix (\((_,o),(i,x)) -> f ((i,o),x))
updateOutCache :: (Identifiable x, Widening y) => Fix x y (x,y) ()
updateOutCache = Fix $ \((_,o),(x,y)) -> (S.insertWith (flip ()) x y o,())
updateOutCache :: (Identifiable x, Widening y) => Fix x y (x,Terminating y) ()
updateOutCache = Fix $ \((_,o),(x,y)) -> (S.insertWith (flip ()) x y o,return ())
deriving instance PreOrd (((Store a b,Store a b),x) -> (Store a b,y)) => PreOrd (Fix a b x y)
deriving instance Complete (((Store a b,Store a b),x) -> (Store a b,y)) => Complete (Fix a b x y)
deriving instance CoComplete (((Store a b,Store a b),x) -> (Store a b,y)) => CoComplete (Fix a b x y)
deriving instance LowerBounded (((Store a b,Store a b),x) -> (Store a b,y)) => LowerBounded (Fix a b x y)
deriving instance UpperBounded (((Store a b,Store a b),x) -> (Store a b,y)) => UpperBounded (Fix a b x y)
deriving instance (Identifiable a, PreOrd b, PreOrd y) => PreOrd (Fix a b x y)
deriving instance (Identifiable a, Complete b, Complete y) => Complete (Fix a b x y)
deriving instance (Identifiable a, CoComplete b, CoComplete y) => CoComplete (Fix a b x y)
deriving instance (Identifiable a, PreOrd b, PreOrd y) => LowerBounded (Fix a b x y)
-- deriving instance (Identifiable a, UpperBounded b, UpperBounded y) => UpperBounded (Fix a b x y)
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE Arrows #-}
module Control.Arrow.Transformer.Abstract.Floor where
import Prelude hiding (id,(.),lookup)
import Control.Category
import Control.Arrow
import Control.Arrow.Environment
import Control.Arrow.Fail
import Control.Arrow.Lift
import Control.Arrow.Fix
import Control.Arrow.Reader
import Control.Arrow.State
import Data.Abstract.Floored
import Data.Order
import Data.Utils
newtype Floor c x y = Floor { runFloor :: c x (Floored y) }
instance ArrowLift Floor where
lift f = Floor (f >>> arr Greater)
instance ArrowChoice c => Category (Floor c) where
id = lift id
Floor f . Floor g = Floor $ proc x -> do
ey <- g -< x
case ey of
Bottom -> returnA -< Bottom
Greater y -> f -< y
instance ArrowChoice c => Arrow (Floor c) where
arr f = lift (arr f)
first (Floor f) = Floor $ first f >>^ strength1
second (Floor f) = Floor $ second f >>^ strength2
instance ArrowChoice c => ArrowChoice (Floor c) where
left (Floor f) = Floor $ left f >>^ costrength1
right (Floor f) = Floor $ right f >>^ costrength2
instance (ArrowChoice c, ArrowApply c) => ArrowApply (Floor c) where
app = Floor $ first runFloor ^>> app
instance (ArrowChoice c, ArrowState s c) => ArrowState s (Floor c) where
getA = lift getA
putA = lift putA
instance (ArrowChoice c, ArrowFail e c) => ArrowFail e (Floor c) where
failA = lift failA
instance (ArrowChoice c, ArrowReader r c) => ArrowReader r (Floor c) where
askA = lift askA
localA (Floor f) = Floor (localA f)
instance (ArrowChoice c, ArrowEnv x y env c) => ArrowEnv x y env (Floor c) where
lookup = lift lookup
getEnv = lift getEnv
extendEnv = lift extendEnv
localEnv (Floor f) = Floor (localEnv f)
instance (ArrowChoice c, ArrowFix x (Floored y) c) => ArrowFix x y (Floor c) where
fixA f = Floor (fixA (runFloor . f . Floor))
deriving instance PreOrd (c x (Floored y)) => PreOrd (Floor c x y)
deriving instance LowerBounded (c x (Floored y)) => LowerBounded (Floor c x y)
deriving instance Complete (c x (Floored y)) => Complete (Floor c x y)
deriving instance CoComplete (c x (Floored y)) => CoComplete (Floor c x y)
deriving instance UpperBounded (c x (Floored y)) => UpperBounded (Floor c x y)
......@@ -37,13 +37,12 @@ evalStore f = runStore f >>> pi2
execStore :: Arrow c => StoreArrow var val c x y -> c (Store var val, x) (Store var val)
execStore f = runStore f >>> pi1
instance (Show var, Identifiable var, ArrowFail String c, ArrowChoice c, Complete (c ((Store var val,val),var) (Store var val,val)), LowerBounded (c () (Store var val,val))) =>
instance (Show var, Identifiable var, ArrowFail String c, ArrowChoice c, Complete (c ((Store var val,val),var) (Store var val,val))) =>
ArrowStore var val (StoreArrow var val c) where
read =
StoreArrow $ State $ proc (s,var) -> case S.lookup var s of
Success v -> joined returnA (proc var -> failA -< printf "could not find variable" (show var)) -< ((s,v),var)
Fail _ -> failA -< printf "could not find variable" (show var)
Bot -> bottom -< ()
write = StoreArrow (State (arr (\(s,(x,v)) -> (S.insert x v s,()))))
instance ArrowState s c => ArrowState s (StoreArrow var val c) where
......
......@@ -20,6 +20,7 @@ import Control.Arrow.Utils
import Control.Category
import Data.Order
import Data.Monoidal
newtype Reader r c x y = Reader { runReader :: c (r,x) y }
......@@ -32,12 +33,11 @@ instance Arrow c => Category (Reader r c) where
instance Arrow c => Arrow (Reader r c) where
arr f = lift (arr f)
first (Reader f) = Reader $ (\(r,(x,y)) -> ((r,x),y)) ^>> first f
second (Reader f) = Reader $ (\(r,(x,y)) -> (x,(r,y))) ^>> second f
first (Reader f) = Reader $ to assoc ^>> first f
instance ArrowChoice c => ArrowChoice (Reader r c) where
left (Reader f) = Reader $ injectLeft ^>> left f
right (Reader f) = Reader $ injectRight ^>> right f
Reader f +++ Reader g = Reader (to distribute ^>> f +++ g)
Reader f ||| Reader g = Reader (to distribute ^>> f ||| g)
instance ArrowApply c => ArrowApply (Reader r c) where
app = Reader $ (\(r,(Reader f,b)) -> (f,(r,b))) ^>> app
......
......@@ -22,6 +22,7 @@ import Control.Category
import Data.Hashable
import Data.Order
import Data.Monoidal
newtype State s c x y = State { runState :: c (s,x) (s,y) }
......@@ -40,12 +41,11 @@ instance ArrowLift (State r) where
instance Arrow c => Arrow (State s c) where
arr f = lift (arr f)
first (State f) = State $ (\(s,(x,y)) -> ((s,x),y)) ^>> first f >>^ (\((s',x'),y) -> (s',(x',y)))
second (State f) = State $ (\(s,(x,y)) -> (x,(s,y))) ^>> second f >>^ (\(x,(s',y')) -> (s',(x,y')))
first (State f) = State $ to assoc ^>> first f >>^ from assoc
instance ArrowChoice c => ArrowChoice (State s c) where
left (State f) = State $ injectBoth ^>> left f >>^ eject
right (State f) = State $ injectBoth ^>> right f >>^ eject
State f +++ State g = State $ to distribute ^>> f +++ g >>^ from distribute
State f ||| State g = State $ to distribute ^>> f ||| g
instance ArrowApply c => ArrowApply (State s c) where
app = State $ (\(s,(State f,b)) -> (f,(s,b))) ^>> app
......
......@@ -42,23 +42,3 @@ foldA f = proc (l,a) -> case l of
a' <- f -< (a,x)
foldA f -< (xs,a')
[] -> returnA -< a
injectLeft :: (r,Either a b) -> Either (r,a) b
injectLeft (r,e) = case e of
Left a -> Left (r,a)
Right b -> Right b
injectRight :: (r,Either a b) -> Either a (r,b)
injectRight (r,e) = case e of
Left a -> Left a
Right b -> Right (r,b)
injectBoth :: (r,Either a b) -> Either (r,a) (r,b)
injectBoth (r,e) = case e of
Left a -> Left (r,a)
Right b -> Right (r,b)
eject :: Either (r,a) (r,b) -> (r,Either a b)
eject e = case e of
Left (r,a) -> (r,Left a)
Right (r,b) -> (r,Right b)
......@@ -10,51 +10,42 @@ import Control.Monad.Except
import Data.Order
import Data.Abstract.Widening
import Data.Monoidal
-- | Error is an Either-like type with the special ordering Error ⊑ Success.
-- Left and Right of the regular Either type, on the other hand are incomparable.
data Error e a = Bot | Fail e | Success a
data Error e a = Fail e | Success a
deriving (Eq, Functor)
instance (Show e,Show a) => Show (Error e a) where
show Bot = "⊥"
show (Fail e) = "Error " ++ show e
show (Success a) = show a
instance PreOrd a => PreOrd (Error e a) where
Bot _ = True
Fail _ Success _ = True
Fail _ Fail _ = True
Success x Success y = x y
_ _ = False
Bot Bot = True
Fail _ Fail _ = True
Success x Success y = x y
_ _ = False
instance Complete a => Complete (Error e a) where
Bot b = b
a Bot = a
Fail _ b = b
a Fail _ = a
Success x Success y = Success (x y)
instance (PreOrd b) => LowerBounded (Error a b) where
bottom = Bot
instance UpperBounded a => UpperBounded (Error e a) where
top = Success top
instance Widening a => Widening (Error e a) where
Bot b = b
a Bot = a
Fail _ b = b
a Fail _ = a
Success x Success y = Success (x y)
instance MonadError e (Error e) where
throwError = Fail
catchError Bot _ = Bot
catchError (Fail e) f = f e
catchError (Success a) _ = Success a
......@@ -64,14 +55,12 @@ instance Applicative (Error e) where
instance Monad (Error e) where
return = Success
Bot >>= _ = Bot
Fail e >>= _ = Fail e
Success a >>= k = k a
fromError :: a -> Error e a -> a
fromError _ (Success a) = a
fromError a (Fail _) = a
fromError a Bot = a
fromEither :: Either e a -> Error e a
fromEither (Left e) = Fail e
......@@ -89,19 +78,28 @@ fromMaybe (Just a) = Success a
-- toMaybe (Error _) = Nothing
-- toMaybe (Success a) = Just a
unzipError :: Error e (a1,a2) -> (Error e a1, Error e a2)
unzipError Bot = (Bot,Bot)
unzipError (Fail e) = (Fail e, Fail e)
unzipError (Success (a1, a2)) = (Success a1, Success a2)
zipError :: Eq e => (Error e a1, Error e a2) -> Error e (a1,a2)
zipError (Bot, Bot) = Bot
zipError (Fail e1, Fail e2) | e1 == e2 = Fail e1
zipError (Success a1, Success a2) = Success (a1, a2)
zipError _ = error "cannot zip these error values"
mapError :: (a1 -> b1) -> (a2 -> b2) -> Error e (a1, a2) -> Error e (b1, b2)
mapError _ _ Bot = Bot
mapError _ _ (Fail e) = Fail e
mapError f1 f2 (Success (a1, a2)) = Success (f1 a1, f2 a2)
instance Monoidal Error where
mmap f _ (Fail x) = Fail (f x)
mmap _ g (Success y) = Success (g y)
assoc = Iso assocTo assocFrom
where
assocTo :: Error a (Error b c) -> Error (Error a b) c
assocTo (Fail a) = Fail (Fail a)
assocTo (Success (Fail b)) = Fail (Success b)
assocTo (Success (Success c)) = Success c
assocFrom :: Error (Error a b) c -> Error a (Error b c)
assocFrom (Fail (Fail a)) = Fail a
assocFrom (Fail (Success b)) = Success (Fail b)
assocFrom (Success c) = Success (Success c)
instance Commutative Error where
commute (Fail a) = Success a
commute (Success a) = Fail a
instance StrongMonoidal Error where
strength (Fail f) = fmap Fail f
strength (Success b) = return (Success b)
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
module Data.Abstract.Floored where
import Control.Monad
import Control.Applicative
import Data.Order
import Data.Abstract.Widening
-- Free cocompletion of a type
data Floored a = Bottom | Greater a deriving (Eq,Functor,Traversable,Foldable)
instance Show a => Show (Floored a) where
show Bottom = "⊥"
show (Greater a) = show a
instance Applicative Floored where
pure = return
(<*>) = ap
instance Monad Floored where
return = Greater
Greater x >>= k = k x
Bottom >>= _ = Bottom
instance PreOrd a => PreOrd (Floored a) where
Bottom _ = True
_ Bottom = False
Greater a Greater b = a b
Bottom Bottom = True
Greater a Greater b = a b
_ _ = False
instance Complete a => Complete (Floored a) where
Greater a Greater b = Greater (a b)
x Bottom = x
Bottom y = y
instance Widening a => Widening (Floored a) where
Greater a Greater b = Greater (a b)
x Bottom = x
Bottom y = y
instance PreOrd a => CoComplete (Floored a) where
Greater a Greater b
| a b = Greater a
| b a = Greater b
| otherwise = Bottom
Bottom _ = Bottom
_ Bottom = Bottom
instance UpperBounded a => UpperBounded (Floored a) where
top = Greater top
instance PreOrd a => LowerBounded (Floored a) where
bottom = Bottom
instance Num a => Num (Floored a) where
(+) = liftA2 (+)
(*) = liftA2 (*)
negate = fmap negate
abs = fmap abs
signum = fmap signum
fromInteger = pure . fromInteger
instance Fractional a => Fractional (Floored a) where
(/) = liftA2 (/)
fromRational = pure . fromRational
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
module Data.Abstract.Terminating where
import Control.Monad
import Control.Applicative
import Data.Order
import Data.Abstract.Widening
-- Free cocompletion of a type
data Terminating a = NonTerminating | Terminating a deriving (Eq,Functor,Traversable,Foldable)
instance Show a => Show (Terminating a) where
show NonTerminating = "⊥"