Haskell 应用函子分析

Haskell 应用函子分析,haskell,static-analysis,functor,applicative,Haskell,Static Analysis,Functor,Applicative,我一直在学习应用函子的静态分析。许多资料来源说,使用它们优于单子的一个优点是对静态分析的敏感性 然而,我能找到的唯一一个实际执行静态分析的方法太复杂了,我无法理解。有没有更简单的例子 具体来说,我想知道我是否可以对递归应用程序执行静态分析。例如,类似于: y = f <$> x <*> y <*> z y=f x y z 在分析上述代码时,是否可以检测到它在y上是递归的?还是引用透明性仍然阻止了这一点?应用程序函子允许在运行时进行静态分析。用一个简单的例子

我一直在学习应用函子的静态分析。许多资料来源说,使用它们优于单子的一个优点是对静态分析的敏感性

然而,我能找到的唯一一个实际执行静态分析的方法太复杂了,我无法理解。有没有更简单的例子

具体来说,我想知道我是否可以对递归应用程序执行静态分析。例如,类似于:

y = f <$> x <*> y <*> z
y=f x y z

在分析上述代码时,是否可以检测到它在y上是递归的?还是引用透明性仍然阻止了这一点?

应用程序函子允许在运行时进行静态分析。用一个简单的例子可以更好地解释这一点

假设您想要计算一个值,但想要跟踪该值具有哪些依赖项。例如,您可以使用
IO a
计算值,并为依赖项提供
字符串列表

data Input a = Input { dependencies :: [String], runInput :: IO a }
现在我们可以很容易地将其作为
Functor
Applicative
的实例。函子实例是平凡的。由于它不会引入任何新的依赖项,因此只需映射
runInput
值:

instance Functor (Input) where
  fmap f (Input deps runInput) = Input deps (fmap f runInput)
Applicative
实例更复杂。
pure
函数只返回一个没有依赖项的值。组合器将合并两个依赖项列表(删除重复项),并组合两个操作:

instance Applicative Input where
  pure = Input [] . return

  (Input deps1 getF) <*> (Input deps2 runInput) = Input (nub $ deps1 ++ deps2) (getF <*> runInput)
接下来,让我们做几个输入:

getTime :: Input UTCTime
getTime = Input { dependencies = ["Time"], runInput = getCurrentTime }

-- | Ideally this would fetch it from somewhere
stockPriceOf :: String -> Input Double
stockPriceOf stock = Input { dependencies = ["Stock ( " ++ stock ++ " )"], runInput = action } where
  action = case stock of
    "Apple" -> return 500
    "Toyota" -> return 20
最后,让我们创建一个使用一些输入的值:

portfolioValue :: Input Double
portfolioValue = stockPriceOf "Apple" * 10 + stockPriceOf "Toyota" * 20
这是一个非常酷的值。首先,我们可以将
portfolioValue
的依赖项作为纯值来查找:

> :t dependencies portfolioValue
dependencies portfolioValue :: [String]
> dependencies portfolioValue
["Stock ( Apple )","Stock ( Toyota )"]
这是
Applicative
允许的静态分析-我们不必执行操作就可以知道依赖关系

尽管如此,我们仍然可以获得操作的价值:

> runInput portfolioValue >>= print
5400.0

现在,为什么我们不能对
Monad
做同样的事情呢?原因是
Monad
可以表达选择,因为一个动作可以决定下一个动作

假设有一个用于
输入的
Monad
接口,您有以下代码:

mostPopularStock :: Input String
mostPopularStock = Input { dependencies ["Popular Stock"], getInput = readFromWebMostPopularStock }

newPortfolio = do
  stock <- mostPopularStock
  stockPriceOf "Apple" * 40 + stockPriceOf stock * 10

是的,应用函子比单子允许更多的分析。但是不,你不能观察递归。我写了一篇关于解析的论文,详细解释了这个问题:

然后,本文讨论了递归的另一种编码方式,这种编码方式允许进行分析,并具有其他一些优点和缺点。其他相关工作包括:


更多的相关工作可以在这些论文的相关工作部分找到…

这是一个很好的答案,但并不能完全回答原始人的问题;具体地说:不,你不能观察到
y
本身是递归的(除了可能通过戳一些GHC特定的API),是的,这是因为引用的透明性。@kq:那太好了!正如Daniel指出的,我没有回答关于检查
y
是否是递归的部分,我在问题中添加了另一部分。我知道你的意思,但是“运行时静态分析”这个短语听起来真的很有趣。这几乎是自相矛盾的。@TikhonJelvis我也不喜欢这种说法,但我想不出更好的方式来描述它:(在运行时能够静态分析
应用程序
函数的一个库是。每个
解析器a
都可以被执行来构造一些东西,将命令行选项解析为
a
,也可以被分析来提取命令行帮助,而无需实际运行解析器。源代码是实际上可读性很好,是对该技术的一个很好的介绍。
mostPopularStock :: Input String
mostPopularStock = Input { dependencies ["Popular Stock"], getInput = readFromWebMostPopularStock }

newPortfolio = do
  stock <- mostPopularStock
  stockPriceOf "Apple" * 40 + stockPriceOf stock * 10
data TrackedComp a = TrackedComp { deps :: [String],  recursive :: Bool, run :: a}

instance (Show a) => Show (TrackedComp a) where
  show comp = "TrackedComp " ++ show (run comp)

instance Functor (TrackedComp) where
  fmap f (TrackedComp deps rec1 run) = TrackedComp deps rec1 (f run)

instance Applicative TrackedComp where
  pure = TrackedComp [] False

  (TrackedComp deps1 rec1 getF) <*> (TrackedComp deps2 rec2 value) =
    TrackedComp (combine deps1 deps2) (rec1 || rec2) (getF value)

-- | combine [1,1,1] [2,2,2] = [1,2,1,2,1,2]
combine :: [a] -> [a] -> [a]
combine x [] = x
combine [] y = y
combine (x:xs) (y:ys) = x : y : combine xs ys

instance (Num a) => Num (TrackedComp a) where
  (+) = liftA2 (+)
  (*) = liftA2 (*)
  abs = liftA abs
  signum = liftA signum
  fromInteger = pure . fromInteger

newComp :: String -> TrackedComp a -> TrackedComp a
newComp name tracked = TrackedComp (name : deps tracked) isRecursive (run tracked) where
   isRecursive = (name `elem` deps tracked) || recursive tracked 


y :: TrackedComp [Int]
y = newComp "y" $ liftA2 (:) x z

x :: TrackedComp Int
x = newComp "x" $ 38

z :: TrackedComp [Int]
z = newComp "z" $ liftA2 (:) 3 y

> recursive x
False
> recursive y
True
> take 10 $ run y
[38,3,38,3,38,3,38,3,38,3]