【问题标题】:How to implement uncurry point-free in Haskell without app?如何在没有应用程序的情况下在 Haskell 中实现 uncurry point-free?
【发布时间】:2020-06-16 07:56:06
【问题描述】:

我一直想知道如何无点实现不同的标准 Haskell 函数。目前,我对uncurry 很感兴趣,我觉得这个很重要。

主要问题是我们无法(或在我看来)对论点进行分组。如果我们使用uncurry(实际上,uncurry ($) 就足够了),那么解决方案将非常简单:

  1. 创建一个元组(f, (x, y))
  2. assoc1 :: (a, (b, c)) -> ((a, b), c) 应用于元组并获得((f, x), y)
  3. 将未咖喱化的($) 应用于该对的第一个元素并得到(f x, y)
  4. 将未咖喱化的($) 应用于该对本身并得到f x y

如果没有未经处理的($),我们将不得不分别提取这对的两个元素。例如:

uncurry f pair = f (fst pair) (snd pair)

我不认为这是实现无点的顺利方法。

事实上,我们已经按照我们的要求得到了这个未经处理的($)Control.Arrow.apply(其他对解决方案组合器有用的也可以从Control.Arrow 导入)。因此:

import Control.Arrow ((>>>), (&&&), first, app)

myUncurry = let myAssoc1 = (fst &&& (fst . snd)) &&& (snd . snd)
            in (,) >>> (>>> myAssoc1 >>> first app >>> app) 

然而,这感觉有点像作弊。

有没有其他方法可以解决这个问题,不需要像app 这样的东西?

【问题讨论】:

  • 我目前看不到任何更简单的解决方案。此外,“无积分”游戏的规则有些模糊,因为它取决于允许使用哪些库原语。当我编写无点代码时,我倾向于使用curryuncurry 作为原语。
  • 我认为有一个很好的原因 uncurry 通常包括在内。我们使事情变得毫无意义,将它们带入了profunctors的土地。 curryuncurry 在这种情况下是非常简单的适配器。
  • 你可以用(<*>)const 来做所有事情(偶尔fix 用于类型检查)(当然还有你正在使用的任何数据类型的构造函数/消除器)。从技术上讲,您甚至不需要(.)。结果通常不是很漂亮,所以像uncurry 这样的附加组合基本上是为了美化。
  • @ZhiltsoffIgor (<*>)constSK 中的 SKI combinators,已知它们是图灵完备的。因此,您可以以任何可以想象的方式将功能与它们组合在一起,这是有道理的。正如 luqui 所说(这里是 fstsnd(,)),您确实需要针对您正在使用的类型的构造函数和消除器,但是您可以使用 SKI 以任何您喜欢的方式来玩弄它们。
  • 同一个底层结构有各种不同的表达方式:uncurry f pair = f (fst pair) (snd pair) = uncurry f = f <$> fst <*> snd = uncurry f = liftA2 f fst snd = uncurry = (<*> snd) . (<$> fst) = uncurry = flip (flip liftA2 fst) snd = ...

标签: haskell tuples currying arrows pointfree


【解决方案1】:

join on functions 给你(a -> a -> b) -> a -> b,所以:

myUncurry f = join (\x y -> f (fst x) (snd y))
myUncurry f = join (\x -> f (fst x) . snd)
myUncurry f = join ((.snd) . f . fst)
myUncurry f = join ((.fst) ((.snd) . f))
myUncurry f = join ((.fst) ((.) (.snd) f))
myUncurry = join . (.fst) . \f -> (.) (.snd) f
myUncurry = join . (.fst) . ((.snd).)

join . (.fst) . ((.snd).) 确实可读性很强

【讨论】:

  • 谢谢!说实话,我以前从未听说过join。看起来很酷。
  • 在范畴论中,join 是 monad 定义的基本部分,你可以实现 m >>= f = join (fmap f m)。在 Haskell 中,(>>=) 是基本组件,join m = m >>= id。它的泛型类型签名是Monad m => m (m a) -> m a,所以当m ~ (->) a,你得到((->) a) (((->) a) b) -> ((->) a) b,或者(a -> a -> b) -> a -> b
【解决方案2】:

简单的机械解决方案,"pushing lambdas inward"

uncurry f (x,y) = f x y
uncurry f p = f (fst p) (snd p)
uncurry f = \p -> f (fst p) (snd p)
uncurry f = (<*>) (\p -> f (fst p)) (\p -> snd p)
uncurry f = (<*>) (f . fst) snd
uncurry = \f -> (<*>) (f . fst) snd
uncurry = flip (\f -> (<*>) (f . fst)) snd
uncurry = flip ((<*>) . (\f -> f . fst)) snd
uncurry = flip ((<*>) . (. fst)) snd

【讨论】:

  • aha,因此将其描述为 deterministic 算法更加简单,它不会对结果的长度提供任何保证。 :)
  • @WillNess,结果的增长速度不会超过消除变量数量的指数增长;)
  • (ISTR 一些结果,使用任何 fixed 组合子集将以这种方式呈指数增长)
  • 我猜在数学中指数已经足够短了。 :)
【解决方案3】:

使用 Lambda 微积分的 S 组合器,Sabc = (a &lt;*&gt; b) c = a c $ b c

uncurry f (x,y)  =   f (fst (x,y)) (snd (x,y))
                 =  (f . fst  <*>  snd) (x,y)
uncurry f  =  (<*> snd) (f . fst)
           =  (<*> snd) . (. fst) $ f

因此,

uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry  =  (<*> snd) . (. fst)

编辑:

仍然保留了一个明确的论点,可读性更高(并且在某种程度上阐明),如上所示:

uncurry f  =  f . fst  <*>  snd

然后这个变体,由Jon Purdythe comments 中显示,

uncurry f  =  liftA2 f fst snd

可能是最清楚的。

这是因为对于函数来说,monad 和 applicative 是等价的,

(k =<< f) x  =  k (f x) x  =  flip k x (f x)  =  (flip k <*> f) x

-- i.e.,  uncurry f  =  flip (f . fst) =<< snd

liftA2 f fst snd 根据定义表示,

           =  [ f a b | a <- fst ; b <- snd ]
           = 
              do {            a   <- fst    ; 
                                b <- snd    ; 
                    return (f a b)
                 }
           =  \x -> let 
                 {            a   =  fst  x ; 
                                b =  snd  x ;
                 } 
                 in  const (f a b)        x

(第一个用 Monad Comprehensions 编写的)。因此,

uncurry f x  =  liftA2 f   fst    snd     x
             =  let 
                 {            a   =  fst  x ; 
                                b =  snd  x ;
                 } 
                 in         f a b
             =
                       f (fst x) (snd x)
             =
                     (f . fst <*> snd) x
             =
               (flip (f . fst) =<< snd) x
             =
                flip (f . fst)    (snd x) x
             =
               (flip (f . fst)  .  snd) x x
             =
          join (flip (f . fst)  .  snd)  x 
             =
          join (flip (f . fst) <$> snd)  x

遵循众所周知的equivalencek =&lt;&lt; m = join (fmap k m)(对于函数,(&lt;$&gt;) = fmap = (.))。

所以我们在这里找到了另一种表达方式,

uncurry f x  =  join (flip (f . fst) . snd)
             =  liftA2      f   fst    snd
             =              f . fst <*> snd
             =        flip (f . fst) =<< snd

liftA2 可能是最清晰、最不嘈杂的。

【讨论】:

    猜你喜欢
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 2021-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    相关资源
    最近更新 更多