努力使用纯函数式编程来解决日常问题

时间:2011-05-31 16:39:43

标签: functional-programming paradigms

我今天在this post看到了hacker news。我正在努力解决理解纯函数式编程如何帮助我抽象现实世界问题的相同问题。 7年前,我从命令式转向OO编程。我觉得我已经掌握了它,它对我很有帮助。在过去的几年里,我在函数式编程中学到了一些技巧和概念,比如map和reduce,我也喜欢它们。我已经在我的OO代码中使用它们,并且对此感到满意,但是当抽象出一组指令时,我只能想到OO抽象来使代码变得更漂亮。

最近我一直在研究python中的一个问题,我一直试图避免使用OO来解决它。在大多数情况下,我的解决方案看起来势在必行,我知道如果我使用OO,我可以让它看起来很干净。我想我会发布问题,也许功能专家可以提出一个美观和功能性的解决方案。如果必须,我可以发布我丑陋的代码,但不愿意。 :)这是问题所在:

用户可以请求图像或图像的缩略图。如果用户请求图像的缩略图,但它尚不存在,请使用python的PIL模块创建它。还使用人类可读路径创建指向原始或缩略图的符号链接,因为原始图像名称是哈希码,而不是描述其内容。最后,重定向到该图像的符号链接。

在OO中,我可能会创建一个SymlinkImage基类,一个ThumbnailSymlinkImage子类和一个OriginalSymlinkImage子类。共享数据(在SymlinkImage类中)将是原始路径之类的东西。共享行为将创建符号链接。子类将实现一个名为'generate'的方法,该方法将负责创建缩略图(如果适用),并调用其超类来创建新的符号链接。

3 个答案:

答案 0 :(得分:20)

是的,使用功能方法你的确会做得非常不同。

这是使用类型化的,默认的纯函数式编程语言Haskell的草图。我们为您的问题的关键概念创建新类型,并将工作分解为一次执行一项任务的离散函数。 IO和其他副作用(如创建符号链接)仅限于某些功能,并用类型表示。为区分这两种操作模式,我们使用a sum type

--
-- User can request an image or a thumbnail of the image.
-- If the user requests the thumbnail of the image, and it doesn't yet exist, create it using
-- python's PIL module. Also create a symbolic link to the original or
-- thumbnail with a human readable path, because the original image name is a
-- hashcode, and not descriptive of it's contents. Finally, redirect to the
-- symbolic link of that image.
--

module ImageEvent where

import System.FilePath
import System.Posix.Files

-- Request types
data ImgRequest = Thumb ImgName | Full ImgName

-- Hash of image 
type ImgName = String

-- Type of redirects
data Redirect

request :: ImgRequest -> IO Redirect
request (Thumb img) = do
    f <- createThumbnail img
    let f' = normalizePath f
    createSymbolicLink f f'
    return (urlOf f)

request (Full img)  = do
    createSymbolicLink f f'
    return (urlOf f)
    where
        f  = lookupPath img
        f' = normalizePath f

除了一些助手之外,我将把这个定义留给你。

-- Creates a thumbnail for a given image at a path, returns new filepath
createThumbnail :: ImgName -> IO FilePath
createThumbnail f = undefined
    where
        p = lookupPath f

-- Create absolute path from image hash
lookupPath :: ImgName -> FilePath
lookupPath f = "/path/to/img" </> f <.> "png"

-- Given an image, construct a redirect to that image url
urlOf :: FilePath -> Redirect
urlOf = undefined

-- Compute human-readable path from has
normalizePath :: FilePath -> FilePath
normalizePath = undefined

一个真正漂亮的解决方案将使用数据结构抽象出请求/响应模型,以表示要执行的命令序列。请求进来,构建一个纯粹的结构来表示它需要完成的工作,并将其传递给执行引擎,并执行创建文件等操作。那么核心逻辑将是完全纯粹的功能(并不是说这个问题有很多核心逻辑)。有关这种真正纯粹功能性编程风格的例子,我推荐Wouter Swiestra的论文,``Beauty in the Beast: A Functional Semantics for the Awkward Squad''

答案 1 :(得分:5)

改变思维方式的唯一方法就是改变你的思维方式。我可以告诉你什么对我有用:

我想开发一个需要并发的个人项目。我环顾四周,找到了二郎。我选择它是因为我认为它对并发性有最好的支持,而不是出于任何其他原因。我之前从未使用过函数式语言(仅仅为了比较,我在20世纪90年代初开始进行面向对象编程。)

我读过阿姆斯特朗的二郎书。这很艰难。我有一个小项目要做,但我一直在努力。

这个项目失败了,但几个月之后我已经把所有东西都映射到了我脑海中,以至于我不再按照以前的方式思考物体了。

我确实通过了将对象映射到erlang进程的阶段,但这不是太长而且我已经离开了它。

现在切换范式就像切换语言,或者从一辆车转到另一辆车。驾驶我父亲的车感觉与我的卡车有所不同,但是不用花很长时间再习惯它。

我认为在Python中工作可能会阻碍你,我强烈建议你查看erlang。它的语法非常陌生 - 但这也很好,因为更传统的语法会(至少对我而言)导致尝试用旧的方式编程并且感到沮丧。

答案 2 :(得分:4)

就个人而言,我认为问题在于您正在尝试使用函数式编程来解决为命令式编程设计/声明的问题。 3种流行的范式(功能性,势在必行,面向对象)具有不同的优势:

  • 功能编程强调对要做的事情的描述,通常是在输入/结果方面。
  • 命令式编程强调如何做某事,通常是按照列表和要采取的步骤顺序,以及要修改的状态。
  • 面向对象编程强调系统中实体之间的关系

因此,当您处理问题时,业务的第一个顺序是对其进行重新定义,以使预期的范例能够正确地解决它。顺便说一下,作为一个侧节点,据我所知,没有“纯OOP”这样的东西。 OOP类的方法中的代码(无论是Java,C#,C ++,Python还是Objective C)都是势在必行。

回到你的例子:你陈述问题的方式(首先,然后,也是,最后)本质上是必要的。因此,功能性解决方案的构建几乎是不可能的(没有像副作用或monad这样的技巧)。同样,即使你创建了一堆类,这些类本身也是无用的。要使用它们,您必须编写命令式代码(尽管这些代码嵌入在类中),逐步解决问题。

重述问题:

  • 输入:图像类型(完整或缩略图),图像名称,文件系统
  • 输出:请求的图像,带有请求图像的文件系统

从新的问题陈述中,你可以这样解决:

def requestImage(type, name, fs) : 
    if type == "full" :
        return lookupImage(name, fs), fs
    else:
        thumb = lookupThumb(name, fs)
        if(thumb) :
            return thumb, fs
        else:
            thumb = createThumbnail(lookupImage(name, fs))
            return thumb, addThumbnailToFs(fs, name, thumb)

当然,这是不完整的,但我们总是可以递归地递归地解决lookupImage,lookupThumb,createThumbnail和addThumbnailToFs。

大注意:创建一个新的文件系统听起来很大,但不应该。例如,如果这是更大的Web服务器中的模块,则“新文件系统”可以像新缩略图所在的指令一样简单。或者,在最糟糕的情况下,它可以是一个IO monad将缩略图放到适当的位置。