在Haskell中增加环境的可应用程序

4
我想知道是否有一个可以跟踪应用操作次数的Applicative。我尝试按照以下方式实现它:
import Control.Applicative

main :: IO ()
main = print $ run 1 $ (,,,) <$> FromInt id <*> FromInt id <*> FromInt id <*> FromInt id

data FromInt a = FromInt (Int -> a)

run :: Int -> FromInt a -> a
run i (FromInt f) = f i

instance Functor FromInt where
  fmap g (FromInt f) = FromInt (g . f)

instance Applicative FromInt where
  pure a = FromInt (const a)
  FromInt f <*> FromInt g = FromInt (\i -> f i (g (i + 1)))

但是,这当然是不行的。如果我们在文件上调用runhaskell,我们会得到以下结果:

(1,2,2,2)

我需要的是:

(1,2,3,4)

我看到有人通过将增量要求推入实际数据来实现此效果(这是yesod-forms实现其表单样式的方式)。这更或多或少使用了State的变体,它允许人们在不使用特定帮助函数的情况下打破假定的不变性(我认为yesod的一个叫做mhelper)。我想知道是否可以像我尝试做的那样将增量操作拉入应用实例中。这将使违反这个特定不变量成为不可能。


我不相信这是可能的,因为可应用动作不能依赖于先前的结果。为了达到这个目的,我认为你必须要使用单子才能得到想要的输出。 - bheklilr
你不需要单子。State的应用实例运行良好,但我希望有更好的方法。 - Andrew Thaddeus Martin
1个回答

10

(,) a 是一个 Applicative,当 a 是一个 Monoid 时。我们可以使用 Data.Functor.Compose(,) (Sum Int) 与其他 applicative 组合,得到一个 applicative,它允许我们在运行计算之前估算分配给计算的“成本”。

为了计算步骤,我们需要从基础 applicative 中提取一个 lift 函数,始终分配成本为 1:

module Main where

import Data.Monoid
import Control.Applicative
import Data.Functor.Compose

type CountedIO a = Compose ((,) (Sum Int)) IO a

-- lift from IO
step :: IO a -> CountedIO a
step cmd = Compose (Sum 1, cmd)

countSteps :: CountedIO a -> Int
countSteps = getSum . fst . getCompose

exec :: CountedIO a -> IO a
exec =  snd . getCompose

program :: CountedIO () 
program = step (putStrLn "aaa") *>  step (putStrLn "bbb") *> step (putStrLn "ccc")

main :: IO ()
main = do
    putStrLn $ "Number of steps: " ++ show (countSteps program)
    exec program 

为了更大的安全性,我们可以将组合应用程序隐藏在一个新类型后面,并且不导出构造函数,只导出step函数。

(使用pure创建的操作成本为0,不计入步数。)


这是一个不错的解决方案,但最终这正是我试图避免做的事情。如果没有人能想出更好的方法(不需要隐藏数据构造函数来保持不变性),我会等一段时间并接受这个方案。 - Andrew Thaddeus Martin
7
@Andrew Thaddeus Martin,我认为在保持应用法则的同时将不变量引入到应用程序中可能会很困难。如果(<*>)总是增加计数,那么如何理解定律pure id <*> v = v - danidiaz
你说得对。我正在寻找的实例显然是不可能的。谢谢。 - Andrew Thaddeus Martin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接