[英]What's a shorter way of writing `(\ x -> traceShow x x )`?
Printing the value of an expression is a common practice in debugging. 打印表达式的值是调试中的常见做法。 For example, if I have a piece of code like this
例如,如果我有一段这样的代码
my . super . cool . fUnCtIoN . chain $ value
and I am trying to see the output of fUnCtIoN . chain
我试图看到
fUnCtIoN . chain
的输出fUnCtIoN . chain
fUnCtIoN . chain
, I would add fUnCtIoN . chain
,我会补充
my . super . cool . (\ x -> traceShow x x ) . fUnCtIoN . chain $ value
which is mouthful for a simple task like this, not to mention if I want to print many intermediate results: 对于像这样的简单任务来说,这是满口的,更不用说我是否要打印许多中间结果:
(\ x -> traceShow x x )
. my
. (\ x -> traceShow x x )
. super
. (\ x -> traceShow x x )
. cool
. (\ x -> traceShow x x )
. fUnCtIoN
. (\ x -> traceShow x x )
. chain
$ value
It would just look awful. 它看起来很糟糕。 Is there a better way to do this?
有一个更好的方法吗?
Just use traceShowId
! 只需使用
traceShowId
! It does exactly what you're asking for. 它完全符合您的要求。
my . super . cool . traceShowId . fUnCtIoN . chain $ value
Yes. 是。
join traceShow
. join traceShow
。
λ> import Control.Monad
λ> :t join
join :: Monad m => m (m a) -> m a
λ> :t join (+)
join (+) :: Num a => a -> a
In the case of the function monad, join fx = fxx
, so join traceShow
is equivalent to \\x -> traceShow xx
. 在函数monad的情况下,
join fx = fxx
,因此join traceShow
等效于\\x -> traceShow xx
。
Or make a where
clause that provides a new definition of (.)
: 或者创建一个提供
(.)
新定义的where
子句:
--...your code without the nasty bits...
where
(.) f g a = f ( join traceShow (g a))
Which may just help, though there will be one more traceShow
call than previously. 这可能只是帮助,虽然会有比以前更多的
traceShow
调用。
How about a helper function for adding a trace call to a function: 如何为函数添加跟踪调用的辅助函数:
dbg :: Show a => String -> a -> a
dbg name x = trace (name ++ ": " ++ show x) x
main = do
let x = dbg "my" . my
. dbg "super" . super
. dbg "cool" . cool
. dbg "func" . fUnCtIoN
. dbg "chain" . chain
$ value
print x
my = (+1)
super = (+2)
cool = (+3)
fUnCtIoN = (+4)
chain = (+5)
value = 3
Output: 输出:
chain: 3
func: 8
cool: 12
super: 15
my: 17
18
You could write a higher-order function which takes a function of two arguments and uses the same value for both arguments. 你可以编写一个高阶函数,它接受两个参数的函数,并对两个参数使用相同的值。
applyBoth :: (a -> a -> b) -> a -> b
applyBoth f x = f x x
(Aside: this is join
for the "reader" monad (->) a
.) (旁白:这是
join
“读者”monad (->) a
。)
Then you can use that combinator in curried form: 然后你可以以咖喱形式使用该组合器:
applyBoth traceShow
. my
. applyBoth traceShow
. super
. applyBoth traceShow
. cool
. applyBoth traceShow
. fUnCtIoN
. applyBoth traceShow
. chain
$ value
Or define an alias for applyBoth traceShow
. 或者为
applyBoth traceShow
定义别名。
traceS = applyBoth traceShow
traceS
. my
. traceS
. super
. traceS
. cool
. traceS
. fUnCtIoN
. traceS
. chain
$ value
For maximum terseness points, you can automatically interleave traceS
into a list of functions by folding it up: 对于最大的简洁点,您可以通过折叠将
traceS
自动交错到函数列表中:
showSteps :: Show a => [a -> a] -> a -> a
showSteps = foldr (\f g -> f . traceS . g) id
showSteps [my, super, cool, fUnCtIoN, chain] value
Edit Eh, what the hell... It's not entirely relevant, but here's how to make showSteps
work when you want to pipeline your data through a number of types. 编辑呃,到底是什么......这并不完全相关,但是当你想通过多种类型管道数据时,如何使
showSteps
工作。 It's an example of a program we wouldn't be able to write without GHC's advanced type system features ( GADTs
and RankNTypes
in this instance). 这是我们在没有GHC的高级类型系统功能(本例中为
GADTs
和RankNTypes
)的情况下无法编写的程序示例。
Path
is a GADT which explains how to walk through a directed graph of types, starting at the source type x
and ending at the destination type y
. Path
是一个GADT,它解释了如何遍历类型的有向图,从源类型x
开始到结束目标类型y
。 It's parameterised by a category c :: * -> * -> *
. 它由类别
c :: * -> * -> *
。
infixr 6 :->
data Path c x y where
End :: Path c z z
(:->) :: c x y -> Path c y z -> Path c x z
:->
reminds us that a journey of a thousand miles begins with a single step: if the category you're working in lets you go from x
to y
, and you can take a path from y
to z
, you can go from x
to z
. :->
提醒我们,一千英里的旅程从一步开始:如果您正在使用的类别允许您从x
到y
,并且您可以从y
到z
的路径,您可以从x
到z
。 End
is for when you have reached your destination - it's pretty easy to walk from z
to z
by not walking at all. End
是当你已经达到你的目的地-它很容易从步行z
到z
通过不走的。
So Path
has the same recursive structure as a linked list, but with a more flexible approach to the things inside it. 因此
Path
具有与链表相同的递归结构,但对其内部的事物采用更灵活的方法。 Rather than requiring all of its elements to have the same type, it gives you a way to join up arrows like dominos, as long as the return type of one arrow matches the input type of the next. 只要一个箭头的返回类型与下一个箭头的输入类型匹配,它就不会要求所有元素具有相同的类型,而是为您提供一种连接像多米诺骨牌一样的箭头的方法。 (To use the mathematical jargon: if you view the underlying category
c
as a logical relation, then End
augments c
with reflexivity and :->
augments c
with transitivity . Path c
thus constructs the reflexive transitive closure of c
. Another way of looking at this is that Path
is the free category , much like []
is the free monoid ; you can define instance Category (Path c)
without any constraint on c
.) (要使用的数学术语:如果查看下层类别
c
作为逻辑关系,然后End
增强c
与自反和:->
增强c
与传递性 。 Path c
由此构建的自反传递闭包 c
看待的另一种方式。这就是Path
是自由类 ,很像[]
是自由的monoid ;你可以定义instance Category (Path c)
而不对c
任何约束。)
You can fold up a Path
with exactly the same code as you use to fold up a list, but the type is more precise: the folding function can't know anything a priori about the types of the arrows inside the path. 您可以使用与用于折叠列表完全相同的代码折叠
Path
,但类型更精确:折叠功能无法知道任何关于路径内箭头类型的先验 。
foldr :: (forall x y. c x y -> r y z -> r x z) -> r z z -> Path c x z -> r x z
foldr f z End = z
foldr f z (x :-> xs) = f x $ foldr f z xs
At this point, I could define type-aligned sequences of functions ( type TAS = Path (->)
) and show you how f :-> g :-> h :-> End
can be folded up into h . g . f
此时,我可以定义类型对齐的函数序列(
type TAS = Path (->)
)并向您展示f :-> g :-> h :-> End
可以折叠成h . g . f
h . g . f
h . g . f
, but since our goal is to print out all the intermediate values, we have to use a category with a tiny bit more structure than plain old ->
. h . g . f
,但由于我们的目标是打印出所有中间值,我们必须使用比普通旧的结构稍微多一点的类别->
。 (Thanks to @dfeuer in the comments for the suggestion - I've adjusted the name he gave to better reflect the attention-seeking nature of my behaviour.) (感谢@dfeuer在建议的评论中 - 我调整了他给出的名字,以更好地反映我的行为引起注意的性质。)
data Showoff x y where
Showoff :: Show y => (x -> y) -> Showoff x y
Showoff
is just like a regular function, except it assures you that the return value y
will be Show
able. Showoff
就像一个常规函数,除了它确保返回值y
将是Show
able。 We can use this extra bit of knowledge to write showSteps
for paths in which each step is a Showoff
. 我们可以使用这些额外的知识为每个步骤为
showSteps
的路径编写Showoff
。
type ShowTAS = Path Showoff
showSteps :: ShowTAS a b -> a -> b
showSteps path = foldr combine id path . traceS
where combine (Showoff f) g = g . traceS . f
It strikes me as a bit of a shame to use the impure traceS
right in the midst of all this strongly typed fun. 在所有这种强烈类型的乐趣中使用不纯净的
traceS
这让我感到有点遗憾。 In real life I'd probably return a String
along with the answer. 在现实生活中,我可能会回答一个
String
以及答案。
To prove that it does actually work, here is a chain of functions with varying types. 为了证明它确实有效,这里有一系列不同类型的函数。 We take in a
String
, read
it into an Int
, add one to it, convert it to a Float
, then divide it by 2. 我们接受一个
String
, read
其read
入Int
,向其中添加一个,将其转换为Float
,然后将其除以2。
chain :: ShowTAS String Float
chain = Showoff read :-> plusOne :-> toFloat :-> divideTwo :-> End
where plusOne :: Showoff Int Int
plusOne = Showoff (+1)
toFloat :: Showoff Int Float
toFloat = Showoff fromIntegral
divideTwo :: Showoff Float Float
divideTwo = Showoff (/2)
ghci> showSteps chain "4"
"4"
4
5
5.0
2.5
2.5 -- this last one is not from a traceShow call, it's just ghci printing the result
Fun! 有趣!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.