简体   繁体   English

在Haskell中没有点

[英]Point-free in Haskell

I have this code that I want to make point-free; 我有这个代码,我想做点无关;

(\\kt -> chr $ a + flip mod 26 (ord k + ord t -2*a))

How do I do that? 我怎么做?

Also are there some general rules for point free style other than "think about this amd come up with something"? 除了“想想这个和某些东西”之外,还有一些关于点自由风格的一般规则吗?

To turn a function 转一个功能

func x y z = (some expression in x, y and z)

into point-free form, I generally try to follow what is done to the last parameter z and write the function as 对于无点形式,我通常会尝试按照对最后一个参数z所做的操作并将函数写为

func x y z = (some function pipeline built using x and y) z

Then I can cancel out the z s to get 然后我可以取消z s来获取

func x y = (some function pipeline built using x and y)

Then repeating the process for y and x should end up with func in point-free form. 然后重复y和x的过程应该以无点形式结束func An essential transformation to recognise in this process is: 在这个过程中认识到的一个重要转变是:

    f z = foo $ bar z    -- or f z = foo (bar z)
<=> f z = foo . bar $ z
<=> f   = foo . bar

It's also important to remember that with partial evaluation, you can "break off" the last argument to a function: 同样重要的是要记住,通过部分评估,您可以“中断”函数的最后一个参数:

foo $ bar x y == foo . bar x $ y    -- foo applied to ((bar x) applied to y)

For your particular function, consider the flow that k and t go through: 对于您的特定功能,请考虑kt经历的流程:

  1. Apply ord to each of them ord应用于每个人
  2. Add the results 添加结果
  3. Subtract 2*a 减去2 * a
  4. Take the result mod 26 取结果mod 26
  5. Add a 添加一个
  6. Apply chr 申请chr

So as a first attempt at simplifying, we get: 因此,作为简化的第一次尝试,我们得到:

func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ord k + ord t

Note that you can avoid flip by using a section on mod , and sections using - get messy in Haskell so there's a subtract function (they clash with the syntax for writing negative numbers: (-2) means negative 2, and isn't the same as subtract 2 ). 请注意,您可以通过使用mod上的部分来避免flip ,并且在Haskell中使用- get messy部分,因此有一个subtract函数(它们与写入负数的语法冲突:( - (-2)表示负数2,而不是与subtract 2 )相同。

In this function, ord k + ord t is an excellent candidate for using Data.Function.on ( link ). 在这个函数中, ord k + ord t是使用Data.Function.onlink )的绝佳候选者。 This useful combinator lets us replace ord k + ord t with a function applied to k and t : 这个有用的组合子让我们用一个应用于kt的函数替换ord k + ord t

func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ((+) `on` ord) k t

We're now very close to having 我们现在非常接近

func k t = (function pipeline) k t

and hence 因此

func = (function pipeline)

Unfortunately Haskell is a bit messy when it comes to composing a binary function with a sequence of unary functions, but there is a trick (I'll see if I can find a good reference for it), and we end up with: 不幸的是Haskell在用一系列一元函数组成二进制函数时有点乱,但是有一个技巧(我会看看我是否可以找到一个很好的参考),我们最终得到:

import Data.Function (on)

func = ((chr . (+a) . (`mod` 26) . subtract (2*a)) .) . ((+) `on` ord)

which is almost a nice neat point-free function pipeline, except for that ugly composing trick. 除了那个丑陋的构图技巧之外,这几乎是一个很好的简洁无点功能管道。 By defining the .: operator suggested in the comments on this page , this tidies up a little to: 通过定义本页评论中建议的.:运算符,这可以整理一下:

import Data.Function (on)

(.:) = (.).(.)

func = (chr . (+a) . (`mod` 26) . subtract (2*a)) .: ((+) `on` ord)

To polish this some more, you could add some helper functions to separate the letter <-> Int conversion from the Caesar cipher arithmetic. 为了更好地完善这一点,您可以添加一些辅助函数来将字母< - > Int转换与Caesar密码算术分开。 For example: letterToInt = subtract a . ord 例如: letterToInt = subtract a . ord letterToInt = subtract a . ord

Also are there some general rules for point free style other than "think about this amd come up with something"? 除了“想想这个和某些东西”之外,还有一些关于点自由风格的一般规则吗?

You can always cheat and use the "pl" tool from lambdabot (either by going to #haskell on freenode or by using eg ghci on acid ). 你总是可以欺骗并使用lambdabot中的“​​pl”工具(通过freenode上的#haskell或者使用例如酸的ghci )。 For your code pl gives: 对于你的代码pl给出:

((chr . (a +) . flip mod 26) .) . flip flip (2 * a) . ((-) .) . (. ord) . (+) . ord

Which isn't really an improvement if you ask me. 如果你问我,这不是一个真正的改进。

I am assuming that the point of your point-freeing is to make the code more concise and more readable. 我假设你的重点是让代码更简洁,更易读。 I therefore think that it is wise to also do some other refactorings towards simplification which then might make it easier to remove the variables. 因此,我认为对简化进行一些其他重构也是明智的,这样可以更容易地删除变量。

(\k t -> chr $ a + flip mod 26 (ord k + ord t - 2*a))

First of all, the flip is unnecessary: 首先, flip是不必要的:

(\k t -> chr $ a + (ord k + ord t - 2*a) `mod` 26)

Next, I would use name and conquer to factor out an independently usable subfunction: 接下来,我将使用名称和征服来分解一个独立可用的子功能:

encode_characters k t = chr $ encode (ord k) (ord t)
encode x y = (x + y - 2*a) `mod` 26 + a

I also gave a name to the first expression to make it clearer and reusable. 我还给第一个表达式命名,使其更清晰,可重用。 encode_characters is now easy to make point-free using the technique from @Nefrubyr: encode_characters现在很容易利用@Nefrubyr技术进行免费点:

encode_characters = chr . encode `on` ord

As for the second expression, I cannot produce a form that's more readable than any shown in the other answers and they're all less readable than the point-wise form. 至于第二个表达式,我不能生成一个比其他答案中显示的更可读的表单,并且它们的可读性都低于逐点表单。 I would therefore suggest to stop refactoring at this point and admire the cleanliness and reusability of the resulting code. 因此,我建议在此时停止重构,并钦佩最终代码的清洁度和可重用性。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~

PS: as an exercise, depending on the context of the problem, some slight modification of the function interfaces (what data in what form is passed into the functions) might yield more simplifications by generalizing the problem. PS:作为练习,根据问题的上下文,对函数接口进行一些微小的修改(以什么形式传递给函数的数据)可以通过概括问题来产生更多的简化。

A. Implement and simplify function encode_n_characters :: [Char] -> Char where encode_characters kt = encode_n_characters [k, t] . A.实现和简化函数encode_n_characters :: [Char] -> Char ,其中encode_characters kt = encode_n_characters [k, t] Is the result simpler than the specialized two-argument function? 结果比专用的双参数函数更简单吗?

B. Implement a function encode' defined via encode' (x + y) = encode xy and reimplement encode_characters using this function. B.使用此函数实现函数encode'通过encode' (x + y) = encode xy定义encode' (x + y) = encode xy和重新实现encode_characters Does either of the functions become simpler? 两种功能都变得更简单吗? Is the implementation simpler overall? 整体实施更简单吗? Is encode' more or less reusable than encode ? encode'或多或少可重复使用而不是encode

There's definitely a set of tricks to transforming an expression into point-free style. 肯定有一系列技巧可以将表达式转换为无点样式。 I don't claim to be an expert, but here are some tips. 我并不认为自己是专家,但这里有一些提示。

First, you want to isolate the function arguments in the right-most term of the expression. 首先,您希望在表达式的最右侧术语中隔离函数参数。 Your main tools here will be flip and $ , using the rules: 你的主要工具将是flip$ ,使用规则:

f a b ==> flip f b a
f (g a) ==> f $ g a

where f and g are functions, and a and b are expressions. 其中fg是函数, ab是表达式。 So to start: 所以开始:

(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
-- replace parens with ($)
(\k t -> chr $ (a +) . flip mod 26 $ ord k + ord t - 2*a)
-- prefix and flip (-)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ord k + ord t)
-- prefix (+)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ (+) (ord k) (ord t))

Now we need to get t out on the right hand side. 现在,我们需要得到t了在右手边。 To do this, use the rule: 为此,请使用以下规则:

f (g a) ==> (f . g) a

And so: 所以:

-- pull the t out on the rhs
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((+) (ord k) . ord) t)
-- flip (.) (using a section)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) $ (+) (ord k)) t)
-- pull the k out
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)

Now, we need to turn everything to the left of k and t into one big function term, so that we have an expression of the form (\\kt -> fkt) . 现在,我们需要将kt左边的所有内容转换为一个大函数项,以便我们有一个表达式(\\kt -> fkt) This is where things get a bit mind-bending. 这是事情变得有点令人费解的地方。 To start with, note that all the terms up to the last $ are functions with a single argument, so we can compose them: 首先,请注意直到最后$所有术语都是带有单个参数的函数,因此我们可以组合它们:

(\k t -> chr . (a +) . flip mod 26 . flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)

Now, we have a function of type Char -> Char -> Int that we want to compose with a function of type Int -> Char , yielding a function of type Char -> Char -> Char . 现在,我们有一个Char -> Char -> Int类型的函数,我们想要用Int -> Char类型的函数组合,产生Char -> Char -> Char类型的函数Char -> Char -> Char We can achieve that using the (very odd-looking) rule 我们可以使用(非常奇怪的)规则来实现这一点

f (g a b) ==> ((f .) . g) a b

That gives us: 这给了我们:

(\k t -> (((chr . (a +) . flip mod 26 . flip (-) (2*a)) .) . ((. ord) . ((+) . ord))) k t)

Now we can just apply a beta reduction: 现在我们可以应用beta减少:

((chr . (a +) . flip mod 26) .) . (flip flip (2*a) . ((-) . ) . ((. ord) . (+) .ord))

Connect on IRC, #haskell , and ask lambdabot ! 连接IRC,#haskell ,并询问lambdabot! :

<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM