如何在不重复自己的情况下使该算法变得更懒惰?

时间:2019-12-14 17:29:34

标签: haskell lazy-evaluation dry

(受我对this question的回答启发。)

考虑以下代码(应该查找小于或等于给定输入的最大元素):

let subscribe : Publish.Subscribe =
    fun request ->

        async {

            let subscribed  = request |> toSubscribedCourier
            let json        = JsonConvert.SerializeObject subscribed
            let buffer      = Encoding.ASCII.GetBytes(json)
            let message     = Message(buffer)

            let topicClient = new TopicClient("MyConnectionString","Subscription.subscribed")

            do! topicClient.SendAsync(message) |> Async.AwaitTask
            return Ok subscribed
        }

这不是很懒。输入data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord) closestLess :: Integer -> TreeMap v -> Maybe (Integer, v) closestLess i = precise Nothing where precise :: Maybe (Integer, v) -> TreeMap v -> Maybe (Integer, v) precise closestSoFar Leaf = closestSoFar precise closestSoFar (Node k v l r) = case i `compare` k of LT -> precise closestSoFar l EQ -> Just (k, v) GT -> precise (Just (k, v)) r 大小写后,我们可以确定最终返回值将是GT而不是Just,但是Nothing直到结束。我想做的比较懒,以便在输入Just大小写后可以使用Just。我的测试用例是,我希望GT评估为Data.Maybe.isJust $ closestLess 5 (Node 3 () Leaf undefined)而不是触底。这是我可以想到的一种方法:

True

但是,我现在要重复一遍:data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord) closestLess :: Integer -> TreeMap v -> Maybe (Integer, v) closestLess _ Leaf = Nothing closestLess i (Node k v l r) = case i `compare` k of LT -> closestLess i l EQ -> Just (k, v) GT -> Just (precise (k, v) r) where precise :: (Integer, v) -> TreeMap v -> (Integer, v) precise closestSoFar Leaf = closestSoFar precise closestSoFar (Node k v l r) = case i `compare` k of LT -> precise closestSoFar l EQ -> (k, v) GT -> precise (k, v) r closestLess中都包含了核心逻辑。我该怎么写,以免偷懒而不重复自己?

5 个答案:

答案 0 :(得分:4)

您可以使用类型系统,而不是使用显式包装器。请注意,第一个代码段使用precise的{​​{1}}版本:

Maybe

与第二个代码段中没有precise :: Maybe (Integer, v) -> TreeMap v -> Maybe (Integer, v) precise closestSoFar Leaf = closestSoFar precise closestSoFar (Node k v l r) = case i `compare` k of LT -> precise closestSoFar l EQ -> Just (k, v) GT -> precise (Just (k, v)) r 的{​​{1}}版本的算法几乎完全相同,该算法可以在precise仿函数中编写为:

Maybe

这些可以在Identity中统一为一个多态版本:

precise :: Identity (Integer, v) -> TreeMap v -> Identity (Integer, v)
precise closestSoFar Leaf = closestSoFar
precise closestSoFar (Node k v l r) = case i `compare` k of
  LT -> precise closestSoFar l
  EQ -> Identity (k, v)
  GT -> precise (Identity (k, v)) r

就其本身而言,这并不能完成很多事情,但是,如果我们知道Applicative分支将始终返回值,那么无论开始什么地方,我们都可以强制其在precise :: (Applicative f) => f (Integer, v) -> TreeMap v -> f (Integer, v) precise closestSoFar Leaf = closestSoFar precise closestSoFar (Node k v l r) = case i `compare` k of LT -> precise closestSoFar l EQ -> pure (k, v) GT -> precise (pure (k, v)) r 函子中运行函子。也就是说,我们可以从GT仿函数开始,然后递归到Identity分支中的Maybe仿函数:

Identity

这可以很好地与您的测试用例配合使用:

GT

是多态递归的一个很好的例子。

从性能的角度来看,此方法的另一个好处是closestLess :: Integer -> TreeMap v -> Maybe (Integer, v) closestLess i = precise Nothing where precise :: (Applicative t) => t (Integer, v) -> TreeMap v -> t (Integer, v) precise closestSoFar Leaf = closestSoFar precise closestSoFar (Node k v l r) = case i `compare` k of LT -> precise closestSoFar l EQ -> pure (k, v) GT -> pure . runIdentity $ precise (Identity (k, v)) r 表明没有包装程序或字典。在这两个函子的特殊功能下,所有这些在类型级别上都被删除了:

> isJust $ closestLess 5 (Node 3 () Leaf undefined)
True

答案 1 :(得分:3)

从我的非延迟实现开始,我首先重构precise来接收Just作为参数,并相应地推广其类型:

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)

closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Just Nothing where
  precise :: ((Integer, v) -> t) -> t -> TreeMap v -> t
  precise _ closestSoFar Leaf = closestSoFar
  precise wrap closestSoFar (Node k v l r) = case i `compare` k of
    LT -> precise wrap closestSoFar l
    EQ -> wrap (k, v)
    GT -> precise wrap (wrap (k, v)) r

然后,我将其更改为尽早进行wrap,并在id情况下使用GT进行调用:

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) deriving (Show, Read, Eq, Ord)

closestLess :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess i = precise Just Nothing where
  precise :: ((Integer, v) -> t) -> t -> TreeMap v -> t
  precise _ closestSoFar Leaf = closestSoFar
  precise wrap closestSoFar (Node k v l r) = case i `compare` k of
    LT -> precise wrap closestSoFar l
    EQ -> wrap (k, v)
    GT -> wrap (precise id (k, v) r)

除了增加了懒惰的好处外,这仍然与以前完全一样。

答案 2 :(得分:3)

我认为您自己回答的CPS版本是最好的,但是为了完整起见,这里还有其他一些想法。 (编辑:布尔的答案现在表现最好。)

第一个想法是摆脱“ closestSoFar”累加器,而让GT情况处理选择逻辑参数最小的最右值的所有逻辑。在这种形式下,GT情况可以直接返回Just

closestLess1 :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess1 _ Leaf = Nothing
closestLess1 i (Node k v l r) =
  case i `compare` k of
    LT -> closestLess1 i l
    EQ -> Just (k, v)
    GT -> Just (fromMaybe (k, v) (closestLess1 i r))

这比较简单,但是当您遇到许多GT案例时,会在堆栈上占用更多空间。从技术上讲,您甚至可以以累加器形式使用该fromMaybe(即,替换luqui答案中隐含的fromJust),但这将是一个冗余的,无法访问的分支。

另一种想法是算法实际上有两个“阶段”,一个是在碰到GT之前,一个是在之后,因此您通过布尔参数化来表示这两个阶段,并使用相关类型进行编码在第二阶段总会有结果的不变性。

data SBool (b :: Bool) where
  STrue :: SBool 'True
  SFalse :: SBool 'False

type family MaybeUnless (b :: Bool) a where
  MaybeUnless 'True a = a
  MaybeUnless 'False a = Maybe a

ret :: SBool b -> a -> MaybeUnless b a
ret SFalse = Just
ret STrue = id

closestLess2 :: Integer -> TreeMap v -> Maybe (Integer, v)
closestLess2 i = precise SFalse Nothing where
  precise :: SBool b -> MaybeUnless b (Integer, v) -> TreeMap v -> MaybeUnless b (Integer, v)
  precise _ closestSoFar Leaf = closestSoFar
  precise b closestSoFar (Node k v l r) = case i `compare` k of
    LT -> precise b closestSoFar l
    EQ -> ret b (k, v)
    GT -> ret b (precise STrue (k, v) r)

答案 3 :(得分:2)

怎么样

GT -> let Just v = precise (Just (k,v) r) in Just v

答案 4 :(得分:1)

我们不仅始终了解Just,在的第一个发现之后,我们还始终了解Nothing,直到 。这实际上是两个不同的“逻辑”。

所以,我们首先走左,所以使那个明确:

data TreeMap v = Leaf | Node Integer v (TreeMap v) (TreeMap v) 
                 deriving (Show, Read, Eq, Ord)

closestLess :: Integer 
            -> TreeMap v 
            -> Maybe (Integer, v)
closestLess i = goLeft 
  where
  goLeft :: TreeMap v -> Maybe (Integer, v)
  goLeft n@(Node k v l _) = case i `compare` k of
          LT -> goLeft l
          _  -> Just (precise (k, v) n)
  goLeft Leaf = Nothing

  -- no more maybe if we're here
  precise :: (Integer, v) -> TreeMap v -> (Integer, v)
  precise closestSoFar Leaf           = closestSoFar
  precise closestSoFar (Node k v l r) = case i `compare` k of
        LT -> precise closestSoFar l
        EQ -> (k, v)
        GT -> precise (k, v) r

价格是我们最多重复一次 步骤一次。