从 Ruby OOP 到 Elixir Functional 示例

发布: (2025年12月2日 GMT+8 12:00)
4 min read
原文: Dev.to

Source: Dev.to

Ruby 实现

学习函数式编程最有效的方法之一是把在面向对象语言中写好的相同功能转换为函数式风格。下面是一个用 Ruby 编写的简单 ToyRobot 对象,拥有三个属性(xydirection)和动作(moveturn leftturn right)。

class ToyRobot
  attr_reader :x, :y, :direction
  DIRECTIONS = [:north, :east, :south, :west]
  BOUNDS = {x: 0..10, y: 0..10}

  def initialize
    @x = 0
    @y = 0
    @direction = :north
  end

  def place(x, y, direction)
    if DIRECTIONS.include?(direction)
      @x = x
      @y = y
      @direction = direction
    else
      raise "Invalid direction"
    end
  end

  def report
    {x: @x, y: @y, direction: @direction}
  end

  def move
    case @direction
    when :north then @y += 1
    when :east  then @x += 1
    when :south then @y -= 1
    when :west  then @x -= 1
    end

    if @x > BOUNDS[:x].max || @y > BOUNDS[:y].max
      raise "Out of bounds"
    end
  end

  def right
    case @direction
    when :north then @direction = :east
    when :east  then @direction = :south
    when :south then @direction = :west
    when :west  then @direction = :north
    end
  end

  def left
    # implement left
  end
end

Elixir 实现

下面是同样的机器人用 Elixir 表达的代码。如果你是 Elixir 新手,先尝试自己实现一遍,再对照代码。

defmodule ToyRobot do
  @directions [:north, :east, :south, :west]
  @bounds_x 0..20
  @bounds_y 0..20

  defstruct x: 0, y: 0, direction: :east

  def new() do
    {:ok, robot} = place(0, 0, :east)
    robot
  end

  def place(_x, _y, direction) when direction not in @directions do
    {:error, :invalid_direction}
  end

  def place(x, y, _direction) when x not in @bounds_x or y not in @bounds_y do
    {:error, :out_of_bounds}
  end

  def place(x, y, direction) do
    {:ok, %ToyRobot{x: x, y: y, direction: direction}}
  end

  def report(%ToyRobot{x: x, y: y, direction: direction}) do
    {x, y, direction}
  end

  def right(%ToyRobot{direction: direction} = robot) do
    new_direction =
      case direction do
        :north -> :east
        :east  -> :south
        :south -> :west
        :west  -> :north
      end

    %ToyRobot{robot | direction: new_direction}
  end

  def left(%ToyRobot{direction: direction} = robot) do
    # implement right
  end

  def move(%ToyRobot{x: x, y: y, direction: direction} = robot) do
    {new_x, new_y} =
      case direction do
        :north -> {x, y - 1}
        :east  -> {x + 1, y}
        :south -> {x, y + 1}
        :west  -> {x - 1, y}
      end

    if new_x in @bounds_x and new_y in @bounds_y do
      %ToyRobot{robot | x: new_x, y: new_y}
    else
      robot
    end
  end
end

关键差异

方法 vs 函数

  • Ruby(OOP):方法属于类并操作对象的内部状态。

    robot = ToyRobot.new
    robot.move
    robot.left
  • Elixir(函数式):模块用于组织函数;你需要显式传递数据。

    robot = ToyRobot.new()
    ToyRobot.move(robot)
    ToyRobot.left(robot)

在 Elixir 中,所有必需的数据都必须作为参数提供,而 Ruby 方法可以隐式访问对象的状态。

可变 vs 不可变

  • Ruby:方法会改变对象的状态。

    robot = ToyRobot.new
    robot.move   # @x, @y 变化
    robot.report # => {x: 0, y: 1, direction: :north}
  • Elixir:数据结构是不可变的;函数返回新的结构体。

    robot = ToyRobot.new()
    # => %ToyRobot{x: 0, y: 0, direction: :east}
    
    new_robot = ToyRobot.move(robot)
    # => %ToyRobot{x: 1, y: 0, direction: :east}
    
    robot
    # => %ToyRobot{x: 0, y: 0, direction: :east}  # 未改变

Elixir 的管道运算符 (|>) 使得链式转换更加简洁:

robot = ToyRobot.new()
|> ToyRobot.move()
|> ToyRobot.move()
|> ToyRobot.right()
|> ToyRobot.left()

结论

根本区别在于状态的处理方式:

  • Ruby(OOP) – “对象自行改变其状态。”
  • Elixir(函数式) – “函数接收数据并返回新数据。”
Back to Blog

相关文章

阅读更多 »

使用 Clprolf 分离类职责

概述:设计干净、结构良好的类是面向对象编程中的核心挑战。Clprolf 引入 declensions —— 一种简单的方式来表达……

函数式四叉树

请提供您希望翻译的具体摘录或摘要文本,我才能为您进行简体中文翻译。

Java OOP概念

Forem 标志https://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%...