惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
P
Privacy International News Feed
Vercel News
Vercel News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 叶小钗
F
Fortinet All Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 最新话题
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta
Attack and Defense Labs
Attack and Defense Labs
Recent Announcements
Recent Announcements
Recent Commits to openclaw:main
Recent Commits to openclaw:main
PCI Perspectives
PCI Perspectives
Cloudbric
Cloudbric
AI
AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
IT之家
IT之家
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
J
Java Code Geeks
M
MIT News - Artificial intelligence
Cisco Talos Blog
Cisco Talos Blog
V2EX - 技术
V2EX - 技术
Webroot Blog
Webroot Blog
Microsoft Security Blog
Microsoft Security Blog
Cyberwarzone
Cyberwarzone
博客园 - 聂微东
G
Google Developers Blog
W
WeLiveSecurity
罗磊的独立博客
P
Privacy & Cybersecurity Law Blog
阮一峰的网络日志
阮一峰的网络日志
A
About on SuperTechFans
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
T
Tailwind CSS Blog
V
Visual Studio Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
Secure Thoughts
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
Google DeepMind News
Google DeepMind News
雷峰网
雷峰网
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
Blog — PlanetScale
Blog — PlanetScale
The Last Watchdog
The Last Watchdog
P
Proofpoint News Feed

Lobsters

CIFSwitch: a non-universal Linux local root vulnerability RIPE NCC session fixation: poaching logins with an Atlas probe GNOME 2.20 but its Web Components Agentic Search for Context Engineering – Leonie Monigatti Garnix is shutting down [not OC] akashina.tngl.sh/jjc Concerning Emacs (and Jazz) Nitpicking the shell history scene in ‘Tron: Legacy’ What's cooking on SourceHut? Q2 2026 The tenth OpenPGP email summit Package managers that package package managers Clojure on Fennel part three: parsing WordPress at 23 Finding Miscompiles for Fun, Not Profit GitHub - creusot-rs/creusot: Creusot helps you prove your Rust code is correct. Announcing Rust 1.96.0 | Rust Blog A Love Letter to Neovim sqlite AGENTS.md Am I a Bad Friend? CSS vs. JavaScript • Josh W. Comeau Erlang Ecosystem Foundation - Supporting the BEAM community A brief note about slot access cost in Common Lisp Keyboard latency probe Rethinking the GNOME clipboard issues Back to the Building Blocks’ Building Blocks Tech Notes: Theseus: translating win32 to wasm Fast is better than slow Content-addressed Rust builds (or, what kache actually caches) Intent to Prototype: Embedding API Canada’s Bill C-22 and the security cost of collecting more data 5 PostgreSQL locking behaviors that trip people up okmij.org Stop advertising in your commits! | AksDev GitHub - mplsllc/macsurf: A modern web browser for Classic Mac OS 9 PowerPC. Real CSS3, ES5 JavaScript, native HTTPS — built with CodeWarrior on the Carbon API. Introducing DoomBench - Can Your Data Stack Run DOOM? What are some of your favourite developer tools? Building a Scalable Ingestion Pipeline with Temporal (Part 1) Converting shallow Git bundles into normal repositories Are you a member of any professional associations? What is a harmonic? An interactive comic about additive synthesis How Virtual Tables Work in the Itanium C++ ABI Using SwiftUI to Build a Mac-assed App in 2026 Rust (and Slint) on a jailbroken Kindle. ~jack/lambda-on-lambda - Serverless Haskell on AWS - sourcehut git Human proof for FOSS contributions Extremely simple internet radio controlled via IRC Announcing BABLR Splitting Konsole views from Helix to run tools | AksDev GitHub - yugr/rust-slides Serving files over HTTP three ways: synchronous, epoll, and io_uring update docs with information about building with build.py (#979) · astral-sh/python-build-standalone@c9c40c5 A Simple Makefile Tutorial On C extensions, portability, and alternative compilers Switching to Colemak | Pedro Alves Just How Bad Was The Intel IAPX432? Nix's Substituter List Is Not a Routing Table Accelerating copy_if using SIMD Lambda on Lambda: Serverless Haskell on AWS | Blog Announcing feed-repeat v1.0 Scaling Akvorado BMP RIB with sharding EYG news: A host of CLI improvements, new guides and new effects The social contract of writing JS Crossword C array types are weird; and related topics Flatpak will depend on systemd – OSnews Migrating from Go to Rust | corrode Rust Consulting A portentous reunion Vivado Licensing Options How my minimal, memory-safe Go rsync steers clear of vulnerabilities the entropy layer of a wavelet codec, on its own GitHub - nferhat/fht-compositor: A dynamic tiling Wayland compositor. Debian SE Linux and PinTheft Does bulk memmove speed up std::remove_if? (No.) 声明式部分更新 | Blog | Chrome for Developers Fully in-browser container builds Dianne Skoll's Web Site - Remind The Architecture of Open Source Applications (Volume 1)Berkeley DB Pardon MIE? - ironPeak Blog “Long-Term Support” doesn’t mean what you think Jira IS Turing-Complete May I recommend thinking of Emacs as your Fortress of Solitude hershey Floodgap Gopher-HTTP gateway gopher://thelambdalab.xyz/1cuneiforth/ HP QuickWeb, Singular And Pointless That one time I used Go panics for flow control A new suite of modern tools coming for editing and publishing RFCs From the Tabletop… The Digital Antiquarian Building a Host-Tuned GCC to Make GCC Compile Faster Are we self-sovereign PKI yet? Claw Patrol: an open-source security firewall for agents | Deno Revised^7 Report on Scheme, Large: Procedural Fascicle Draft is now public A Network Allow-List Won't Stop Exfiltration — André Graf From AFSK to Goertzel – µArt.cz Software For My New Home Server Introducing Neptune: Direct3D virtualization for QEMU AI Agent Bankrupted Their Operator While Trying to Scan DN42 - Lan Tian @ Blog mimalloc: A new, high-performance, scalable memory allocator for the modern era Making wl_shm fast The Soul of Maintaining a New Machine - Third Draft | Books in Progress What is Git made of?
Homomorphic static analysis
marcosh.gith · 2026-05-21 · via Lobsters

It is somehow shared knowledge among Haskellers that applicatives allow performing static analysis, while monads do not. But what does it mean in practice? And what kind of static analysis is this referring to?

The best answer I could find online is this answer on StackOverflow. In this post I’d like to expand on it a bit and take the concept a bit further1.

Contextual interface

Consider an example interface dependent on a generic context m, implemented as a record of functions:

data Interface m = Interface
  { foo :: FooInput -> m FooOutput
  , bar :: BarInput -> m BarOutput
  , baz :: BazInput -> m BazOutput
  }

This is an approach for defining interfaces in a pure language which has been discussed and proposed multiple times2 and works pretty well. It defines a set of operations which work in the same context and which can be used to define more complex programs2.

myProgram :: Interface m -> Input -> m Output
myProgram interface input = ...

myProgram will use the basic operations defined by Interface m, but how such operations can be combined is defined by m. If m admits an instance of Functor, we are able to execute one of its operations and then map the result with a pure function. In case m admits an instance of Applicative, we are able to independently execute operations from Interface m and then combine their results. If m admits an instance of Monad, we will be able to combine operations from Interface m sequentially, deciding whether to execute an operation based on the result of a previous one.

Interpreting the code without executing it

In our production code myProgram will be interpreted in some effectful context m where we will execute all the required effects. For example m could be a concrete monad, a stack of monad transformers or an Eff monad from your favourite effect system.

Still, we might want to collect some information about our code without performing any of the concrete effects; examples of this could be analysing some code metric, computing a list of dependencies or creating a graphical representation of our code flow.

One way to achieve this is by interpreting myProgram in another context m' which happen not to perform any of the effects, but it’s there just to collect information. Once we have this we can write another executable which interprets myProgram in m' and retrieves the information we are interested in without executing any effect. This could be done without running our application and that’s why it is called static analysis, even if it requires running some code.

Interpreting in a data-agnostic context

One way of defining a context m' like the one described in the last paragraph of the previous section is to make it data-agnostic. With this I mean a data structure like

newtype StaticInterpreter a = StaticInterpreter StaticDatum

The relevant thing of StaticInterpreter is that a is a phantom type, meaning that it appears in the type, but there is nothing of that type at the value level.

The benefit of this is that we can now combine values of StaticInterpreter even if they are tagged with different types. For example, suppose we have StaticDatum = Int, then we can combine StaticInterpreter i1 :: StaticInterpreter a1 and StaticInterpreter i2 :: StaticInterpreter a2 and obtain StaticInterpreter (i1 + i2) :: StaticInterpreter a.

Constraining context constrains static datum

Let’s now try to see what happens to StaticDatum as soon as we start imposing constraints on the m from myProgram.

If we need m to be a Functor, then also StaticInterpreter should admit a Functor instance

instance Functor StaticInterpreter where
  fmap :: (a -> b) -> StaticInterpreter a -> StaticInterpreter b
  fmap f (StaticInterpreter datum) = StaticInterpreter datum

To implement it, we have no constraint to impose on StaticDatum. This means that if we just need m to be a Functor, we can statically analyse myProgram and interpret it with any possible type.

Usually though our programs will require something more than Functor. Our next step is requiring m to have an Applicative instance. Let’s see what happens to StaticDatum as soon as we do that.

instance Applicative StaticInterpreter where
  pure :: a -> StaticInterpreter a
  pure a = StaticInterpreter _

  (<*>) :: StaticInterpreter (a -> b) -> StaticInterpreter a -> StaticInterpreter b
  (<*>) (StaticInterpreter sd1) (StaticInterpreter sd2) = StaticInterpreter _

We basically need a way to create a StaticDatum out of nothing, and a way to combine two StaticDatum values into a new StaticDatum value. In other terms, we need

_outOfNothing :: StatidDatum

_combineTwo :: StaticDatum -> StaticDatum -> StaticDatum

But this is exactly what a Monoid instance on StaticDatum gives us!

This means that we can use any type with a Monoid instance to statically analyse a program which is constrained by an Applicative constraint. For example StaticDatum could be a list of values and interpreting myProgram in StaticInterpreter would collect and concatenate all elements3.

Let’s now see what happens if we require StaticInterpreter to have a Monad instance.

instance Monad StaticInterpreter where
  (>>=) :: StaticInterpreter a -> (a -> StaticInterpreter b) -> StaticInterpreter b
  (>>=) (StaticInterpreter sd) f = _

If we want a non-trivial implementation of >>= (i.e. we don’t want to return StaticInterpreter mempty every time), we would need to use f to obtain a value of type StaticInterpreter b. But to call f we need a value of type a, which we can’t extract from StaticInterpreter a, because a there is actually a phantom type and there’s no value of type a in there. The fact that we can not implement >>= is the reason why we usually say that Monads do not allow static analysis4.

Let’s play the same game with categories

In the previous section we had a look at what kind of static analysis we can perform on a program written in a given context m. We concluded that if m is just required to have an Applicative instance, we can statically analyse our program with any monoid, while if we require an instance of Monad, then we basically lose our ability to introspect our code statically.

What could be interesting now (it is for me!) is trying to apply the same reasoning working with categories instead of functors/applicatives/monads. This would mean considering types with kind Type -> Type -> Type instead of Type -> Type. Examples of this could be simply (->) or Kleisli m5.

Let’s consider, as above, a data type which is defined with phantom types

newtype CatStaticInterpreter a b = CatStaticInterpreter StaticDatum

and see what happens when we try to implement instances of several type classes. Let’s start with the Profuctor typeclass.

instance Profunctor CatStaticInterpreter where
  dimap :: (a -> b) -> (c -> d) -> CatStaticInterpreter b c -> CatStaticInterpreter a d
  dimap f g (CatStaticInterpreter sd) = CatStaticInterpreter sd

As we did above with Functor, we just ignore the f and g functions, and we keep the same StaticDatum value.

Next we can investigate what happens with the Category typeclass

instance Category CatStaticInterpreter where
  id :: CatStaticInterpreter a a
  id = _

  (.) :: CatStaticInterpreter b c -> CatStaticInterpreter a b -> CatStaticInterpreter a c
  (.) (CatStaticInterpreter sd1) (CatStaticInterpreter sd2) = _

Similarly to what was happening with the Applicative typeclass in the previous section, we can notice that we can implement the Category instance for CatSTaticInterpreter if StaticDatum admits a Monoid instance. In that case we can define

  id = CatStaticInterpreter mempty

  (.) (CatStaticInterpreter sd1) (CatStaticInterpreter sd2) = CatStaticInterpreter (sd1 <> sd2)

Please notice here that, while Monad and Category are equally powerful from a computational point of view, the former does not admit any kind of static analysis, while the latter does.

An example with Strong and Choice

Thing start to get even more interesting as soon as we start other constraints like Strong or Choice. For each one of these typeclasses we need another binary operation on StaticDatum which would correspond to (***) and (+++) respectively.

For this use case, let’s define StaticDatum as

data StaticDatum
  = Unit Text
  | Compose StaticDatum StaticDatum
  | Alternative StaticDatum StaticDatum
  | Parallel StaticDatum StaticDatum

With this definition we can define instances for Category, Strong and Choice for CatStaticInterpreter.

instance Category CatStaticInterpreter where
  id = CatStaticInterpreter Unit
  (.) (CatStaticInterpreter sd1) (CatStaticInterpreter sd2) = CatStaticInterpreter (Compose sd1 sd2)

instance Strong CatStaticInterpreter where
  first' :: CatStaticInterpreter a b -> CatStaticInterpreter (a, c) (b, c)
  first' (CatStaticInterpreter sd) = CatStaticInterpreter (Parallel sd (Unit "id"))

  second' :: CatStaticInterpreter a b -> CatStaticInterpreter (c, a) (c, b)
  second' (CatStaticInterpreter sd) = CatStaticInterpreter (Parallel (Unit "id") sd)

instance Choice CatStaticInterpreter where
  left' :: CatStaticInterpreter a b -> CatStaticInterpreter (Either a c) (Either b c)
  left' (CatStaticInterpreter sd) = CatStaticInterpreter (Alternative sd (Unit "id"))

  right' :: CatStaticInterpreter a b -> CatStaticInterpreter (Either c a) (Either c b)
  right' (CatStaticInterpreter sd) = CatStaticInterpreter (Alternative (Unit "id") sd)

At this point let’s define an interface depending on a category p

data Interface p a b c d e f g h = Interface
  { foo1 :: p a b
  , foo2 :: p a c
  , foo3 :: p a (Either a a)
  , foo4 :: p a d
  , foo5 :: p a e
  , foo6 :: p (b, c) f
  , foo7 :: p (Either d e) g
  , foo8 :: p (f, g) h
  }

With it, we can define an example function foo consuming that interface

foo :: (Category p, Strong p, Choice p) => Interface p a b c d e f g h -> p a h
foo Interface {..} =
  foo8 . ((foo6 . (foo1 &&& foo2)) &&& (foo7 . (foo4 +++ foo5) . foo3))

Using our CatStaticInterpreter we can now define an implementation for Interface CatStaticInterpreter

implementation :: Interface CatStaticInterpreter a b c d e f g h
implementation =
  Interface
    { foo1 = CatStaticInterpreter (Unit "foo1")
    , foo2 = CatStaticInterpreter (Unit "foo2")
    , foo3 = CatStaticInterpreter (Unit "foo3")
    , foo4 = CatStaticInterpreter (Unit "foo4")
    , foo5 = CatStaticInterpreter (Unit "foo5")
    , foo6 = CatStaticInterpreter (Unit "foo6")
    , foo7 = CatStaticInterpreter (Unit "foo7")
    , foo8 = CatStaticInterpreter (Unit "foo8")
    }

Eventually we can statically analyse foo with CatStaticInterpreter just by evaluating foo implementation

>>> foo implementation
Compose
  (Unit "foo8")
  (Compose
    (Parallel
      (Compose
        (Unit "foo6")
        (Compose
          (Parallel (Unit "foo1") (Unit "id"))
          (Parallel (Unit "id") (Unit "foo2"))
        )
      )
      (Unit "id")
    )
    (Parallel
      (Unit "id")
      (Compose
        (Unit "foo7")
        (Compose
          (Compose
            (Alternative (Unit "foo4") (Unit "id"))
            (Alternative (Unit "id") (Unit "foo5"))
          )
          (Unit "foo3")
        )
      )
    )
  )

For example, with this tree-like representation, it’s not hard to traverse it and check whether a specific pattern is used (just a single function, or a more complex combination), or even optimise and restructure the tree. For example any (Compose (Parallel (Unit "foo1") (Unit "id")) (Parallel (Unit "id") (Unit "foo2"))) could probably be reinterpreted to (Parallel (Unit "foo1") (Unit "foo2")).

Another possibility is printing out a graphical representation of the resulting tree, which documents the flow of our program. For foo it could look like

Profunctor flow

Conclusion

To recap what we went through in this post, we saw that we can analyse statically any code working in an Applicative context with any monoid; on the other hand monads do not allow non-trivial static analysis.

Moving to a categorical realm, which is as powerful as the monadic one, we saw that we can actually perform static analysis, and actually adding more constraints allow us to have more powerful understanding of the structure of our code. In our example we saw what we can do using Strong and Choice. One can use the same trick with Closed, which deals with functions, CoStrong, which deals with recursion, or CoChoice.