受歧视工会内部的分配
Assignment inside discriminated unions
我是 F# 的新手,正在尝试开发一款贪吃蛇游戏,如果这听起来很愚蠢,请原谅我。
现在,这是游戏的模型:
// value objects
type Position = int * int
type Block = { Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
type World = { Snake: Snake }
//
为了让蛇移动起来更简单,我希望每个 Direction
都有自己的 Position
,就像:
type Direction =
| North of Position (0, 1)
| South of Position (0, -1)
| West of Position (-1, 0)
| East of Position (0, 1)
所以我可以在这里应用它:
let moveSnakeHead direction snake =
// easily move the snake's head
// tail[0].x += direction.x, tail[0].y += direction.y
但是,在我看来,of Position (x, y)
在受歧视的工会内部不可能做到这一点?
有人可以解释为什么吗?我正在尽力学习类型。还有什么替代方案?
确保您清楚 F# 中 values 和 types 之间的区别。对于刚接触 F# 的人来说,这是一个常见的陷阱,尤其是在受歧视的联合周围。
1
是 int
.
类型的值
(1, 1)
是 (int * int)
.
类型的值
当你定义一个DU类型时,每个case都可以保存某种类型的数据:
type DUType =
| DUCase1 of int
所以每个 DU 案例都可以包含 any int
,而不仅仅是特定的 int
.
您的代码中还有一个 类型别名 :type Position = int * int
。这只是说您可以在任何地方写 Position
,它的意思与 int * int
相同。它实际上不是另一种类型。
因此在您的代码中您不能说 DU 案例必须始终包含某个 值。您需要编写一个函数来代替 Direction
和 returns 一个 Position
:
type Direction =
| North
| South
| West
| East
let directionToPostion direction : Position =
match direction with
| North -> (0, 1)
| South -> (0, -1)
| West -> (-1, 0)
| East -> (0, 1)
您编写的任何 F# 代码通常始终处于 3 种“模式”:
- 价值
- 类型
- 模式(如模式匹配)
尝试确保您在任何给定时间都知道自己属于这三个中的哪一个。
滥用@TheQuickFrownFox 的回答,我实际上让它按照我认为你想要的方式工作。我认为您的数据类型过于复杂,但 可以创建这样的贪吃蛇游戏。注意引用类型和可变变量的用法。
// value objects
type Position = int * int
type Block = { mutable Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
//
let directionToPostion = function
| North -> (0, 1)
| South -> (0, -1)
| West -> (-1, 0)
| East -> (0, 1)
let moveSnakeHead (direction: Direction) (snake: Snake ref) =
// easily move the snake's head
let (dirX, dirY) = directionToPostion direction
let snakeHeadPos = (!snake).Tail.Blocks.[0].Position
(!snake).Tail.Blocks.[0].Position <- (dirX + fst snakeHeadPos, dirY + snd snakeHeadPos)
let blocks: Block list = [ {Position = (5,3)}; {Position = (4,2)} ]
let tail: Tail = { Blocks = blocks }
let snake = ref <| {Tail = tail}
printfn "%A" blocks
moveSnakeHead North snake
printfn "%A" blocks
快速说明:
F# 不是一种干净的函数式语言,因此您可以像使用 object-oriented 语言一样使用它,但需要做一些工作,但这不是首选方式。最佳情况下,您将拥有一个读取蛇的函数(我建议简单地使用类型 type Snake = (int * int) list
,并将其输出(映射)到包含更新位置的新列表中。这将更清晰、更易于维护且更贴合F# 的设计目标。
编辑:
我决定回来更新我的答案以包含我认为在 F# 中执行此操作的规范方法。我想您会发现它更简洁易读:
type Snake = (int * int) list
type Direction = North | South | East | West
let moveSnake snake dir =
if List.isEmpty snake then []
else
let h = List.head snake
match dir with
| North -> (fst h, snd h - 1) :: List.tail snake
| South -> (fst h, snd h + 1) :: List.tail snake
| East -> (fst h + 1, snd h) :: List.tail snake
| West -> (fst h - 1, snd h) :: List.tail snake
let snake = [(5,3); (1,2)]
printfn "%A" snake
printfn "%A" <| moveSnake snake North
如果你真的想要,你可以声明蛇变量mutable
,这样你就可以改变蛇了。但我建议远离这一点,并尽可能让您的程序严格运行。
我是 F# 的新手,正在尝试开发一款贪吃蛇游戏,如果这听起来很愚蠢,请原谅我。
现在,这是游戏的模型:
// value objects
type Position = int * int
type Block = { Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
type World = { Snake: Snake }
//
为了让蛇移动起来更简单,我希望每个 Direction
都有自己的 Position
,就像:
type Direction =
| North of Position (0, 1)
| South of Position (0, -1)
| West of Position (-1, 0)
| East of Position (0, 1)
所以我可以在这里应用它:
let moveSnakeHead direction snake =
// easily move the snake's head
// tail[0].x += direction.x, tail[0].y += direction.y
但是,在我看来,of Position (x, y)
在受歧视的工会内部不可能做到这一点?
有人可以解释为什么吗?我正在尽力学习类型。还有什么替代方案?
确保您清楚 F# 中 values 和 types 之间的区别。对于刚接触 F# 的人来说,这是一个常见的陷阱,尤其是在受歧视的联合周围。
1
是 int
.
(1, 1)
是 (int * int)
.
当你定义一个DU类型时,每个case都可以保存某种类型的数据:
type DUType =
| DUCase1 of int
所以每个 DU 案例都可以包含 any int
,而不仅仅是特定的 int
.
您的代码中还有一个 类型别名 :type Position = int * int
。这只是说您可以在任何地方写 Position
,它的意思与 int * int
相同。它实际上不是另一种类型。
因此在您的代码中您不能说 DU 案例必须始终包含某个 值。您需要编写一个函数来代替 Direction
和 returns 一个 Position
:
type Direction =
| North
| South
| West
| East
let directionToPostion direction : Position =
match direction with
| North -> (0, 1)
| South -> (0, -1)
| West -> (-1, 0)
| East -> (0, 1)
您编写的任何 F# 代码通常始终处于 3 种“模式”:
- 价值
- 类型
- 模式(如模式匹配)
尝试确保您在任何给定时间都知道自己属于这三个中的哪一个。
滥用@TheQuickFrownFox 的回答,我实际上让它按照我认为你想要的方式工作。我认为您的数据类型过于复杂,但 可以创建这样的贪吃蛇游戏。注意引用类型和可变变量的用法。
// value objects
type Position = int * int
type Block = { mutable Position: Position }
type Tail = { Blocks: Block list }
type Direction =
| North
| South
| West
| East
//
// entities
type Snake = { Tail: Tail }
//
let directionToPostion = function
| North -> (0, 1)
| South -> (0, -1)
| West -> (-1, 0)
| East -> (0, 1)
let moveSnakeHead (direction: Direction) (snake: Snake ref) =
// easily move the snake's head
let (dirX, dirY) = directionToPostion direction
let snakeHeadPos = (!snake).Tail.Blocks.[0].Position
(!snake).Tail.Blocks.[0].Position <- (dirX + fst snakeHeadPos, dirY + snd snakeHeadPos)
let blocks: Block list = [ {Position = (5,3)}; {Position = (4,2)} ]
let tail: Tail = { Blocks = blocks }
let snake = ref <| {Tail = tail}
printfn "%A" blocks
moveSnakeHead North snake
printfn "%A" blocks
快速说明:
F# 不是一种干净的函数式语言,因此您可以像使用 object-oriented 语言一样使用它,但需要做一些工作,但这不是首选方式。最佳情况下,您将拥有一个读取蛇的函数(我建议简单地使用类型 type Snake = (int * int) list
,并将其输出(映射)到包含更新位置的新列表中。这将更清晰、更易于维护且更贴合F# 的设计目标。
编辑:
我决定回来更新我的答案以包含我认为在 F# 中执行此操作的规范方法。我想您会发现它更简洁易读:
type Snake = (int * int) list
type Direction = North | South | East | West
let moveSnake snake dir =
if List.isEmpty snake then []
else
let h = List.head snake
match dir with
| North -> (fst h, snd h - 1) :: List.tail snake
| South -> (fst h, snd h + 1) :: List.tail snake
| East -> (fst h + 1, snd h) :: List.tail snake
| West -> (fst h - 1, snd h) :: List.tail snake
let snake = [(5,3); (1,2)]
printfn "%A" snake
printfn "%A" <| moveSnake snake North
如果你真的想要,你可以声明蛇变量mutable
,这样你就可以改变蛇了。但我建议远离这一点,并尽可能让您的程序严格运行。