问题描述
目前我正在通过进行工作,这非常有趣行使。它涉及到实现类型类
如 pointfree 有可能自动执行任何此类转换。但是,结果往往比改进更加混淆。如果一个人的目标是增强可读性而不是摧毁它,那么第一个目标应该是识别为什么表达式有一个特定的结构,找到一个合适的抽象,并以这种方式构建事物。
最简单的结构就是在一个线性流水线中简单地链接在一起,这是一个简单的函数组合。这让我们感到很遥不可及,但正如你注意到的那样,它并不能处理所有事情。
一个泛化是带有附加参数的函数,可以建立它们增量。这里有一个例子:定义 onResult =(。(。))。现在,对初始值 id 应用 onResult n次,给出函数组合,其结果为n元功能。所以我们可以定义 comp2 = onResult(。),然后写 comp2 not(&&)来定义一个NAND操作。
另一个概括 - 其中包含上述内容 - 确实是 - 定义将函数应用于更大值的组件的运算符。这里的一个例子是第一个和第二个在 Control.Arrow ,它使用2元组。 Conal Elliott的基于这种方法。
稍微不同的情况是,当你对某些类型的 b 和一个函数 a - >有一个多参数函数时, b ,并且需要使用 a 将它们组合成一个多参数函数。对于2元函数的常见情况,模块 Data.Function 在组合器上提供,您可以使用它编写诸如比较`fst 之类的表达式来比较它们的第一个元素上的2元组。
这很麻烦问题时多次使用单个参数,但这里也有可以提取的有意义的重复模式。这里常见的情况是将多个函数应用于单个参数,然后使用另一个函数收集结果。这恰好对应于函数的 Applicative 实例,这使我们可以编写(&&)< $> (> 3)*> (< 9)来检查一个数字是否落在给定范围内。
重要的是,如果你想使用任何一个在实际的代码中,要考虑表达式的含义是什么,以及它如何反映在结构中。如果这样做,然后使用有意义的组合器将其重构为无点式样,那么与通常的 > pointfree 。
I am working through the 20 Intermediate Haskell Exercises at the moment, which is quite a fun exercise. It involves implementing various instances of the typeclasses Functor and Monad (and functions that takes Functors and Monads as arguments) but with cute names like Furry and Misty to disguise what we're doing (makes for some interesting code).
I've been trying to do some of this in a point-free style, and I wondered if there's a general scheme for turning a point-ful (?) definition into a point-free definition. For example, here is the typeclass for Misty:
class Misty m where unicorn :: a -> m a banana :: (a -> m b) -> m a -> m b
(the functions unicorn and banana are return and >>=, in case it's not obvious) and here's my implementation of apple (equivalent to flip ap):
apple :: (Misty m) => m a -> m (a -> b) -> m b apple x f = banana (\g -> banana (unicorn . g) x) f
Later parts of the exercises have you implement versions of liftM, liftM2 etc. Here are my solutions:
appleTurnover :: (Misty m) => m (a -> b) -> m a -> m b appleTurnover = flip apple banana1 :: (Misty m) => (a -> b) -> m a -> m b banana1 = appleTurnover . unicorn banana2 :: (Misty m) => (a -> b -> c) -> m a -> m b -> m c banana2 f = appleTurnover . banana1 f banana3 :: (Misty m) => (a -> b -> c -> d) -> m a -> m b -> m c -> m d banana3 f x = appleTurnover . banana2 f x banana4 :: (Misty m) => (a -> b -> c -> d -> e) -> m a -> m b -> m c -> m d -> m e banana4 f x y = appleTurnover . banana3 f x y
Now, banana1 (equivalent to liftM or fmap) I was able to implement in pointfree style, by a suitable definition of appleTurnover. But with the other three functions I've had to use parameters.
My question is: is there a recipe for turning definitions like these into point-free definitions?
As demonstrated by the pointfree utility, it's possible to do any such conversion automatically. However, the result is more often obfuscated than improved. If one's goal is to enhance legibility rather than destroy it, then the first goal should be to identify why an expression has a particular structure, find a suitable abstraction, and build things up that way.
The simplest structure is simply chaining things together in a linear pipeline, which is plain function composition. This gets us pretty far just on its own, but as you noticed it doesn't handle everything.
One generalization is to functions with additional arguments, which can be built up incrementally. Here's one example: Define onResult = (. (.)). Now, applying onResult n times to an initial value of id gives you function composition with the result of an n-ary function. So we can define comp2 = onResult (.), and then write comp2 not (&&) to define a NAND operation.
Another generalization--which encompasses the above, really--is to define operators that apply a function to a component of a larger value. An example here would be first and second in Control.Arrow, which work on 2-tuples. Conal Elliott's Semantic Editor Combinators are based on this approach.
A slightly different case is when you have a multi-argument function on some type b, and a function a -> b, and need to combine them into a multi-argument function using a. For the common case of 2-ary functions, the module Data.Function provides the on combinator, which you can use to write expressions like compare `on` fst to compare 2-tuples on their first elements.
It's a trickier issue when a single argument is used more than once, but there are meaningful recurring patterns here that can also be extracted. A common case here is applying multiple functions to a single argument, then collecting the results with another function. This happens to correspond to the Applicative instance for functions, which lets us write expressions like (&&) <$> (> 3) <*> (< 9) to check if a number falls in a given range.
The important thing, if you want to use any of this in actual code, is to think about what the expression means and how that's reflected in the structure. If you do that, and then refactor it into pointfree style using meaningful combinators, you'll often make the intent of the code clearer than it would otherwise be, unlike the typical output of pointfree.
这篇关于用无点式编写函数的一般方案是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!