Point-Free style: What is it good for? | OJ's rants
Point-Free style: What is it good for?
Posted on June 27, 2009, 21:49, by OJ
If you're not interested in what inspired this post, then skip this section and jump
to the more interesting bits.
もしあなたがこの post によって inspire されることに興味がないのであれば
このセクションをスキップして興味のあるところに飛んでください。
A little bit of history…
ちょっとした歴史を…
Recently I've been delving into Haskell quite a bit. It's part of my apparently
never-ending quest to learn as much as I can about as many languages as I can (well,
those that appeal to me at least :)). While I love playing around with a language,
toying with ideas, writing small programs, reading books, blog posts, etc it's not
really the same as having an on-call expert to help and guide you.
わたしは最近、Haskell について少々詳しく調べたことがありました。それは決して終わらない
ことがはっきりしている冒険 (never-ending quest) であり、わたしに可能な限りの数多くの言
語を学ぶというものでした(そう、少なくともこれはわたしにはアピールするものなんです :))。
わたしはプログラミング言語で何かするのがとても好きでたとえば何かのアイデアを試してみる
とか、小さなプログラムを書く、本を読む、blog をポストするなどなどといったことをします。
これはあなたを助け導いてくれる on-call expert とはぜんぜん違うものです。
While I'm aware of, and frequently visit, the Haskell IRC channell I find that the
level of understanding there is so great that my piddly noob questions tend to get
lost amongst the bombardment of much more advanced & interesting discussions. In
short, while I find it a great place to go, it's not a great place to find someone
who's happy to help guide me through the maze and go over topics in quite a bit of
detail while I annoy them with questions.
わたしが気にかけていて頻繁に訪れていたのがHaskell の IRC チャンネルでそたが、そこはわ
たしのくだらない質問 (piddly noob questions) をより高度で興味深いディスカッションでか
き消してしまうようなとても great な場所であることを思い知らされるところでした。手短に
いうと、わたしは great place to go を見つけはしたのだけど、そこは喜んでわたしをガイド
してくれるような誰かを見つけるのには great な place ではなくてわたしが発する質問で彼ら
をいらいらさせてしまう場所だったのです。
Haskell is one of those languages where having a mentor is really beneficial. So I set
about finding myself one. Thankfully, I found a rather helpful chap based in the UK,
(he shall remain anonymous, as I don't want to violate the man's privacy), Peter
Marks, who has been very forthcoming with information. He's humoured me and been
incredibly patient so far, and I'm very grateful for his time.
Haskell はメンター (mentor) を持つことが非常に重要な言語の一つです。ですから、わたしは
自分のメンターを探しました。ありがたいことに、わたしは英国に住んでいるHaskell の情報に
ついてとても協力的な Peter Marks という非常に親切な方を見つけました(その方のプライバシ
ーを侵害したくはないので仮名にしておきます)。彼は信じられないくらい我慢強くわたしに合
わせてくれました。わたしは彼にとても感謝しています。
One of the questions that I asked him was:
わたしが彼に質問したことの一つにこういうものがあります:
Why is it that everyone seems to strive to get their code into Point-Free style? I
can see how a lot of the implementations are more concise, but many of those lose
readability.
なぜ誰もが自分のコードをポイントフリースタイル (Point-Free style) で書こうとするので
すか? ポイントフリースタイルにするのがより一貫性のあるものだということはわたしにもわ
かるのですが、読みやすさを大幅に失ってしまうのではないでしょうか。
What is so special about it?
ポイントフリースタイルのなにがそんなに特別なことなんですか?
The discussion that followed was really insightful. That is what has inspired me to
write this post.
そのあとのディスカッションはとても示唆に富むものでした (insightful)。
そしてそれがこのポストを書くことをわたしに inspire させたのです。
Please note, any errors in this post are totally my own. They are not the fault of my
mentor :)
あらかじめお断りしておきますが、このポストにある間違いはすべてわたし自身に帰するものです。
記述に間違いがあったとしてもそれはわたしの mentor の過ちではありません :)
The purpose of the post: Why aim for Point-Free style?
(このポストの目的: なぜポイントフリースタイルにするのか?)
So it is just me, or does anyone else out there feel that there's a bit of a “thing”
going on for Point-Free style? Sometimes I share my terrible code with people and I
get shunned when it's not Point-Free and it could be.
So it is just me, or does anyone else out there feel that
there's a bit of a “thing” going on for Point-Free style?
まさにわたしのことですし、
わたしは自分の酷いコード (terrible code) を他の人と共有し
それがポイントフリーにできるのにしていなかったりしたときには
わたしは shunned していたのです。
Anyone? (みんな?)
I hope it's not just me :) Let's start with taking a quick look at what Point-Free
style actually is.
わたしだけではないことを望みます :)
ポイントフリースタイルが実際にどういうものなのかについて見ていきましょう。
So what is Point-Free style? (結局、ポイントフリースタイルってなんなわけ?)
If we take a look at the Wikipedia we can see that..
Wikipedia でポイントフリーについて調べると次のようなことが書かれています
Tacit [point-free] programming is a programming paradigm in which a function definition
does not include information regarding its arguments, using combinators and function
composition (but not ?-abstraction) instead of variables.
関数定義においてその関数に関係する引数についての情報を含めず、変数の代わりにコンビネーター
(combinators) や関数合成 (function compositon) を使うようなプログラミングパラダイム。
Simple eh? :) In essence, it basically means that your function definition doesn't
reference any of its arguments/variables. For a crass definition, think point ==
argument and it should make sense.
単純ですね? :) In essence, ポイントフリーとは、基本的には
あなたが関数定義をするときにその関数の引数や変数を一切参照しないということです。
そしてわたしたちは関数に対する引数を直接参照することなしに関数を記述します。
Haskell やこの機能をサポートするその他の言語に馴染みのない人たちのために
例を挙げてみることにしましょう:
-- sum : take a list of numbers and add them all up to get a total
-- start with the base-case: an empty list
-- 数値のリストを受け取り、それらすべてを加算して合計を求める
sum [] = 0
sum (x:xs) = x + sum xs
We can see that the above function is written in such a way that the arguments passed
into the function are actually referenced in the body of the code. This is how a
standard imperative programmer would write this function if he/she was new to Haskell.
If we instead used a fold, we could define it like so:
この例にある関数ではその引数を関数本体のコードの中で実際に参照するやり方で書かれている
ことがわかります。このやり方は標準的な命令型プログラマー (standard imperative
programmer) がHaskell にあまり慣れていないときに関数をこのように書くだろうというもので
す。ここで fold を使うようにすると関数の定義を次のようにできます:
sum xs = foldr (+) 0 xs
This does exactly the same thing as the previous definition, but as you can see the
grunt work is done by the fold function. Now that we have this definition, we can
easily move to Point-Free style:
これは前の定義と全く同じものなのですが、見てわかるように grunt work は fold 関数がやっ
ています。このとき、この関数定義をポイントフリースタイルにするのは簡単にできます。
sum = foldr (+) 0
Here we can see that no reference is made to the arguments of the function. Since we
haven't referenced any “points”, we have a Point-Free implementation.
ここでわたしたちは関数の引数が参照されないようにされているということを確認できます。
一切の“ポイント”(points)を参照していないので、ポイントフリーな実装を得たというわけです。
Awesome. Cool. Sweet. Nifty.
素晴らしい。クール。いいね。イカす。
But what does it give me? Why is it better?
でもこれがわたしにとってどんな利益をもたらすの? なにが良いの?
Why use Point-Free style? (なぜポイントフリースタイルを使うの?)
This section is based totally on my own opinion and is not an official definition :) I
think that Point-Free style fits in the same category as many other coding patterns
and styles, and that it's usually down to the individual programmer to determine the
when and the why. So take my view with a pinch of salt.
このセクションは全体的にはわたし自身の意見に基づいているものであり、公式の定義ではあり
ません :)わたしはポイントフリースタイルがほかのコーディングパターンやコーディングスタ
イルのようなものと同じカテゴリにフィットすると考えているからです。そしてそれは、So
take my view with a pinch of salt. 手短に書くと、ポイントフリースタイルでは書き手に対し
て“what” ではなく “how”に焦点を当てさせるようになります。
It might be just me, but imperative programs seem to have a large focus on the data.
The code which operates on the data is lost within a plethora of code that isn't
necessarily specific to what the program needs to do. I've found that functional
programming tends to be very different, at least in Haskell. Haskell lets me focus on
what it is I need to do, and I feel that Point-Free is another step in the same
direction. Is this good? I think so :) But I'll let you decide.
ひょとしたらこれはわたしだけかもしれませんが、命令型のプログラム (imperative program)
というのはデータに対する large focus を持っているように思われるのです。データに対して
操作を行うコードは、プログラムが行う必要のあることを特定する必要のないコードの
plethora の中で失われています。わたしは関数型プログラミングが非常に異なる傾向にあるこ
とに気づきました。少なくとも Haskellでは。Haskell はわたしにわたしが行う必要のあること
について焦点を当てさせて、わたしはポイントフリーが同じ direction の中のもう一つのステ
ップであると感じていました。これはよいことではないでしょうか?わたしはそう思っています :)
しかし判断はあなたにお任せします。
As well as the focus on the what I've found that aiming for a Point-Free solution can
aid in helping you understand your problem better. This claim sounds like fluff, so
let's go through an example and hopefully you'll see what I mean.
As well as the focus on the what
I've found that aiming for a
Point-Free solution can aid in helping you understand your problem better.
Point-Free solution はあなたが自分の問題をよりよく理解することを手助けするかもしれない
この主張は fluff のように聞こえるので、
例を挙げてわたしが言っていることをあなたが理解できることを期待します。
Where Point-Free helped me get a better understanding
(ポイントフリーがわたしの理解をより一層助けるところ)
Haskell developers use the composition operator (.) a lot. It actually aids in
creating Point-Free style. I love the irony here. We add points (.) to remove points
(arguments).
Haskellの開発者たちは composition operator (.) を多用します。それは実際にはポイントフ
リースタイルの実行を手助けします。わたしはこの irony が大好きです。わたしたちはポイン
ト(引数)を取り除くためにポイント((.))を付加しているのです。
Anyway, the composition operator's definition, according to Prelude is:
いずれにしても、composition operator の定義は Prelude によれば次のようなものです:
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g x = f (g x)
This operator takes two functions and produces a function which is composed of the two.
This function takes the output of one function and sends it through as the input to
another function, returning the result of that call.
この演算子は二つの関数を取り、それらを compose した関数を返します。
結果として得られた関数はある関数の出力を受け取って
それを別の関数の入力として送ります。そして呼び出した結果を返します。
This is really handy. We can do some interesting things like:
これはとても便利です。次のような面白いことができます:
ghci> let doubleAndAdd5 = (+5) . (*2)
ghci> doubleAndAdd5 20
45
ghci> :m +Data.List
ghci> let sumColumns = map sum . transpose
ghci> sumColumns [[1,2,3],[4,5,6],[7,8,9]]
[12,15,18]
Very handy indeed. While handy, it doesn't necessarily allow us to do everything we
might want to do. One example is handling cases where we want to compose a function
where the right-hand function takes two arguments instead of one.
とても handy で役に立ちます。handy である一方で、わたしたちが行いたいと望むかもしれな
いすべてのことを行えるように許可する必要はありません一つの例が右辺にある関数が引数を一
つではなく二つとるような関数をcompose したいケースに対処するというものです。
So if we wanted to create a function that would take two values, add them together and
double the result, all while using Point-Free style, we'd like to do something like
this:
ですからわたしたちが引数を二つ取る関数を作り出したいのであれば、二つの引数を足し合わせ
てからその結果を二倍すればよいのです。全部をポイントフリースタイルを使ってやってみると
次のようなものになるでしょう:
ghci> let addAndDouble = (*2) . (+)
:1:20:
No instance for (Num (a -> a))
arising from a use of `*' at :1:20-21
Possible fix: add an instance declaration for (Num (a -> a))
In the first argument of `(.)', namely `(* 2)'
In the expression: (* 2) . (+)
In the definition of `addAndDouble': addAndDouble = (* 2) . (+)
ghci>
As you can see, ghci doesn't like it. And rightly so! This is because the composition
operator's signature is:
見ての通り、ghciではうまくいきません。 And rightly so!
これは composition の演算子のシグネチャが次のようになっているためです:
(.) :: (b -> c) -> (a -> b) -> a -> c
That is:
1. It takes an argument which is a function which takes a value of type b which
returns a value of type c
型 c の値を返す型 b の値を取る関数である引数を一つ取る
2. It takes a function which takes a value of type a which returns returns a value
of type b
型 a の値を取って型 b の値を返す関数を返す関数を一つ取る
3. It returns a new function which takes a value of type a and returns a value of
type c
型 a の値を取って型 c の値を返す新しい関数を返します。
This doesn't work in our case, as we want our right-hand function to take two
arguments. That is, we want a type signature that looks like this:
これはわたしたちのケースではうまく動作しません。それは右側の関数で二つの引数を取るよう
にさせたかったからですつまりわたしたちが欲していた型シグネチャは次のようなものだったの
です:
foo :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
So everywhere we had (a -> b), what we really want is (a -> a1 -> b). Given
that we don't have an operator that does that for us, let's define one. We'll start
with the definition of the (.) operator and work towards a function that does what we
need.
ですから (a -> b) としていたすべての場所で、本当に望んでいたものは
(a -> a1 -> b) なのです。わたしたちのために仕事をする定義されていない演算子を
与えて定義してみましょう。(.) 演算子の定義から始めて、わたしたちが必要とすることを行
う関数にしていきます。
-- here is the composition operator again
-- composition 演算子再び
(.) f g x = f (g x)
-- here's our new operator's definition
-- わたしたちの新しい演算子の定義
(.^) f g x y = f (g x y)
Simple! Let's see what ghci says about it:
シンプルですね! ghci ではどうなるか見てみましょう:
ghci> let (.^) f g x y = f (g x y)
ghci> :t (.^)
a :: (t2 -> t3) -> (t -> t1 -> t2) -> t -> t1 -> t3
Looks good! This is exactly what we need. Now that we have a working function, let's
aim to write this in Point-Free style. We do this by breaking down the function slowly
and eliminating arguments by moving them to the far right hand side of the function
definition.
いいですね! これはわたしたちが必要としていることそのものです。いまやわたしたちは動作す
る関数を手に入れましたから、これをポイントフリースタイルで書いてみましょう。わたしたち
はこれをこの関数を slowly にブレークダウンして、引数を関数定義の右辺のさらに外側に移動
することによって消去するという手順で行います。
The first argument to get rid of is ‘y', as this is the last argument passed in:
取り除くべき最初の引数は 'y' です。これは最後の引数として次のように渡されています:
-- start by moving y to the right
-- y を右側に動かすことから開始
(.^) f g x y = f (g x y)
-- becomes
-- こうなります
(.^) f g x y = f . (g x) $ y
These are functionally equivalent, and now that ‘y' is out on it's own, we can drop
it from our function definition:
これらは機能的には等価なもので、'y' はこれで外側に出ましたから、関数定義から取り除くこ
とができます:
ghci> let (.^) f g x = f . (g x)
ghci> :t (.^)
(.^) :: (b -> c) -> (t -> a -> b) -> t -> a -> c
ghci>
Excellent, we're a step closer. Now we need to do the same with ‘x'. This takes a
little more fiddling:
すばらしい。we're step closer.
今度は同じことを 'x' について行う必要があります。
これはもう少し fiddling になります。
-- we need to take the original definition
-- オリジナルの定義を持ってくる必要があります
(.^) f g x = f . (g x)
-- and change it so that it uses prefix notation instead of infix
-- そしてそれを中置記法ではなく前置記法を使うように変更します
(.^) f g x = (.) f (g x)
-- which is the same as
-- これは次のものと同じで
(.^) f g x = ((.) f) (g x)
-- what we're doing is calling g with x and applying the result to f
-- わたしたちが行っているのは x を引数とする g の呼び出しとその結果のf に対する適用です
-- and hence we can compose the composition of f with the call to g
-- そしてそれによって f の composition を g の呼び出しと compose することができます
-- giving us the following
-- 次のようなものを与えると
(.^) f g x = ((.) f) . g $ x
-- finally leaving us with
-- 最終的な結果はこうなります
(.^) f g = ((.) f) . g
Phew :) I hope you can see the progression. We've managed to move x out of the picture,
so now we're down to a fairly crazy looking definition. Let's see what ghci has to say:
Phew :) あなたがこの progression を理解できると良いのですが。わたしたちは x を定義から
無くそうとして、その結果こうして fairly crazy looking definition にたどり着きました。
Let's see what ghci has to say:
ghci> let (.^) f g = ((.) f) . g
ghci> :t (.^)
(.^) :: (b -> c) -> (a1 -> a -> b) -> a1 -> a -> c
So we've got rid of two variables, but there are still two more to go! Remember, ‘f'
and ‘g' are still variables, they just carry functions. So let's get rid of ‘g':
わたしたちは二つの変数を取り除きましたが、まだ三つ以上変数が残っています! ここで、
'f' と 'g' はまだ変数であるということを思い出してください。これらは単なる carry 関数で
す。それでは 'g' を取り除いてみましょう:
-- start with what we had before
-- 以前の結果から開始
(.^) f g = ((.) f) . g
-- change to prefix notation again
-- 再度前置表記に変更する
(.^) f g = (.) ((.) f) g
-- and drop g
-- g を落とす
(.^) f = (.) ((.) f)
This is looking rather crazy :) Again, let's make sure we haven't done anything stupid:
これはとっても crazy に見えますね :)
再度、わたしたちがおバカなことをやっていないことを確認しましょう:
ghci> let (.^) f = (.) ((.) f)
ghci> :t (.^)
(.^) :: (b -> c) -> (a1 -> a -> b) -> a1 -> a -> c
Excellent. We've still got what we need. One more point needs to be dropped, so let's
get rid of ‘f':
すばらしい。わたしたちにはまだ必要とされていることがあります。
もう一つ drop する必要のある引数があります。
では 'f' を取り除いてみることにしましょう:
-- start with what we had before
-- 以前のところから始めます
(.^) f = (.) ((.) f)
-- which means that we're composing a composition with a function composed of f and something else
-- f と何かを compose した関数と composition を compose するということです
(.^) f = (.) . (.) $ f
-- and we finally drop f
-- そして最後に f を drop します
(.^) = (.) . (.)
Isn't that just crazy! Let's again check ghci:
ghciで再度確認してみましょう:
ghci> let (.^) = (.) . (.)
ghci> :t (.^)
(.^) :: (b -> c) -> (a1 -> a -> b) -> a1 -> a -> c
So there we have it, our operator completely in Point-Free style.
このように、わたしたちの演算子は完全にポイントフリースタイルです。
This is where the penny dropped for me. The whole exercise of moving through to
Point-Free made me really understand what it was I was doing in the first place. The
final definition makes it very clear. We're composing two separate compositions.
This is where the penny dropped for me.
ポイントフリーに変形するというエクササイズを通じて
わたしはそれが最初に自分がおこなっていたものだったことを本当に理解しました
最終的な定義はそれを非常に明確なものにしました。
わたしたちは二つの分かれた compositions を compose しています。
Let's see if our function behaves itself using the example we listed above.
わたしたちの関数がどのように振舞うのかを上でリストにあげた例を使って見てみましょう。
ghci> let addAndDouble = (*2) .^ (+)
ghci> :t addAndDouble
addAndDouble :: Integer -> Integer -> Integer
ghci> addAndDouble 10 15
50
ghci> addAndDouble 21 3
48
It does exactly what we need it too.
これもまたわたしたちが必要としていることそのものをおこなっています。
Conclusion (結論)
To sum up, Point-Free helps you tidy your code into more concise implementations which
tend to aid you in understanding what it is you are trying to do. I feel it really
helps you focus on what you're doing as opposed to what you're doing it to. It's
down to you to determine whether you feel this is a good thing or not!
要約すると、ポイントフリーはあなたが自分のコードを tidy して
何をしようとしているものなのかを理解しやすいような
より concise な実装にするための手助けをします。
I feel it really helps you focus on what you're doing as opposed to what you're doing it to.
It's down to you to determine whether you feel this is a good thing or not!
It is ultimately down to the developer to dictate when it should be used. There are
definitely cases where the resulting function might not actually help in making things
clearer. But on the whole, Point-Free style seems to help me understand what it is I'm
doing (or, arguably, not doing).
It is ultimately down to the developer to dictate when it should be used.
開発者に結果として得られた関数が明快に理解するのを助けないことがある場合も確かにあります。
しかし全体としては、ポイントフリースタイルは
自分が何をしようとしているかを自分自身が理解するのを助けてくれるように
思えるのです。
Of course, you could just get sick and tired of trying to come up with variable names,
in which case Point-Free is the bomb :)
もちろん、うんざりして変数名を探しだそうという試みに疲れてしまうだけかもしれません。
その場合は Point-Free が爆弾だったのでしょうね :)