Skip to content
This repository was archived by the owner on Apr 1, 2025. It is now read-only.
5 changes: 4 additions & 1 deletion semantic-core/src/Analysis/Concrete.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{-# LANGUAGE FlexibleContexts, FlexibleInstances, LambdaCase, MultiParamTypeClasses, NamedFieldPuns, OverloadedStrings, RecordWildCards, TypeApplications, TypeOperators, UndecidableInstances #-}
{-# LANGUAGE DerivingVia, FlexibleContexts, FlexibleInstances, LambdaCase, MultiParamTypeClasses, NamedFieldPuns, OverloadedStrings, RecordWildCards, TypeApplications, TypeOperators, UndecidableInstances #-}
module Analysis.Concrete
( Concrete(..)
, concrete
Expand Down Expand Up @@ -28,6 +28,7 @@ import qualified Data.IntSet as IntSet
import Data.Loc
import qualified Data.Map as Map
import Data.Name
import Data.Semigroup (Last (..))
import qualified Data.Set as Set
import Data.Term
import Data.Text (Text, pack)
Expand All @@ -47,6 +48,8 @@ data Concrete
| String Text
| Record Env
deriving (Eq, Ord, Show)
-- NB: We derive the 'Semigroup' instance for 'Concrete' to take the second argument. This is equivalent to stating that the return value of an imperative sequence of statements is the value of its final statement.
deriving Semigroup via Last Concrete
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This instance makes me so happy!


recordFrame :: Concrete -> Maybe Env
recordFrame (Record frame) = Just frame
Expand Down
36 changes: 20 additions & 16 deletions semantic-core/src/Analysis/Eval.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Prelude hiding (fail)
eval :: ( Carrier sig m
, Member (Reader Loc) sig
, MonadFail m
, Semigroup value
)
=> Analysis address value m
-> (Term Core User -> m value)
Expand All @@ -41,12 +42,15 @@ eval Analysis{..} eval = \case
addr <- alloc n
v <- bind n addr (eval (instantiate1 (pure n) b))
v <$ assign addr v
a :>> b -> eval a >> eval b
-- NB: Combining the results of the evaluations allows us to model effects in abstract domains. This in turn means that we can define an abstract domain modelling the types-and-effects of computations by means of a 'Semigroup' instance which takes the type of its second operand and the union of both operands’ effects.
--
-- It’s also worth noting that we use a semigroup instead of a semilattice because the lattice structure of our abstract domains is instead modelled by nondeterminism effects used by some of them.
a :>> b -> (<>) <$> eval a <*> eval b
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combining the results of evaluations together with <> enables us to model effects compositionally. E.g. given a program like:

def f
  a
  b
end

Then a domain modelling the type-and-effect of f will define <> to combine the effects incurred by both a and b while taking the type of only b, whereas a domain modelling the import graph would union together any imports performed by either a or b. On the other hand, the concrete domain implements <> to simply discard a, since it isn’t attempting to instrument the effects, only perform them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s also worth asking: why a semigroup, when so much of the abstract interpretation literature focuses on (semi)lattices? In short, our lattice structure is provided by the sets of computations performed under the nondeterminism effect; paraphrasing Darais et al, concretization maps values in the abstract domain to finite sets of observations in the concrete domain. So in short, the lattice’s least upper bound operation is a) provided by NonDet, and b) semantically distinct from the use of <> to combine sequenced abstracted values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally, we could eventually consider strengthening the Semigroup constraint to Monoid to allow us to represent divergent/exceptional computation with mempty; or further strengthening it to some sort of unital semiring structure to combine the two kinds of combination, with addition for the least upper bound, zero for the bottom, multiplication for sequencing, and unit for its, well, unit. (I haven’t actually checked to see if this would actually be a lawful semiring; the distributive laws seem like they’d be pretty interesting.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all makes total sense to me, and is a huge win for clarity. Can we move the relevant sections of the above comments (which are wonderful!) to comments on the deriving instances for Concrete and Term Monotype a?

Named (Ignored n) a :>>= b -> do
a' <- eval a
addr <- alloc n
assign addr a'
bind n addr (eval (instantiate1 (pure n) b))
bind n addr ((a' <>) <$> eval (instantiate1 (pure n) b))
Lam (Named (Ignored n) b) -> abstract eval n (instantiate1 (pure n) b)
f :$ a -> do
f' <- eval f
Expand Down Expand Up @@ -210,18 +214,18 @@ ruby = fromBody $ annWith callStack (rec (named' __semantic_global) (do' stateme


data Analysis address value m = Analysis
{ alloc :: User -> m address
, bind :: forall a . User -> address -> m a -> m a
, lookupEnv :: User -> m (Maybe address)
, deref :: address -> m (Maybe value)
, assign :: address -> value -> m ()
, abstract :: (Term Core User -> m value) -> User -> Term Core User -> m value
, apply :: (Term Core User -> m value) -> value -> value -> m value
, unit :: m value
, bool :: Bool -> m value
, asBool :: value -> m Bool
, string :: Text -> m value
, asString :: value -> m Text
, record :: [(User, value)] -> m value
, (...) :: address -> User -> m (Maybe address)
{ alloc :: User -> m address
, bind :: forall a . User -> address -> m a -> m a
, lookupEnv :: User -> m (Maybe address)
, deref :: address -> m (Maybe value)
, assign :: address -> value -> m ()
, abstract :: (Term Core User -> m value) -> User -> Term Core User -> m value
, apply :: (Term Core User -> m value) -> value -> value -> m value
, unit :: m value
, bool :: Bool -> m value
, asBool :: value -> m Bool
, string :: Text -> m value
, asString :: value -> m Text
, record :: [(User, value)] -> m value
, (...) :: address -> User -> m (Maybe address)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitespace-only changes to this block.

}
8 changes: 7 additions & 1 deletion semantic-core/src/Analysis/Typecheck.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{-# LANGUAGE DeriveGeneric, DeriveTraversable, FlexibleContexts, FlexibleInstances, LambdaCase, OverloadedStrings, QuantifiedConstraints, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TypeApplications, TypeOperators #-}
{-# LANGUAGE DeriveGeneric, DeriveTraversable, DerivingVia, FlexibleContexts, FlexibleInstances, LambdaCase, OverloadedStrings, QuantifiedConstraints, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TypeApplications, TypeOperators #-}
module Analysis.Typecheck
( Monotype (..)
, Meta
Expand Down Expand Up @@ -30,6 +30,7 @@ import qualified Data.Map as Map
import Data.Maybe (fromJust, fromMaybe)
import Data.Name as Name
import Data.Scope
import Data.Semigroup (Last (..))
import qualified Data.Set as Set
import Data.Term
import Data.Void
Expand All @@ -44,6 +45,11 @@ data Monotype f a
| Record (Map.Map User (f a))
deriving (Foldable, Functor, Generic1, Traversable)

-- FIXME: Union the effects/annotations on the operands.

-- | We derive the 'Semigroup' instance for types to take the second argument. This is equivalent to stating that the type of an imperative sequence of statements is the type of its final statement.
deriving via (Last (Term Monotype a)) instance Semigroup (Term Monotype a)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was legit surprised that this instance isn’t orphaned, but it’s due to Monotype being defined in this module.


deriving instance (Eq a, forall a . Eq a => Eq (f a), Monad f) => Eq (Monotype f a)
deriving instance (Ord a, forall a . Eq a => Eq (f a)
, forall a . Ord a => Ord (f a), Monad f) => Ord (Monotype f a)
Expand Down