(受我对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
中都包含了核心逻辑。我该怎么写,以免偷懒而不重复自己?
答案 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
价格是我们最多重复一次 步骤一次。