【问题标题】:Scheme: Implementing n-argument compose using fold方案:使用折叠实现 n 参数组合
【发布时间】:2010-12-14 04:07:11
【问题描述】:

我正在尝试在 Scheme 中找到多参数“compose”的“最佳”实现(我知道它在某些实现中是内置的,但目前假设我使用的是没有这个的) .

对于 2 参数 compose 函数,我有这个:

(define compose
  (lambda (f g)
    (lambda x
      (f (apply g x)))))

这样做的好处是,如果最右边的函数需要额外的参数,这些仍然可以通过组合函数传递。这具有令人愉悦的特性,即在某物之上组合身份函数不会改变函数。

例如:

(define identity
  (lambda (x) x))

(define list1
  (compose identity list))

(define list2
  (compose identity list1))

(list2 1 2 3)
> (1 2 3)

现在要做一个“n-argument”组合我可以这样做:

(define compose-n
  (lambda args
    (foldr compose identity args)))

((compose-n car cdr cdr) '(1 2 3))
> 3

但这不再保留那个漂亮的“身份”属性:

((compose-n identity list) 1 2 3)
> procedure identity: expects 1 argument, given 3: 1 2 3

问题是用于 foldr 命令的“初始”函数。它已经建成:

(compose identity (compose list identity))

所以...我不确定解决这个问题的最佳方法。 “foldl”似乎是自然更好的选择,因为我希望它以 left 上的“identity”而不是 right...

但是一个幼稚的实现:

(define compose-n
  (lambda args
    (foldl compose identity args)))

哪个有效(必须颠倒功能应用的顺序):

((compose-n cdr cdr car) '(1 2 3))
> 3

没有解决问题,因为现在我不得不把标识函数放在左边!

((compose-n cdr cdr car) '(1 2 3))
> procedure identity: expects 1 argument, given 3: 1 2 3

就像,我需要使用“foldr”,但需要一些不同于标识函数的“初始”值......还是更好的标识函数?显然我在这里很困惑!

我想实现它 不必编写显式的尾递归“循环”......似乎应该有一种优雅的方式来做到这一点,我只是卡住了。

【问题讨论】:

    标签: functional-programming scheme


    【解决方案1】:

    OP 提到(在对我的回答的评论中)他的 Scheme 实现没有call-with-values。这是一种伪造它的方法(如果您可以确保在您的程序中永远不会使用 <values> 符号:您可以将其替换为 (void)(if #f #f) 或任何您喜欢的未使用的符号,并且支持你的实现):

    (define (values . items)
      (cons '<values> items))
    
    (define (call-with-values source sink)
      (let ((val (source)))
        (if (and (pair? val) (eq? (car val) '<values>))
            (apply sink (cdr val))
          (sink val))))
    

    它的作用是伪造一个带有以&lt;values&gt; 符号为首的列表的多值对象。在call-with-values 站点,它会检​​查该符号是否存在,如果不存在,则将其视为单个值。

    如果你的链中最左边的函数可能返回一个多值,你的调用代码必须准备解压&lt;values&gt;-headed 列表。 (当然,如果您的实现没有多个值,那么您可能不会很担心。)

    【讨论】:

      【解决方案2】:

      您可能想尝试this version(使用来自SRFI 1reduce):

      (define (compose . fns)
        (define (make-chain fn chain)
          (lambda args
            (call-with-values (lambda () (apply fn args)) chain)))
        (reduce make-chain values fns))
      

      这不是火箭科学:当我在#scheme IRC 频道上发布此内容时,Eli 指出这是compose 的标准实现。 :-)(作为奖励,它也适用于您的示例。)

      【讨论】:

      • Dirk's answer(已删除)有一个正确的想法:只需使用values 而不是identity。这实际上是我实现compose 漏洞利用的方法:(compose) 只是返回values
      • 谢谢!我现在唯一的问题是我使用的方案解释器不支持带值调用...有没有办法在现有方案之上实现“值”和“带值调用”?
      • 我已经开发出一种伪造valuescall-with-values 的方法:即将发布新帖子。 :-)
      【解决方案3】:

      这里的问题是您正在尝试混合不同数量的程序。你可能想要 curry list 然后这样做:

      (((compose-n (curry list) identity) 1) 2 3)
      

      但这并不是很令人满意。

      你可以考虑一个 n 元恒等函数:

      (define id-n
        (lambda xs xs))
      

      然后您可以创建一个专门用于组合 n 元函数的组合过程:

      (define compose-nary
        (lambda (f g)
          (lambda x
            (flatten (f (g x))))))
      

      用以下方式组合任意数量的 n 元函数:

      (define compose-n-nary
        (lambda args
          (foldr compose-nary id-n args)))
      

      哪个有效:

      > ((compose-n-nary id-n list) 1 2 3)
      (1 2 3)
      

      编辑:从类型的角度来思考是有帮助的。让我们为我们的目的发明一个类型符号。我们将把对的类型表示为(A . B),将列表的类型表示为[*],约定[*] 等价于(A . [*]),其中Acar 的类型列表(即列表是一对原子和列表)。让我们进一步将函数表示为(A =&gt; B),意思是“接受一个 A 并返回一个 B”。 =&gt;. 都关联到右侧,所以 (A . B . C) 等于 (A . (B . C))

      那么...鉴于此,这里是 list 的类型(将 :: 读作“有类型”):

      list :: (A . B) => (A . B)
      

      这是身份:

      identity :: A => A
      

      种类不同。 list 的类型由两个元素构成(即列表的类型具有种类 * =&gt; * =&gt; *),而 identity 的类型由一种类型构成(身份的类型具有种类 * =&gt; *)。

      Composition 有这种类型:

      compose :: ((A => B).(C => A)) => C => B
      

      查看将compose 应用于listidentity 时会发生什么。 Alist 函数的域统一,因此它必须是一对(或空列表,但我们将忽略它)。 Cidentity 函数的域统一,所以它必须是一个原子。那么,两者的组合必须是一个函数,它接受一个原子C 并产生一个列表B。如果我们只给这个函数原子,这不是问题,但如果我们给它列表,它会窒息,因为它只需要一个参数。

      咖喱的作用如下:

      curry :: ((A . B) => C) => A => B => C
      

      curry 应用到list,您可以看到会发生什么。 list 的输入与 (A . B) 统一。结果函数接受一个原子(汽车)并返回一个函数。该函数依次获取列表的其余部分(B 类型的 cdr),并最终生成列表。

      重要的是,柯里化的list 函数与identity 属于同一类,因此它们可以毫无问题地组合。这也适用于另一种方式。如果您创建一个接受对的恒等函数,它可以与常规 list 函数组合。

      【讨论】:

      • 我不确定你的 curry 函数是做什么的......我能想到的任何“curry”定义都有“(curry list)”正在创建一个与原始函数完全相同的函数“list”函数确实......我的定义:(定义咖喱(lambda(func。args)(lambda剩余(应用func(append args剩余))))((咖喱列表)1 2 3)......还有,关键是我希望我的“撰写”功能适用于“普通”情况,例如: ((compose-n-nary car cdr) '(2 3 4)) => 3
      • Curry 接受一个函数,该函数需要 n 个参数给一个接受 1 个参数的函数,并返回另一个接受其余参数的函数。 IE。将 n 元函数转换为一元函数(这是 compose 所期望的)。请注意,compose-n 和 compose-n-nary 是两种不同的东西。前者采用一元函数列表,后者采用 n 元函数列表。
      • 那么 (curry list) 有什么意义呢?肯定至少需要一个更多的参数来咖喱?
      • 如果你能给我一个例子来说明“compose-nary”函数的作用,这将有助于我理解它到底应该做什么
      • 您对 curry 的实际定义是什么?...我认为您所说的是可以返回事物“列表”而不仅仅是单个事物的函数...
      【解决方案4】:

      虽然“空”列表转移到身份功能会很好,但放弃它似乎会导致以下结果,这还不错:

      (define compose-n
        (lambda (first . rest)
          (foldl compose first rest)))
      
      ((compose-n cdr cdr car) '(1 2 3))
      
      ((compose-n list identity identity) 1 2 3)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-11
        • 2011-11-01
        相关资源
        最近更新 更多