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"?
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"?
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:where
f
andg
are functions, anda
andb
are expressions. So to start:Now we need to get
t
out on the right hand side. To do this, use the rule:And so:
Now, we need to turn everything to the left of
k
andt
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:Now, we have a function of type
Char -> Char -> Int
that we want to compose with a function of typeInt -> Char
, yielding a function of typeChar -> Char -> Char
. We can achieve that using the (very odd-looking) ruleThat gives us:
Now we can just apply a beta reduction:
To turn a function
into point-free form, I generally try to follow what is done to the last parameter
z
and write the function asThen I can cancel out the
z
s to getThen 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:It's also important to remember that with partial evaluation, you can "break off" the last argument to a function:
For your particular function, consider the flow that
k
andt
go through:ord
to each of themchr
So as a first attempt at simplifying, we get:
Note that you can avoid
flip
by using a section onmod
, and sections using-
get messy in Haskell so there's asubtract
function (they clash with the syntax for writing negative numbers:(-2)
means negative 2, and isn't the same assubtract 2
).In this function,
ord k + ord t
is an excellent candidate for usingData.Function.on
(link). This useful combinator lets us replaceord k + ord t
with a function applied tok
andt
:We're now very close to having
and hence
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:
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: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
Connect on IRC, #haskell, and ask lambdabot !:
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.
First of all, the
flip
is unnecessary:Next, I would use name and conquer to factor out an independently usable subfunction:
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: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
whereencode_characters k t = encode_n_characters [k, t]
. Is the result simpler than the specialized two-argument function?B. Implement a function
encode'
defined viaencode' (x + y) = encode x y
and reimplementencode_characters
using this function. Does either of the functions become simpler? Is the implementation simpler overall? Isencode'
more or less reusable thanencode
?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.