【问题标题】:Point-free form versus style无点形式与风格
【发布时间】:2010-11-21 00:16:13
【问题描述】:

你能转换吗

-- tupleUnfold :: forall a. ((forall b. a -> b)) -> a -> ((b))
tupleUnfold :: Int -> ExpQ
tupleUnfold n = do
  xs <- forM [1 .. n] (const . newName $ "x")
  y <- newName "y"
  let y' = varE y
      g (ps', es') x = (varP x : ps', appE (varE x) y' : es')
      (ps, es) = foldl' g ([], []) xs
  lamE [tupP ps, varP y] (tupE es)

在保持清晰的同时采用无点样式(我知道程序“无点”,但更不想混淆代码)?

无论哪种方式,可以进行哪些更改来改进函数的样式,或者使其意图更清晰?该函数的用途如下。

$(tupleUnfold 3) ((+ 1), (+ 2), (+ 3)) 2
-- (3, 4, 5)

可以使用哪些更好的命名约定(参见 ps、ps'、es 和 es' 变量)?

【问题讨论】:

    标签: haskell coding-style pointfree


    【解决方案1】:

    这就是我得到的。需要Control.Arrow (&amp;&amp;&amp;)Control.Applicative (&lt;$&gt;)

    tupleUnfold :: Int -> ExpQ
    tupleUnfold n = do
        y <- newName "y"
        (ps,es) <- unzip . map (varP &&& (`appE` varE y) . varE) 
                    <$> replicateM n (newName "x")
        lamE [tupP ps, varP y] (tupE es)
    

    在不让它完全无法理解的情况下,不能再细化它了。

    编辑虽然不是免费的,但这是我能做到的最清晰。需要Data.Function (on)

    tupleUnfold :: Int -> ExpQ
    tupleUnfold n = do
        y <- newName "y"
        xs <- replicateM n (newName "x")
        let exps = tupE $ zipWith appVars xs (repeat y)
            pats = tupP $ map varP xs
        lamE [pats, varP y] exps
      where
        appVars = appE `on` varE
    

    【讨论】:

    • 我喜欢使用“replicateM”——不知道我是怎么错过的
    • 我有点武断地将这个标记为正确答案。虽然另一个在顶层是无点的,但这个似乎更好地代表了流程和转换 - 尽管它有点难以阅读。
    • 折叠(而不是地图)的部分原因是为了避免多次遍历。我也知道 GHC 非常擅长融合列表遍历。通常有显着差异吗?鉴于这两种形式都相对容易编写,(根据经验)应该首选哪种形式?
    • 为了提高效率,如果你无法摆脱有意义的计算共享,线性(每个参数只使用一次)通常是你应该争取的。所以我猜是折叠。但是,嗯,这是 TH 代码,它在 编译时tuples 的大小执行,即小于 62 并且更有可能...... 3. 这不完全是瓶颈。
    • 我想我的意思是作为一般规则。
    【解决方案2】:

    有点难以理解(尝试从右到左阅读):

    tupleUnfold n = do
      y <- newName "y"
      uncurry lamE . ((:[varP y]) . tupP *** tupE) . unzip .   
       map (varP &&& (`appE` varE y) . varE) <$> replicateM n (newName "x")
    

    编辑
    用于处理的箭头和函数组合的混合

    tupleUnfold n = do
      y <- newName "y"
      uncurry lamE . ((tupP >>> (:[varP y])) *** tupE) . unzip .
        map (varP &&& (varE >>> (`appE` varE y))) <$> replicateM n (newName "x")
    

    并且主要使用箭头(从左到右读取处理函数)

    tupleUnfold n = do
      y <- newName "y"
      (map (varP &&& (varE >>> (`appE` varE y))) >>> unzip >>>
        ((tupP >>> (:[varP y])) *** tupE) >>> uncurry lamE) <$> replicateM n (newName "x")
    

    注意箭头函数(>>>)等价于翻转(.)

    【讨论】:

      【解决方案3】:

      我个人认为已经很清楚了,但是这个怎么样:

      tupleUnfold :: Int -> ExpQ
      tupleUnfold = mapM (const . newName $ "x") . enumFromTo 1 >=> \xs -> do
          y <- newName "y"
          let y' = varE y
              g (ps', es') x = (varP x : ps', appE (varE x) y' : es')
              f ps = lamE [tupP ps, varP y] . tupE
          uncurry f $ foldl' g ([],[]) xs
      

      Kleisli 组合运算符&gt;=&gt;(来自 Control.Monad)对于创建无点单子函数很有用。

      【讨论】:

      • 我有一个顶级无点版本(使用 >=>),但最终不得不将其分解。很高兴知道它是相对清晰的 - 让我了解 Haskell 的大部分原因是可以重写多少种方法。
      • let 绑定在性能方面有多重要?这主要是为什么我有 y' - 我不确定表达式是否会被拉到外部范围。
      • 是的,如果没有 y',varE y 计算将不会被共享。但是那个计算可能只是委托给一个构造函数,所以它是无关紧要的(不要担心对构造函数的两次调用与一次调用,这只是让你的语言拥有你——你不会得到更快的代码,你'只会得到糟糕的代码)。 foldl' 中的素数是无用的,因为它是一个普通(惰性)元组。使用严格的元组data Tuple a b = Tuple !a !b。但由于这是类似流的代码,请改用foldr——它使用的内存逐渐减少。这基本上相当于我的回复中的unzip . map
      • 但同样,这不是优化的地方。让你的 Haskell 更漂亮。然后它会和你说话,很快你就会意识到你的代码库已经缩小了 75%。性能不是一切。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-30
      • 2016-01-01
      • 2011-03-20
      • 2023-01-04
      • 1970-01-01
      • 2010-10-09
      • 2013-10-07
      相关资源
      最近更新 更多