这是一个关于在我看来函数式编程中非常常见的模式的名称的问题。
在像 Haskell 这样的纯函数式编程语言中,程序的“外部”部分通常允许执行 IO 副作用,例如打印到屏幕或写入数据库(例如 IO monad),然后是程序的内部部分仅由纯函数组成的程序。
如果纯函数想要执行某种副作用,例如打印,可以将其表示为代数数据类型的值,该值返回到外部 IO 部分,然后在其中执行。
这是一个有点人为的例子:
data Command = PutStrLn String
fizz :: Int -> Command
fizz n | n `mod` 15 == 0 = PutStrLn "FizzBuzz"
| n `mod` 3 == 0 = PutStrLn "Fizz"
| n `mod` 5 == 0 = PutStrLn "Buzz"
| otherwise = PutStrLn (show n)
runCommand :: Command -> IO ()
runCommand (PutStrLn str) = putStrLn str
main :: IO ()
main = mapM_ runCommand $ map fizz [1..100]
fizz
函数是完全纯粹的,但我不只是返回String
,而是选择了字符串以及预期的效果。这里使用 fizzuzz 只是一个玩具示例,用于展示 Command
类型和有效的 runCommand
函数之间的关系。
在更复杂的程序中,
fizz
将是我的应用程序逻辑的一部分,并且Command
可以是例如代表 Web API 或操作系统调用或其他一些 IO 效果。
是否有一个名称可以用这种方式表示有效的 API 和数据?
我经常看到纯部分被描述为特定领域语言 (DSL) 的抽象语法树 (AST),而“运行”函数则被描述为该语言的解释器。
正如 Fyodor Soikin 在评论中提到的,免费 monad 可以被视为这种编程风格的一个例子。
Gary Bernhardt 称之为“功能核心,命令外壳”。 通常 AST 被定义为递归求和类型,并且由于
求和类型与访问者模式同构,如果您更倾向于面向对象,您也可以将解释器视为访问者。 (需要明确的是,不是在 Haskell 中,而是在其他上下文中。) 在我看来,这种编程风格独立地出现在多个地方,并且通常有一位作者/发明者不了解现有技术。因此,即使有很多重叠,您也会发现不同的名称。