背景:它是功能性DI的变体。在Scott's post之后,我写了一个翻译。扭曲的是我的翻译是通用的,并根据你提供的内容进行参数化。
出于测试目的,我想通过另一个口译员,其中有瑕疵 - 我怎么办?以下是问题的简化概述:
let y f =
let a = f 1
let b = f 2L
(a,b)
f
是我的通用解释器,但在这里它显然受到int -> 'a
首次使用的限制。
在这个简化的场景中,我可以只传递两次解释器,但在我的实际实现中,类型空间相当大(基类型x3输出类型)。
是否有一些F#机制让我这样做,没有太多的开销?
答案 0 :(得分:9)
你不能在F#中用函数来做这件事。当作为值传递时,函数会失去通用性。
但是,F#确实有一种机制可以做到这一点,尽管有点笨拙:接口。接口方法可以是通用的,因此您可以使用它们来包装通用函数:
type Wrapper =
abstract member f<'a> : 'a -> 'a
let y (w: Wrapper) =
let a = w.f 1
let b = w.f 2L
(a, b)
let genericFn x = x
// Calling y:
y { new Wrapper with member __.f x = genericFn x }
缺点是,你不能回到高阶函数,以免失去通用性。你必须有接口一直到海龟。例如,您无法通过将实例抽象为函数来简化实例创建:
let mkWrapper f =
// no can do: `f` will be constrained to a non-generic type at this point
{ new Wrapper with member __.f x = f x }
但是你可以在另一方面提供一些便利。至少摆脱类型注释:
type Wrapper = abstract member f<'a> (x: 'a): 'a
let callF (w: Wrapper) x = w.f x
let y w =
let a = callF w 1
let b = callF w 2L
(a,b)
(注意:上面的代码中可能存在轻微的语法错误,因为我正在手机上写字)
答案 1 :(得分:2)
不确定您是否仍然感兴趣,因为您已经接受了答案,但正如@Fyodorsoikin所要求的那样,这里是静态的&#39;静电&#39;这一切都发生在编译时,所以没有运行时开销:
let inline y f =
let a = f $ 1
let b = f $ 2L
(a, b)
type Double = Double with static member inline ($) (Double, x) = x + x
type Triple = Triple with static member inline ($) (Triple, x) = x + x + x
type ToList = ToList with static member ($) (ToList, x) = [x]
let res1 = y Double
let res2 = y Triple
let res3 = y ToList
当我需要在任意结构上使用泛型函数时,我使用这种技术,我用一种方法命名类型&#39; Invokable&#39;。
<强>更新强>
要向函数添加参数,请将其添加到DU,如下所示:
type Print<'a> = Print of 'a with
static member inline ($) (Print printer, x) = printer (string x)
let stdout (x:string) = System.Console.WriteLine x
let stderr (x:string) = System.Console.Error.WriteLine x
let res4 = y (Print stdout)
let res5 = y (Print stderr)
这只是一个快速而简单的示例代码,但这种方法可以改进:您可以使用方法名称而不是运算符,可以避免在声明中重复DU,并且可以组成Invokable。如果您对这些增强功能感兴趣,请告诉我们。我之前在生产代码中使用了这种方法的改进,从未遇到过任何问题。
答案 2 :(得分:1)
请查看Crates。
这是一个简短的片段,描述了您要完成的任务的关键。我相信此代码段很有用,因为它有助于教会我们如何使用数学语言来正式推理使用F#和其他ML类型系统。换句话说,它不仅向您展示了它是如何工作的,而且还教会您为什么有效的深层原理。
这里的问题是,我们已经达到了在F#中直接可表达的内容的基本限制。因此,模拟通用量化的技巧是避免直接传递函数,而不是隐藏类型参数,以使调用者无法将其固定为一个特定值,但是怎么可能呢?
回想一下F#提供了对.NET对象系统的访问。如果我们创建了自己的类(面向对象),并在其上放置了通用方法,该怎么办?我们可以创建可以传递的实例,并因此携带我们的功能(以所述方法的形式)?
// Encoding the function signature... // val id<'a> : 'a -> 'a // ...in terms of an interface with a single generic method type UniversalId = abstract member Eval<'a> : 'a -> 'a
现在我们可以创建一个实现,可以在不固定类型参数的情况下进行传递:
// Here's the boilerplate I warned you about. // We're implementing the "UniversalId" interface // by providing the only reasonable implementation. // Note how 'a isn't visible in the type of id - // now it can't be locked down against our will! let id : UniversalId = { new UniversalId with member __.Eval<'a> (x : 'a) : 'a = x }
现在,我们有了一种模拟通用量化函数的方法。我们可以将
id
作为值传递,并且在任何时候都可以选择类型'a
传递给它,就像处理任何值级参数一样。现有量化
There exists a type x, such that…
存在性是一个值,其类型静态未知,这是因为我们有意隐藏了已知的东西,或者是因为在运行时确实选择了该类型,例如由于反射。但是,在运行时,我们可以检查存在对象,以在其中找到类型和值。
如果我们不知道存在的量化类型中的具体类型,我们如何安全地对其进行操作?好吧,我们可以应用任何本身可以处理任何类型值的函数-即我们可以应用通用量化的函数!
换句话说,存在可以用可用于对它们进行操作的普遍性来描述。
此技术是如此有用,以至于在数据类型通用编程库TypeShape中使用,它可以让您删除样板.NET反射代码,以及MBrace和FsPickler来“打包现有数据类型”。有关“在.NET中编码 safe 存在性解压缩”和在.NET中编码rank-2类型的更多信息,请参见Eirik Tsarpalis的slides on TypeShape。
像TypeShape这样的反射帮助程序库也应该直观地涵盖大部分(如果不是全部)用例:依赖注入需要在后台实现服务位置,因此TypeShape可以被视为构建依赖项的“原始组合器库”注入。请参阅以Arbitrary Type Shapes开头的幻灯片:特别是,请注意Code Lens数据类型:
type Lens<'T,'F> =
{
Get : 'T -> 'F
Set : 'T -> 'F -> 'T
}
最后,有关更多想法,您可能需要阅读Don Stewart的博士学位论文Dynamic Extension of Typed Functional Languages。
我们提出了静态扩展中动态扩展问题的解决方案 具有类型擦除功能的类型化功能语言。提出的解决方案保留 静态检查的好处,包括类型安全,积极的优化和组件的本机代码编译,同时允许 程序在运行时的可扩展性。
我们的方法基于静态扩展框架 类型设置,结合动态链接,运行时类型检查, 一流的模块和代码热插拔。 我们展示了这个框架 足以在任何情况下提供广泛的动态扩展功能 具有类型擦除语义的静态类型功能语言。
我们唯一地采用了完整的编译时类型系统来执行运行时 对动态组件进行类型检查,并强调使用本机 代码扩展以确保静态键入的性能优势 被保留在动态环境中。我们还提出了 全动态软件体系结构,其中静态核心最小且 所有代码均可热插拔。该方法的好处包括可热插拔 通过嵌入式领域特定的代码和复杂的应用程序扩展 语言。
以下是Don列出的一些粗粒度设计模式,供将来的工程师遵循:
以及帮助构建“可扩展性塔”的一般图表:
答案 3 :(得分:0)
你可以用完全成熟的类型来做到这一点:
type Function() =
member x.DoF<'a> (v:'a) = v
let y (f: Function) =
let a = f.DoF 1
let b = f.DoF 2L
(a,b)
y (Function())
我不知道如何让它与F#中的第一类函数一起使用