传递解析为多种类型的泛型函数的最佳方法是什么

时间:2017-03-04 16:58:36

标签: f# functional-programming

背景:它是功能性DI的变体。在Scott's post之后,我写了一个翻译。扭曲的是我的翻译是通用的,并根据你提供的内容进行参数化。

出于测试目的,我想通过另一个口译员,其中有瑕疵 - 我怎么办?以下是问题的简化概述:

let y f =
    let a = f 1
    let b = f 2L
    (a,b)

f是我的通用解释器,但在这里它显然受到int -> 'a首次使用的限制。 在这个简化的场景中,我可以只传递两次解释器,但在我的实际实现中,类型空间相当大(基类型x3输出类型)。

是否有一些F#机制让我这样做,没有太多的开销?

4 个答案:

答案 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列出的一些粗粒度设计模式,供将来的工程师遵循:

  1. 第3.6.3节:专用模拟器方法。
    • 演示如何将程序专业化技术应用于聚合物化学的蒙特卡洛模拟。这种方法演示了如何“注入”专用代码以解决所谓的“窥孔优化”。

以及帮助构建“可扩展性塔”的一般图表:

Don Stewart's framework for dynamic extension of typed functional languages

答案 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#中的第一类函数一起使用