从 Ruby OOP 到 Elixir Functional 示例
发布: (2025年12月2日 GMT+8 12:00)
4 min read
原文: Dev.to
Source: Dev.to
Ruby 实现
学习函数式编程最有效的方法之一是把在面向对象语言中写好的相同功能转换为函数式风格。下面是一个用 Ruby 编写的简单 ToyRobot 对象,拥有三个属性(x、y、direction)和动作(move、turn left、turn 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(函数式) – “函数接收数据并返回新数据。”