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

发布于 2024-11-10 17:00:34 字数 874 浏览 1 评论 0原文

我在这篇文章中看到了http://news.ycombinator.com" rel="noreferrer">今天的黑客新闻。我正在努力解决同样的问题,即理解纯函数式编程如何帮助我抽象现实世界的问题。 7 年前,我从命令式编程转向了面向对象编程。我觉得我已经掌握了它,而且它对我很有帮助。在过去的几年里,我学习了函数式编程中的一些技巧和概念,例如映射和归约,我也喜欢它们。我已经在我的 OO 代码中使用了它们,并且对此很满意,但是在抽象一组指令时,我只能想到 OO 抽象来使代码更漂亮。

最近我一直在研究python中的一个问题,并且我一直在努力避免使用OO来解决它。在大多数情况下,我的解决方案看起来势在必行,而且我知道如果我使用面向对象,我可以让它看起来漂亮干净。我想我应该发布这个问题,也许功能专家可以提出一个既美观又实用的解决方案。如果必须的话,我可以发布我的丑陋代码,但我宁愿不发布。 :) 问题是:

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

在面向对象中,我可能会创建一个 SymlinkImage 基类、一个 ThumbnailSymlinkImage 子类和一个 OriginalSymlinkImage 子类。共享数据(在 SymlinkImage 类中)将类似于原始数据的路径。共享行为将创建符号链接。子类将实现一个名为“generate”的方法,该方法将负责创建缩略图(如果适用),并调用其超类来创建新的符号链接。

I saw this post in hacker news today. I am struggling with the same problems of understanding how pure functional programming will help me abstract a real world problem. I made the switch from imperative to OO programming 7 years ago. I feel that I have mastered it, and it has served me well. In the last couple years I have learned some tricks and concepts in functional programming like map and reduce, and I like them as well. I have used them in my OO code, and have been happy with that, but when abstracting a set of instructions, I can only think of OO abstractions to make the code prettier.

Recently I have been working on a problem in python, and I have been trying to avoid using OO to solve it. For the most part my solution looks imperative, and I know that I could make it look nice and clean if I used OO. I thought I would post the problem, and maybe the functional experts can pose a solution that's beautiful and functional. I can post my ugly code if I must, but would rather not. :) Here's the problem:

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.

In OO I would likely create a SymlinkImage base class, a ThumbnailSymlinkImage subclass, and an OriginalSymlinkImage subclass. The shared data (in SymlinkImage class) will be things like the path to the original. The shared behavior will be creating the symbolic link. The subclasses will implement a method called something like 'generate' that will be responsible for creating the thumbnail if applicable, and making the call to their superclass to create the new symbolic link.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

錯遇了你 2024-11-17 17:00:34

是的,您确实可以使用函数式方法以非常不同的方式做到这一点。

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

--
-- 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 的论文 “野兽之美:尴尬小队的功能语义”

Yeah, you'd really do this very differently using a functional approach.

Here's a sketch using the typed, by-default pure, functional programming language Haskell. We create new types for the key concepts of your problem, and break apart the work into discrete functions that do one task at a time. The IO and other side effects (like creating a symlink) are restricted to certain functions, and indicated with a type. To distinguish the two modes of operation, we use 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

Along with some helpers, which I'll leave the definition up to you.

-- 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

A truly beautiful solution would abstract out the request/response model with a data structure to represent the sequence of commands to be executed. A request comes in, builds a structure purely to represent what work it needs done, and that is handed to the execution engine with does things like creating files and so on. Then the core logic will be entirely pure functions (not that there's much core logic in this problem). For an example of this style of truly purely functional programming with effects, I recommend Wouter Swiestra's paper, ``Beauty in the Beast: A Functional Semantics for the Awkward Squad''

以为你会在 2024-11-17 17:00:34

就我个人而言,我认为问题在于您试图使用函数式编程来解决为命令式编程设计/陈述的问题。 3 种流行的范式(函数式、命令式、面向对象)具有不同的优点:

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

因此,当您处理问题时,首要任务是重新表述它,以便预期的范例可以正确地解决它。顺便说一下,作为一个侧节点,据我所知不存在“纯OOP”这样的东西。 OOP 类(无论是 Java、C#、C++、Python 还是 Objective C)的方法中的代码都是命令式的。

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

重述问题:

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

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

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。

重要提示:创建一个新的文件系统听起来很大,但事实并非如此。例如,如果这是更大的网络服务器中的模块,则“新文件系统”可以像新缩略图应该在哪里的指令一样简单。或者,在最坏的情况下,它可以是一个 IO monad 将缩略图放置到适当的位置。

Personally, I think the problem is that you are trying to use Functional Programming to solve problems that are designed/stated for Imperative programming. The 3 popular paradigms (Functional, Imperative, Object-oriented) have different strengths:

  • Functional Programming emphasizes on description of WHAT to be done, usually in term of input/result.
  • Imperative Programming emphasizes on HOW to do something, usually in term of list and order of steps to taken, and states to modify.
  • Object-oriented Programming emphasizes on the RELATIONSHIPS between entities in a system

Thus, when you approach a problem, the first order of business is to rephrase it such that the intended paradigm can properly solve it. By the way, as a side node, there is no such thing as "pure OOP" as far as I know. Code in the methods of your OOP classes (be it Java, C#, C++, Python, or Objective C) are all Imperative.

Back to your example: the way that you state your problem (first, then, also, finally) is imperative in nature. As such, construction of a functional solution is almost impossible (without doing the tricks like side-effect or monads, that is). Similarly, even if you create a bunch of classes, those classes are useless in and of themselves. To use them, you have to write imperative code (albeit these codes are embedded within classes) that solve the problem step by step.

To restate the problem:

  • Input: Image type (Full or thumbnail), Image name, file system
  • Output: the requested image, the file system with the requested image

From the new problem statement, you can solve it like this:

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)

Of course, this is incomplete, but we can always recursively solve lookupImage, lookupThumb, createThumbnail, and addThumbnailToFs roughly the same way.

Big Note: creating a new filesystem sounds big, but it should not be. For example, if this is a module in a bigger web server, the "new filesystem" can be as simple as the instruction to where the new thumbnail should be. Or, at the very worst, it can be an IO monad to put the thumbnail to the appropriate location.

苹果你个爱泡泡 2024-11-17 17:00:34

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

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

我读过 Armstrong 的 erlang 书。这很艰难。我有一个小项目要做,但我只是继续努力。

这个项目失败了,但几个月后,我已经在脑海中充分映射了一切,我不再像以前那样思考物体。

我确实经历了将对象映射到 erlang 进程的阶段,但这并不太长,我就摆脱了它。

现在,切换范式就像切换语言,或者从一辆车换到另一辆车。开我父亲的车和开我的卡车感觉不一样,但没过多久就又习惯了。

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

The only way to change your way of thinking is to change your way of thinking. I can tell you what worked for me:

I wanted to work on a personal project that required concurrency. I looked around and found erlang. I chose it because I thought it had the best support for concurrency, not for any other reason. I had never worked with a functional language before (and just for comparison, I started doing object oriented programming in the early 1990s.)

I read Armstrong's erlang book. It was tough. I had a small project to work on, but I just kept hammering at it.

The project was a failure, but after a couple months I had mapped everything in my head sufficiently that I no longer think in objects the way I used to.

I did go thru a phase where I mapped objects to erlang processes, but that wasn't too long and I got out of it.

Now switching paradigms is like switching languages, or going from one car to another. Driving my father's car feels different than my truck, but it doesn't take long to get used to it again.

I think working in Python might be holding you back, and I would strongly recommend you check out erlang. Its syntax is very alien-- but that's also good, as a more traditional syntax would (at least for me) lead to trying to program it the old ways and being frustrated.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文