Haskell 中的无点

发布于 2024-08-25 03:36:50 字数 160 浏览 6 评论 0原文

我有这段代码,我想使其成为无点的;

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

我该怎么做?

除了“思考这个并想出一些东西”之外,还有一些关于无点风格的一般规则吗?

I have this code that I want to make point-free;

(\k t -> 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"?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

苦笑流年记忆 2024-09-01 03:36:50

要将函数转换

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

为无点形式,我通常尝试遵循对最后一个参数 z 所做的操作,并将函数编写为

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

然后我可以取消 z s然后

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

对 y 和 x 重复该过程应该以无点形式的 func 结束。在此过程中需要认识到的一个重要转换是:

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

同样重要的是要记住,通过部分求值,您可以“中断”函数的最后一个参数:

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

对于您的特定函数,请考虑 k 的流程和 t 执行:

  1. 对它们中的每一个应用 ord
  2. 添加结果
  3. 减去 2*a
  4. 取结果 mod 26
  5. 添加一个
  6. 应用 chr

因此第一次尝试简化,我们得到:

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

请注意,您可以通过使用 mod 上的部分来避免 flip,并且使用 - 的部分会变得混乱Haskell 所以有一个 subtract 函数(它们与书写负数的语法冲突:(-2) 表示负 2,并且与 subtract 不同2)。

在此函数中,ord k + ord t 是使用 Data.Function.on (链接)。这个有用的组合器让我们可以用应用于 kt 的函数来替换 ord k + ord t

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

我们现在非常接近拥有

func k t = (function pipeline) k t

和因此

func = (function pipeline)

不幸的是,Haskell 在用一系列一元函数组成二元函数时有点混乱,但有一个技巧(我会看看是否能找到一个很好的参考),我们最终得到

import Data.Function (on)

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

:除了那个丑陋的组合技巧之外,几乎是一个漂亮整洁的无点函数管道。通过定义评论中建议的 .: 运算符 此页面,这整理了一些:

import Data.Function (on)

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

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

为了进一步完善这一点,您可以添加一些辅助函数来分隔字母 <-> 和从 凯撒密码算术进行的 Int 转换。例如:letterToInt = 减去 a 。订单

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

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

Then I can cancel out the zs to get

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. 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:

  1. Apply ord to each of them
  2. Add the results
  3. Subtract 2*a
  4. Take the result mod 26
  5. Add a
  6. Apply 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).

In this function, ord k + ord t is an excellent candidate for using Data.Function.on (link). This useful combinator lets us replace ord k + ord t with a function applied to k and 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:

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. For example: letterToInt = subtract a . ord

一江春梦 2024-09-01 03:36:50

除了“思考这个并想出一些东西”之外,还有一些关于点自由风格的一般规则吗?

您始终可以作弊并使用 lambdabot 中的“pl”工具(通过转到 freenode 上的 #haskell 或使用例如 ghci 酸)。对于您的代码,请给出:

((chr . (a +) . Flip mod 26) .) 。翻转翻转 (2 * a) 。 ((-) .) 。 (.ord) 。 (+)。 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 e.g. ghci on acid). For your code pl gives:

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

Which isn't really an improvement if you ask me.

北城孤痞 2024-09-01 03:36:50

肯定有一系列技巧可以将表达式转换为无点样式。我并不自称是专家,但这里有一些提示。

首先,您想要隔离表达式最右边项中的函数参数。这里的主要工具是 flip$,使用规则:

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

其中 fg 是函数,并且ab 是表达式。首先:

(\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))

现在我们需要将 t 从右侧取出。为此,请使用以下规则:

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

因此:

-- 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)

现在,我们需要将 kt 左侧的所有内容转换为一个大函数项,这样我们就有了(\kt -> fkt) 形式的表达式。这就是事情变得有点令人费解的地方。首先,请注意直到最后一个 $ 的所有项都是带有单个参数的函数,因此我们可以组合它们:

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

现在,我们有一个 Char ->; 类型的函数。字符-> Int 我们想要与 Int 类型的函数组合 -> Char,产生一个 Char -> 类型的函数字符->字符。我们可以使用(看起来非常奇怪的)规则来实现这一点,

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

该规则为我们提供了:

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

现在我们可以应用 beta 缩减:

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

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:

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. 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. 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 (\k t -> f k t). 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. 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:

((chr . (a +) . flip mod 26) .) . (flip flip (2*a) . ((-) . ) . ((. ord) . (+) .ord))
荒路情人 2024-09-01 03:36:50

我假设你释放点的目的是使代码更简洁、更具可读性。因此,我认为明智的做法是还进行一些其他重构以实现简化,这样可以更轻松地删除变量。

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

首先,翻转是不必要的:

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

接下来,我将使用名称和征服来分解出一个独立可用的子函数:

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

我还为第一个表达式指定了名称它更清晰且可重复使用。 encode_characters 现在可以使用 @Nefrubyr 的技术轻松实现无点:

encode_characters = chr . encode `on` ord

至于第二个表达式,我无法生成比其他答案中显示的任何形式更具可读性的形式,而且它们都更少比逐点形式可读。因此,我建议此时停止重构,并欣赏所得代码的整洁性和可重用性。

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

PS:作为一个练习,根据问题的上下文,对函数接口进行一些轻微的修改(什么数据以什么形式传递到函数中) )可能通过概括问题来产生更多的简化。

A. 实现并简化函数encode_n_characters :: [Char] -> Char,其中 encode_characters kt =encode_n_characters [k, t]。结果比专门的二参数函数更简单吗?

B. 实现通过 encode' (x + y) =encode x y 定义的函数 encode' 并使用该函数重新实现 encode_characters。其中任一功能是否变得更简单?整体实施是否更简单? encode'encode 的可重用性更高还是更低?

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:

(\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 = 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.

A. Implement and simplify function encode_n_characters :: [Char] -> Char where encode_characters k t = 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 x y and reimplement encode_characters using this function. Does either of the functions become simpler? Is the implementation simpler overall? Is encode' more or less reusable than encode?

淑女气质 2024-09-01 03:36:50

连接 IRC、#haskell询问 lambdabot !

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

Connect on IRC, #haskell, and ask lambdabot !:

<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文