2017-08-07 177 views
4
let inline (=~) a b = abs (single a - single b) <= 0.001f 

type Vector = 
    { x : single; y : single; z : single } 

    static member (=~) (v1, v2) = (v1.x =~ v2.x) && (v1.y =~ v2.y) && (v1.z =~ v2.z) 

let v1, v2 = 
    { x = 0.1f; y = single Math.PI; z = 0.f }, 
    { x = 0.1f; y = 3.14159f; z = 0.0001f } 

v1 =~ v2 

編譯F#運營商抱怨:The type 'Vector' does not support a conversion to the type 'single'不能正確解析

我不明白這一點。顯然,特定於類型的運算符不會優先於泛型運算符,這會挫敗我的直覺。做這項工作有什麼竅門?

回答

4

當您使用let定義自定義運算符時,它將始終優先於類型定義的運算符。處理這個問題的簡單方法是避免在本地和全球運營商名稱中發生名稱衝突,或者儘量減少綁定運營商的範圍。例如,你可以把全球=~運營商在一個單獨的模塊:

module VectorImplementation = 
    let inline (=~) a b = abs (single a - single b) <= 0.001f 

module Vectors = 
    open VectorImplementation 
    type Vector = 
     { x : single; y : single; z : single } 
     static member (=~) (v1, v2) = 
     (v1.x =~ v2.x) && (v1.y =~ v2.y) && (v1.z =~ v2.z) 

open System 
open Vectors 

let v1, v2 = 
    { x = 0.1f; y = single Math.PI; z = 0.f }, 
    { x = 0.1f; y = 3.14159f; z = 0.0001f } 

v1 =~ v2 

還有一個somewhat strange hack,你可以用它來定義被重載的全局let結合的運營商。對於這是不是一個好主意,我們有一些爭議 - 我認爲通常可以避免衝突而不訴諸這個魔術,但其他人可能會不同意。

2

頁97 F# 4.0 language specification的說:

如果操作員不能解決用戶定義的或者庫定義的運營商,名稱解析規則(§14.1)保證操作解析爲表達式隱式使用涉及操作數類型的靜態成員調用表達式(第0節)。這意味着未在F#庫中定義的運算符的有效行爲是要求運算符的一個操作數的類型與運算符具有相同名稱的靜態成員。

正如托馬斯Petricek在他的回答,這意味着你在全球範圍定義的=~操作員「隱藏」的=~運營商對你Vector型剛指出。他的回答暗示了處理這個問題的好方法。另一種方法是簡單地使這兩個運營商不同

let inline (==~) a b = abs (single a - single b) <= 0.001f 
type Vector = 
    { x : single; y : single; z : single } 
    static member (=~) (v1, v2) = 
     (v1.x ==~ v2.x) && (v1.y ==~ v2.y) && (v1.z ==~ v2.z) 

你會發現這個方法比托馬斯的做法比較簡單,或者你會發現他的方法更簡單。這是一個風格偏好的問題;任何人都應該工作。