通过游戏开发学习 F# 第 3 章错误

Learning F# Through Game Dev chapter 3 error

我正在阅读《通过游戏开发学习 F#》一书,在第 3 章中我得到了一个带有以下代码的 TypeInitializationException:

// edit: missing definitions, thanks John
let earth_mass  = 5.97e24<kg> // defined in other file that is ref'd in ok
let moon_mass = 7.35e22<kg>

let lerp (x:float<'T>) (y:float<'T>) (a:float) = (x * a) + (y * (1.0 - a))
// inside list comprehension for loop -
let m = (lerp earth_mass moon_mass (rand.NextDouble())) * 1.0e-4 // error!!

我已经下载了作者的源代码,看看我是否遗漏了什么,但它产生了同样的错误。 异常消息几乎没有提供问题的线索(至少对我而言)。

下面是本章的全部代码,供参考:

namespace Games

module Chapter3 =
open System
open System.Threading
open Games.Math

type Asteroid =
    {
    Position : Vector2<m>
    Velocity : Vector2<m/s>
    Mass     : float<kg>
    Name     : string
    }

let dt = 60.0<s>
let G = 6.67e-11<m^3 * kg^-1 * s^-2>

let earth_radius = 6.37e6<m>
let field_size = earth_radius * 60.0
let max_velocity = 2.3e4<m/s>
let earth_mass  = 5.97e24<kg>
let moon_mass = 7.35e22<kg>

let create_field num_asteroids =
    let lerp (x:float<'T>) (y:float<'T>) (a:float) = x * a + y * (1.0 - a)
    let rand = Random()
    [
    for i = 1 to num_asteroids do
        let m = (lerp earth_mass moon_mass (rand.NextDouble())) * 1.0e-4
        let x = lerp 0.0<m> field_size (rand.NextDouble())
        let y = lerp 0.0<m> field_size (rand.NextDouble())
        let vx = max_velocity * (rand.NextDouble() * 2.0 - 1.0) * 0.1
        let vy = max_velocity * (rand.NextDouble() * 2.0 - 1.0) * 0.1

        yield
            {
            Position = { X = x; Y = y}
            Velocity = { X = vx; Y = vy}
            Mass = m
            Name = "a"
            }
    ]

let f0 = create_field 20

let clamp (p:Vector2<_>, v:Vector2<_>) = 
    let p,v =
        if p.X < 0.0<_> then
            {p with X = 0.0<_>}, {v with X = -v.X}
        else
            p,v
    let p,v =
        if p.X > field_size then
            {p with X = field_size}, {v with X = -v.X}
        else
            p,v
    let p,v =
        if p.Y < 0.0<_> then
            {p with Y = 0.0<_>}, {v with Y = -v.Y}
        else
            p,v
    let p,v =
        if p.Y > field_size then
            {p with Y = field_size}, {v with Y = -v.Y}
        else
            p,v
    p,v

let force (a:Asteroid, a':Asteroid) =
    let dir = a'.Position - a.Position
    let dist = dir.Length + 1.0<m>
    G * a.Mass * a'.Mass * dir/(dist * dist * dist)

let simulation_step (asteroids:Asteroid list) =
    [
        for a in asteroids do
            let forces =
                [
                    for a' in asteroids do
                        if a' <> a then
                            yield force(a, a')
                ]    
            let F = List.sum forces
            let p', v' = clamp(a.Position, a.Velocity)
            yield
                {
                    a with
                        Position = p' + dt * v'
                        Velocity = v' + dt * F/a.Mass
                }
    ]

let print_scene (asteroids:Asteroid list) =
    do Console.Clear()
    for i = 0 to 79 do
        Console.SetCursorPosition(i, 0)
        Console.Write("*")
        Console.SetCursorPosition(i, 23)
        Console.Write("*")
    for j = 0 to 23 do
        Console.SetCursorPosition(0, j)
        Console.Write("*")
        Console.SetCursorPosition(79, j)
        Console.Write("*")
    let set_cursor_on_body b =
        Console.SetCursorPosition(
            ((b.Position.X/4.0e8<m>) * 78.0 + 1.0) |> int,
            ((b.Position.Y/4.0e8<m>) * 23.0 + 1.0) |> int)
    for a in asteroids do
        do set_cursor_on_body a
        do Console.Write(a.Name)
    do Thread.Sleep(100)

let simulation() =
    let rec simulation m =
        do print_scene m
        let m' = simulation_step m
        do simulation m'
    do simulation f0

数学模块代码:

namespace Games

module Math =
[<Measure>]
type m //metres

[<Measure>]
type kg //kilogram

[<Measure>]
type s // seconds

[<Measure>]
type N = kg*m/s^2 //Newtons

type Vector2<[<Measure>]'T> =
    {
        X : float<'T>
        Y : float<'T>
    }

    static member Zero : Vector2<'T> =
        { X = 0.0<_>; Y = 0.0<_> }

    static member (+) (v1:Vector2<'T>, v2:Vector2<'T>) : Vector2<'T> =
        { X = v1.X + v2.X; Y = v1.Y + v2.Y }

    static member (+) (v:Vector2<'T>, k:float<'T>) : Vector2<'T> =
        { X = v.X + k; Y = v.Y + k }

    static member (+) (k:float<'T>, v:Vector2<'T>) : Vector2<'T> = v + k

    static member (~-) (v:Vector2<'T>) : Vector2<'T> =
        { X = -v.X; Y = -v.Y }

    static member (-) (v1:Vector2<'T>, v2:Vector2<'T>) : Vector2<'T> =
        v1 + (-v2)

    static member (-) (v:Vector2<'T>, k:float<'T>) : Vector2<'T> =
        v + (-k)

    static member (-) (k:float<'T>, v:Vector2<'T>) : Vector2<'T> =
        k + (-v)

    static member (*) (v1:Vector2<'a>, v2:Vector2<'b>) : Vector2<'a * 'b> =
        { X = v1.X * v2.X; Y = v1.Y * v2.Y }

    static member (*) (v:Vector2<'a>, f:float<'b>) : Vector2<'a * 'b> =
        { X = v.X * f; Y = v.Y * f }

    static member (*) (f:float<'b>, v:Vector2<'a>) : Vector2<'b * 'a> =
        { X = f * v.X; Y = f * v.Y }

    static member (/) (v:Vector2<'a>, f:float<'b>) : Vector2<'a / 'b> =
        v * (1.0/f)

    member this.Length : float<'a> =
        sqrt((this.X * this.X + this.Y * this.Y))

    static member Distance(v1:Vector2<'T>, v2:Vector2<'T>) =
        (v1 - v2).Length

    static member Normalize(v:Vector2<'T>) : Vector2<1> = 
        v / v.Length

运行 示例的代码:

[<EntryPoint>]
let main argv = 
    Games.Chapter3.simulation()
    0 // return an integer exit code

有人可以使用 VS2013 和 F# 3.1/.Net4.5 解释发生了什么吗

谢谢。

米克

相信您 运行 陷入 this F# 编译器错误。它存在于 F# 3.1.2 及更早版本中,但此后已修复,因此在 4.0 及更高版本中这将起作用。

根据问题讨论,在 F# 3.0 和更早版本中存在错误,但并不总是导致运行时崩溃。查看本书的示例代码项目,我猜作者使用 F# 3.0 或更早版本编写这些示例,并没有意识到它们会触发错误。

解决方法:

  • 在 Release 模式下编译(启用优化),codegen 应该是正确的(假设这确实是同一个问题)
  • 尝试使用 F# 4.0 预发布版(通过 VS 2015 CTPs or grab just the F# bits from here