首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Haskell中扁平的元组(无点)

Haskell中扁平的元组(无点)
EN

Stack Overflow用户
提问于 2022-01-19 01:18:26
回答 3查看 128关注 0票数 2

我最近学到了Haskell,我正在编写一个简单的程序来对缩写词进行排序。基本上,我有一个缩略词列表--每个缩略词都是一对字符串,例如“DH”、“Diffie-Hellman”--我想对它们进行排序,并以以下格式打印它们(缩写,意为,索引)。

例如,如果

代码语言:javascript
复制
acronyms = [("ECDLP", "elliptic curve discrete logarithm problem"),
            ("DH", "Diffie-Hellman"),
            ("KDF", "key derivation function")]

那么输出应该是

代码语言:javascript
复制
("DH","Diffie-Hellman",1)
("ECDLP","Elliptic Curve Discrete Logarithm Problem",2)
("KDF","Key Derivation Function",3)

目前,我的代码如下。

代码语言:javascript
复制
main :: IO ()
main =
    mapM_
        print
        (zipWith
             (curry (\((a, b), c) -> (a, b, c)))
             (sort . nub $ map (capitalise <$>) acronyms)
             [1 ..])

在这里,大写是我写的一个函数,它用字符串中每个单词的第一个字符大写。总的来说,这个计划是有效的,但我想得到一些反馈,我如何可以改进它。最后,有什么方法可以让这个curry (\((a, b), c) -> (a, b, c))部件成为无点样式呢?

谢谢

EN

回答 3

Stack Overflow用户

发布于 2022-01-19 02:03:28

代码评审

首先,curry实际上只是增加了复杂性。您可以编写一个lambda,在没有它的情况下使用多个参数。而不是

代码语言:javascript
复制
curry (\((a, b), c) -> (a, b, c))

考虑一下

代码语言:javascript
复制
\(a, b) c -> (a, b, c)

现在,你似乎有了这样的想法:一切都应该是没有意义的。对于娱乐编程来说,这是一个很好的练习,在代码高尔夫中也很有用(目标是尽可能缩短代码),但是当您编写通用软件时,通常是一个糟糕的设计选择(目标是使代码尽可能地可读性)。因此,我建议引入一些局部变量和一些额外的函数来处理中间层。

我们可以将拉链部分分割成一个单独的函数。

代码语言:javascript
复制
enumerateAcronyms :: [(a, b)] -> [(a, b, Int)]
enumerateAcronyms xs = zipWith (\(a, b) c -> (a, b, c)) xs [1..]

注意,虽然我们使用这个函数作为[(String, String)] -> [(String, String, Int)],但我将它写成[(a, b)] -> [(a, b, Int)]。这种更一般的类型为我们的输入提供了非常有力的保证。也就是说,我们不会修改元组的前两个元素;我们要做的就是移动它们并将整数与它们关联起来。

Functor实例用于(,) a是很奇怪的。我花了一分钟才意识到,(capitalize <$>)的意思是“对元组的第二个元素执行它”。所以你要么显式地这么做

代码语言:javascript
复制
\(x, y) -> (x, capitalize y)

或者用一个更符合你意图的名字。在这种情况下,我们实际上有两个选择:Control.Arrow.secondData.Bifunctor.second。它们做同样的事情,并且简单地基于不同的抽象(前者抽象函数类型,后者抽象数据结构)。

代码语言:javascript
复制
second capitalize

我个人的偏好是,永远不要使用特定于列表的函数,比如map,并且总是使用通用函数式fmap。这样,如果您想将此函数扩展为泛型函数,并对序列或其他数据结构进行处理,那么您所要做的工作就少了。

nub是O(n^2),如果要保持列表的顺序,这是很好的。但是如果你对列表进行排序,我们可以通过自己滚动得到它在O(n)中。因此,让我们编写我们自己的nub

同样,我可以将需要mapM_Monad替换为相同的traverse_,后者只需要Applicative,因此可以在更一般的情况下工作。

最后,我不太喜欢把长的操作序列链接在一起。有些人这样做,但我不喜欢从右向左阅读我的代码,而不是

代码语言:javascript
复制
main :: IO ()
main = traverse_ print (enumerateAcronyms (uniqSort $ map (second capitalise) acronyms))

我可能会写

代码语言:javascript
复制
main :: IO ()
main =
  let uniqAcronyms = uniqSort $ map (second capitalise) acronyms
  in traverse_ print (enumerateAcronyms uniqAcronyms)

就像这样的东西

代码语言:javascript
复制
import Data.Char
import Data.List
import Data.Bifunctor
import Data.Foldable

acronyms :: [(String, String)]
acronyms = [("ECDLP", "elliptic curve discrete logarithm problem"),
            ("DH", "Diffie-Hellman"),
            ("KDF", "key derivation function")]

capitaliseFirst :: String -> String
capitaliseFirst [] = []
capitaliseFirst (x:xs) = toUpper x : xs

capitalise :: String -> String
capitalise = unwords . fmap capitaliseFirst . words

enumerateAcronyms :: [(a, b)] -> [(a, b, Int)]
enumerateAcronyms xs = zipWith (\(a, b) c -> (a, b, c)) xs [1..]

uniqSort :: Ord a => [a] -> [a]
uniqSort = go . sort
    where go [] = []
          go [x] = [x]
          go (x:x':xs)
             | x == x' = go (x':xs)
             | otherwise = x : go (x':xs)

main :: IO ()
main =
  let uniqAcronyms = uniqSort $ map (second capitalise) acronyms
  in traverse_ print (enumerateAcronyms uniqAcronyms)

这比您所写的要长得多,但是对于一般的Haskell程序员来说,它也会更加清晰。在阅读任何特定的函数时,需要记住的代码要少得多,所以代码更容易分解。如果您来自Java或其他一些语言,那么您已经看到了它的另一面:代码过于冗长,被分割成10个文件,这使得您很难看到正在发生的事情。但是在Haskell中,我们遇到了相反的问题:代码可能太短,太时髦,很难推理。中间有一种快乐的媒介。

无点位

现在,回答您最初的问题:我们可以不使用curry (\((a, b), c) -> (a, b, c))吗?我不建议在生产代码中这样做,因为它绝对是不可读的,但是让我们来探讨一下。首先,如前所述,curry是不必要的。这只是

代码语言:javascript
复制
\(a, b) c -> (a, b, c)

这就是

代码语言:javascript
复制
\(a, b) c -> (,,) a b c

然后就是移动变量和去掉元组的问题。c可以被消除,因此

代码语言:javascript
复制
\(a, b) -> (,,) a b

现在我们在左边有两个元组,右边有两个参数。两者之间的区别是uncurry,因此

代码语言:javascript
复制
\(a, b) -> uncurry (,,) (a, b)

然后我们消除最后一个元素

代码语言:javascript
复制
uncurry (,,)

但当然,我不知道这是什么一目了然,而\(a, b) c -> (a, b, c)是相当不言自明的。

一般来说,只要我们对所使用的任何数据结构都有变质作用 (这是maybe表示Maybe,或者foldr表示列表),以及序曲中的一些基本的生活质量函数,我们就可以不使用任何Haskell函数。您可以使用其他技巧,如读取器monad或liftA2,使事情变得更简单或更短,但从根本上说,您不需要这样的东西。这只是一个能够围绕各种Haskell语法元素移动变量的问题。您可以将递归转换为fix,模式匹配转换为变形,并将处于“错误”顺序的参数转换为几个flip调用。

兰达博特过去能够使用这种系统方法自动释放大多数Haskell表达式。我不确定它是否还在附近。

票数 3
EN

Stack Overflow用户

发布于 2022-01-19 05:31:16

并行列表理解完全消除了元组(zipWithcurry、lambda和(<$>))的模糊,我认为这使它更加可读性更强。另外,由于您正在排序,所以您不需要nub --只需对相邻的等号元素进行group就足够了。所以:

代码语言:javascript
复制
{-# Language ParallelListComp #-}

main = mapM_ print
    [ (acronym, capitalise meaning, i)
    | (acronym, meaning):_ <- group (sort acronyms)
    | i <- [1..]
    ]
票数 2
EN

Stack Overflow用户

发布于 2022-01-19 01:35:51

我会先把它弄得更少--没有任何意义!curry是个烂摊子,什么都没做。只需编写一个lambda,它直接将元组与索引结合起来。

代码语言:javascript
复制
main :: IO ()
main = mapM_ print . zipWith output [1 ..] . sort . nub . map (fmap capitalise) $ acronyms
  where output idx (acronym, meaning) = (acronym, meaning, idx)

这很好,但我认为元组的fmap实例令人惊讶,在代码中,我希望其他人阅读,我也会命名该函数:

代码语言:javascript
复制
main :: IO ()
main = mapM_ print . zipWith output [1 ..] . sort . nub . map capitaliseDefinition $ acronyms
  where output idx (acronym, meaning) = (acronym, meaning, idx)
        capitaliseDefinition (acronym, meaning) = (acronym, capitalise meaning)

您还可以更加现代化,使用traverse_而不是mapM_。我很同情那些粘在mapM_上的人--这个名字更明显地表明了它的作用。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70764269

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档