如何创建一个“计算连接数”的箭头?

时间:2016-06-02 17:18:20

标签: haskell arrows

我最近有想法建立一个计算计算经过的绑定数量的monad。我想出了以下内容:

newtype K a = K { runK :: Int -> (a, Int) }

instance Functor K where
  fmap f k = K $ \s ->
    let (iv, s') = runK k s
     in (f iv, s')

instance Applicative K where
  pure x = K $ \s -> (x, s)
  f <*> b = K $ \s ->
    let (f', s')  = runK f s
        (ih, s'') = runK b s'
     in (f' ih, s'')

instance Monad K where
  return x = K $ \s -> (x, s)
  a >>= f = K $ \s ->
    let (iv, s') = runK a s
     in runK (f iv) (s' + 1)

我后来意识到这只是State monad并不是很有趣。然后,我有了尝试构建一个计算“连接”的箭头的想法。在这里,我有点难过。

newtype L a b = L { runL :: Int -> (a -> b, Int) }

instance Category L where
  id = arr Prelude.id
  b . a = L $ \s ->
    let (f1, s') = runL a s
        (f2, s'') = runL b s'
     in (f2 Prelude.. f1, s'' + 1)

instance Arrow L where
  arr f = L $ \s -> (f, s + 1)
  first a = L $ \s ->
    let (f1, s') = runL a s
     in (\(x, y) -> (f1 x, y), s')

我提出了上面的计算连接数,但不计算计算经过的连接数。建议?

2 个答案:

答案 0 :(得分:5)

(这与Daniel Wagner的观察结果非常相似,但我认为我会更友好。)

你应该冥想像monad法则那样具有法律的含义。这是一个可以变得微妙和复杂的主题,但您可以应用的一个非常简单的经验法则是:

  • 如果类型或功能承诺遵守法律x = y,则该类别的客户不应该分开xy
  • 即,您应能够编写函数f,以便:
    • 法律规定x = y;
    • f x /= f y

现在这不是一个绝对的规则。以排序为例;合并排序和冒泡排序都是稳定的排序算法,因此它们具有相同的功能&#34;从某种意义上说,除了查看输入和输出之外,你无法区分它们。但是如果你使用&#34;侧面频道&#34;像时间一样,你仍然可以将它们区分开来,但是当我们询问某些代码是否符合某项法律时,我们通常会将其排除在外。

现在,当我们将此应用于您的K类型时,问题是您可以编写一个程序来区分monad法则所说的两个表达式,这些表达式应该是难以区分的。例如,在documentation for the Monad class中它表示:

  

此外,MonadApplicative操作应如下所示:

pure = return
(<*>) = ap

我们可以通过这样的QuickCheck测试轻松驳斥其中的第二个:

import Control.Monad (ap)
import Test.QuickCheck
import Test.QuickCheck.Function

-- Snipped your code for `K`

prop_ap :: Fun Int Int -> Int -> Bool
prop_ap f x = runK applicative 0 == runK monadic 0
    where applicative = pure (apply f) <*> pure x
          monadic = return (apply f) `ap` return x

立即进行测试失败:

>>>  quickCheck prop_ap
*** Failed! Falsifiable (after 1 test and 2 shrinks): 
{_->0}
0

这里的原因是您的Monad实例正在执行+1,但您的Applicative实例并未执行此类操作。因此,我们可以区分等效计算,但其中一个使用<*>构建,另一个使用ap构建。

答案 1 :(得分:2)

正如其他人所描述的那样,你无法统计&#34;连接&#34;例如绑定或箭头绑定操作,因为它们不会满足monad / arrow法则。

但是,可以做的是创建一个箭头,您可以在其中明确地为构建块指定大小,然后计算此类电路的大小。举个例子:     { - #LANGUAGE箭头# - }

import Control.Arrow
import qualified Control.Category as C

data A a b c = A { runA :: a b c, sizeA :: !Int }

在这里,我们定义了一个包装现有箭头并添加其大小的箭头变换器。

instance (C.Category a) => C.Category (A a) where
    id = A C.id 0
    (A a1 s1) . (A a2 s2) = A (a1 C.. a2) (s1 + s2)

instance (Arrow a) => Arrow (A a) where
    arr f = A (arr f) 0
    first (A f s) = A (first f) s

instance (ArrowChoice a) => ArrowChoice (A a) where
    left (A f s) = A (left f) s

-- instance (Arrow a) => ArrowTransformer A a where
--     lift a = A a 0

默认大小必须始终为0才能满足法律要求。例如,id的大小必须为x . id === x,这要归功于法律arr id === id,我们认为arr f的大小必须为0等。

但我们可以定义一个自定义函数,为给定的箭头指定给定的大小:

sized :: Int -> a b c -> A a b c
sized = flip A

举一个例子,让我们构建一些A (->)类型的箭头。也就是说,底层箭头只是函数。

-- * Example (adapted from https://wiki.haskell.org/Arrow_tutorial)

-- | Both 'f' and 'g' are constructed to have a size of 1.
f, g :: A (->) Int Int
f = sized 1 $ arr (`div` 2)
g = sized 1 $ arr (\x -> x*3 + 1)

h :: A (->) Int Int
h = proc x -> do
      fx <- f -< x
      gx <- g -< x
      returnA -< (fx + gx)

您可以h作为runA h 5运行。但您也可以按sizeA h来衡量其大小,它会按预期返回2。请注意,您不需要运行箭头来获取其大小。你可以把它想象成一个电路,你不需要实际上电电路来查看它的大小。

请注意,我们无法为monad执行此操作,因此我们无法拥有实例ArrowChoice A。在monad中我们可以计算下一个&#34;效果&#34;从之前的结果来看,这意味着我们永远不能在没有实际运行monadic计算的情况下计算大小。