【问题标题】:What else can `loeb` function be used for?`loeb` 函数还能用来做什么?
【发布时间】:2023-03-17 07:27:01
【问题描述】:

我试图理解"Löb and möb: strange loops in Haskell",但现在意义正在远离我,我只是不明白它为什么有用。只是为了回忆函数loeb被定义为

loeb :: Functor f => f (f a -> a) -> f a
loeb x = go where go = fmap ($ go) x

或等效:

loeb x = go 
  where go = fmap (\z -> z go) x

在文章中,有一个 [] 函子和电子表格实现的示例,但它对我来说有点陌生,就像电子表格本身一样(从未使用过它们)。

虽然我了解电子表格的内容,但我认为尽管列出了更多示例,但对我和其他人会有很大帮助。 loeb Maybe 或其他仿函数是否有任何申请?

【问题讨论】:

  • 当你尝试使用 'Maybe' 使用 'loeb' 时,你会发现只有两种简单的方法可以做到这一点。或者你可以尝试定义斐波那契数列,[Integer]...
  • 如果函子也是可应用的,那么根据交换律loeb g = x where x = g <*> pure x。让我们想起了fix f = x where x = f $ x

标签: haskell functor curry-howard


【解决方案1】:

loeb 的主要来源(我认为)是Dan Piponi's blog, A Neighborhood of Infinity。他在那里更详细地解释了整个概念。我将复制一点作为答案并添加一些示例。

loeb 实现了一种奇怪的惰性递归

loeb :: Functor a => a (a x -> x) -> a x
loeb x = fmap (\a -> a (loeb x)) x

假设我们有一个类型a,其中Functor a,和一个a-代数(a x -> x 类型的函数)。您可能会认为这是从值结构中计算值的一种方式。例如,这里有一些[]-algebras:

length                ::          [Int] -> Int
(!! 3)                ::          [a]   -> a
const 3               :: Num a => [a]   -> a
\l -> l !! 2 + l !! 3 :: Num a => [a]   -> a

我们可以看到,这些a-代数既可以使用Functor中存储的值,也可以使用Functor本身的结构。

d :: a x -> x 视为x 的值的另一种方式是需要一些上下文——一个完整的Functor 化值a x——才能被计算。也许这种解释写成Reader (a x) x 更清楚,强调这只是x 的值,它是延迟的,等待生成a x 上下文。

type Delay q x = q -> x

使用这些想法,我们可以将loeb 描述如下。我们得到了一个f-结构,其中包含一些Delayed 值,其中fFunctor

Functor f, f (Delay q x)

当然,如果我们得到q,那么我们可以将其转换为不延迟的形式。事实上,只有一个(非作弊)函数可以多态地做到这一点:

force :: Functor f => f (Delay q x) -> q -> f x
force f q = fmap ($ q) f

loeb 所做的是处理额外的棘手情况,其中q 实际上是force f q,这正是这个函数的结果。如果您熟悉fix,这正是我们产生此结果的方式。

loeb :: Functor a => a (Delay (a x) x) -> a x
loeb f = fix (force f)

所以举个例子,我们只需要构建一个包含Delayed 值的结构。一个自然的例子是使用之前的列表示例

> loeb [ length                  :: [Int] -> Int
       , const 3                 :: [Int] -> Int
       , const 5                 :: [Int] -> Int
       , (!! 2)                  :: [Int] -> Int
       , (\l -> l !! 2 + l !! 3) :: [Int] -> Int
       ]
[5, 3, 5, 5, 10]

在这里我们可以看到列表中充满了延迟等待评估列表结果的值。这个计算可以准确地进行,因为数据依赖没有循环,所以整个事情可以懒惰地确定。例如,const 3const 5 都可以立即作为值使用。 length 要求我们知道列表的长度,但不包含任何值,因此它也立即在我们的固定长度列表上继续。有趣的是值延迟等待我们的结果列表中的其他值,但由于(!! 2) 仅最终取决于结果列表的第三个值,该值由const 5 确定,因此可以立即可用,计算向前推进。 (\l -> l !! 2 + l !! 3) 也有同样的想法。


所以你有它:loeb 完成了这种奇怪的延迟值递归。不过,我们可以在任何类型的 Functor 上使用它。我们需要做的就是想出一些有用的Delayed 值。


Chris Kuklewicz 的评论指出,使用Maybe 作为函子,您可以做的事情并不多。那是因为Maybe 上的所有延迟值都采用这种形式

maybe (default :: a) (f :: a -> a) :: Maybe a -> a

并且Maybe (Delay (Maybe a) a) 的所有有趣值都应该是Just (maybe default f),因为loeb Nothing = Nothing。因此,归根结底,default 的值甚至从未被使用过——我们总是拥有它

loeb (Just (maybe default f)) == fix f

所以我们还是直接写吧。

【讨论】:

  • Maybe a -> a 是一个有趣的 default-value-代数,与用于实现的 difference semigroup data Diff a = Diff (a -> a) a 同构折叠非空列表..我不知道它是否有用或有趣,但希望以其他方式显示
【解决方案2】:

您可以将其用于动态编程。想到的例子是Smith-Waterman algorithm

import Data.Array
import Data.List
import Control.Monad

data Base = T | C | A | G deriving (Eq,Show)
data Diff = Sub Base Base | Id Base | Del Base | Ins Base deriving (Eq,Show)

loeb x = let go = fmap ($ go) x in go

s a b = if a == b then 1 else 0

smithWaterman a' b' = let
  [al,bl] = map length [a',b']
  [a,b] = zipWith (\l s -> array (1,s) $ zip [1..] l) [a',b'] [al,bl]
  h = loeb $ array ((0,0),(al,bl)) $
    [((x,0),const 0) | x <- [0 .. al]] ++
    [((0,y),const 0) | y <- [1 .. bl]] ++
    [((x,y),\h' -> maximum [
       0,
       (h' ! (x - 1,y - 1)) + s (a ! x) (b ! y),
       (h' ! (x - 1, y)) + 1,
       (h' ! (x, y - 1)) + 1
      ]
     ) | x <- [1 .. al], y <- [1 .. bl]]
  ml l (0,0) = l
  ml l (x,0) = ml (Del (a ! x): l) (x - 1, 0)
  ml l (0,y) = ml (Ins (b ! y): l) (0, y - 1)
  ml l (x,y) = let
    (p,e) = maximumBy ((`ap` snd) . (. fst) . (const .) . (. (h !)) . compare . (h !) . fst) [
      ((x - 1,y),Del (a ! x)),
      ((y, x - 1),Ins (b ! y)),
      ((y - 1, x - 1),if a ! x == b ! y then Id (a ! x) else Sub (a ! x) (b ! y))
     ]
    in ml (e : l) p
  in ml [] (al,bl)

【讨论】:

    【解决方案3】:

    这是一个活生生的例子,它用于:Map String Float

    http://tryplayg.herokuapp.com/try/spreadsheet.hs/edit

    带有循环检测和循环解析。

    这个程序计算速度、时间和空间。每一个都依赖于其他两个。每个单元格有两个值:他当前输入的值和作为其他单元格值/表达式函数的表达式。允许循环。

    Cell 重新计算代码使用了 Dan Piponi 在 2006 年著名的 loeb 表达式。据我所知,到目前为止,该公式还没有在真正的工作电子表格中实现。这个离它很近。由于 loeb 在使用循环表达式时会进入无限循环,因此程序会计算循环并通过逐步用单元格值替换公式来降低复杂度,直到表达式没有循环

    此程序配置为在单元格更改时立即重新计算,但可以调整为允许在重新计算之前修改多个单元格,方法是通过按钮触发它。

    这是博文:

    http://haskell-web.blogspot.com.es/2014/09/spreadsheet-like-program-in-browser.html

    【讨论】:

      猜你喜欢
      • 2010-10-03
      • 2022-07-13
      • 1970-01-01
      • 2011-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-12
      • 1970-01-01
      相关资源
      最近更新 更多