今天在网上看到有人用 Haskell 写了一个一行代码实现四则运算的帖子,感觉十分厉害就想着自己也实现一个,但是无奈我看不懂 Haskell 代码,所以就找了一个 F# 版本的例子来模仿:
// 这里是被我展开后的版本
let eval =
let s (c: char) (s: string) = s.Split c
in let t (s: string) = s.Trim ()
in let o c r f = s c >> Seq.map (t >> f) >> Seq.reduce r
in int |> o '/' (/) |> o '*' (*) |> o '-' (-) |> o '+' (+)
可以看到,这个 eval
函数的功能并不是太复杂,他首先定义了一个函数 s
,这个函数的作用是将字符串安装指定的字符分割成数组。接着又定义了一个 t
函数,作用是去除字符串首尾的空格。接着,他又定义了一个 o
函数,这个函数的首先接受一个字符 c
,然后接受一个四则运算符(+、-、*、/),最后接受的是一个函数 f
,这个函数的作用是把字符串转换成数字。
而 o
函数实际的作用是先把一个字符串按照运算符符号字符进行分割(s c
)成数组,然后再把数组中的字串首尾的空格去掉同时调用 f
求出子串的值,最后,再把最终的值数组按照与四则运算符号 c
对应的函数进行 reduce
。
那么其中的 f
到底是怎么样的一个函数呢?为了弄明白这点,我们需要重新审视一下四则运算(不含括号)的步骤——先算加减,再算乘除。不过这里还要再加上一步——先读数字,再算加减,最后乘除。
在函数 o
的定义中我们知道,f
是要比运算符 r
先进行运算的,结合我们在上面总结出来的四则运算规律,我们可以得出:
读数字 >> 乘除 >> 加减
这也就是说,当运算符是乘除的时候,f
是一个把字符串转换成数字的函数 int
,当运算符是加减的时候,f
则是先读数,再乘除。
在弄明白了 eval
函数的原理之后,我们就可以试着自己模仿一个了,我在这里选择的语言是 Elm:
eval = (\char reducer fn -> String.split char >> List.map (String.trim >> fn) >> List.foldr reducer (if char == "+" || char == "-" then 0 else 1)) |> \op -> (String.toFloat >> Maybe.withDefault 0) |> op "*" (*) |> op "/" (/) |> op "-" (-) |> op "+" (+)
由于 Elm 中 let ... in ...
语法似乎不能写在同一行,所以我们只好把函数 o
写成一个 lambda 然后再把这个函数作为参数传入 eval 中。