【问题标题】:Using functions before they are declared在声明函数之前使用函数
【发布时间】:2012-11-30 13:31:38
【问题描述】:

为了在 F# 中使用文学编程(即 cweb),我需要能够转发声明函数(即在定义它们之前使用它们)。我想出了两种方法,它们都令人不快。你能想出更好的东西吗(对程序员来说更容易使用)?

不错,但不适用于多态函数

// This can be ugly
let declare<'a>  = ref Unchecked.defaultof<'a>

// This has to be beautiful
let add = declare<float -> float>

let ``function I want to explain that depends on add`` nums = nums |> Seq.map !add

add := fun x -> x + 1.

丑陋,但适用于一切

// This can be ugly
type Literate() =
static member Declare<'a, 'b>  (ref : obj ref) (x : 'a) : 'b =
                                    unbox <| (unbox<obj -> obj> !ref)
static member Define<'a, 'b> (func : 'a -> 'b) (ref : obj ref) (f : 'a -> 'b) =
                                    ref := box (unbox<'a> >> f >> box)

// This has to be beautiful
let rec id (x : 'a) : 'a = Literate.Declare idImpl x
and idImpl = ref null

let f () = id 100 + id 200

Literate.Define id idImpl (fun x -> x)

【问题讨论】:

  • 那是反惯用语。你确定你不能记录你的编程方式吗?
  • 这个要求的原因值得单独写一篇文章(即将发布),现在让我们尝试解决这个难题,如果有更好的方法来进行前向函数声明。

标签: f#


【解决方案1】:

在创建www.tryjoinads.org 时,我使用了一个遵循与文学编程相同理念的工具。文档只是一个带有代码 sn-ps 的 Markdown,它被转换为您可以运行的 F# 源代码,并且 sn-ps 必须以正确的顺序排列。 (在一些有文化的编程工具中,文档是写在注释中的,但是思路是一样的。)

现在,我认为让您的代码更复杂以便您可以用有文化的编程风格编写它(并记录它)会引入很多意外的复杂性,并且违背了主要目的文学编程。

所以,如果我想解决这个问题,我会用一些注释来扩展我的文学编程工具,这些注释指定使脚本工作所需的代码块的顺序(并且一个简单的预处理工具可以重新排序它们在生成 F# 输入时)。您可以查看 TryJoinads 的 [查看我的构建脚本][1],这很容易扩展。

我用于 TryJoinads 的工具已经提供了一些元标记,可用于隐藏输出中的代码块,因此您可以编写如下内容:

## This is my page heading

    [hide]
    // This function will be hidden from the generated HTML
    // but it is visible to the F# compiler
    let add a b = a + b 

Here is the description for the other function:

   let functionThatUsesAdd x = add x x

And later on I can repeat `add` with more comments (and I can add an annotation
`module` to avoid conflicts with the previous declaration):

   [module=Demo]
   let add a b =
     // Add a and b
     a + b

这也不是完美的,因为您必须复制功能,但至少您生成的博客文章或 HTML 文档不会被无关紧要的东西所掩盖。当然,添加一些元命令(如modulehide)来指定块的顺序并不会太难,这将是一个干净的解决方案。

总之,我认为你只需要一个更好的识字编程工具,而不是不同的 F# 代码或 F# 语言。

【讨论】:

  • 如果你预处理代码,你会失去智能感知、调试和交互式控制台,除非你愿意重写整个工具集。如果你按照编译器想要的顺序编写代码,您可能会失去文学编程的主要显着特征。所以你在岩石和坚硬的地方之间,因此我渴望一条出路。
  • @user965221 取决于您如何操作。我的 TryJoinads 原始工具专注于编写 Markdown,但您也可以反过来做 - 将所有 Markdown 放在 F# cmets 中,按所需顺序保持 F# 文件重新排序生成的文档. IntelliSense 可以正常工作。对于博客文章,我更喜欢重新排序代码 sn-ps,但对于文档,重新排序文档听起来是一种更好的方法。
  • 你明白了。总的来说,这就是我正在做的事情。但是重新排序生成的文档需要您仍然按照编译器想要的顺序编写代码,而不是按照您考虑的顺序编写代码,这是次优的。因此,我希望有一种方法可以直接按“思考”顺序编写代码,但仍然让编译器满意。我想当我发布它时,我的恶作剧计划会变得清晰。 - 卢卡
【解决方案2】:

也许我遗漏了一些东西,但你为什么不一路“正确地做”?

先使用函数:

<<test.fs>>=
<<add>>
let inc = add 1

事后声明函数:

<<add>>=
let add a b = a + b

【讨论】:

  • 以 knuth 的方式做这件事会失去 IDE、调试器和交互式控制台(除非你很幸运让他们能够识字)。我还没准备好付出那个代价:-)
  • @user965221 是的,我确实朝着制作 REPL 类型的文学编程“IDE”迈出了一步,但从未比 Alpha 走得更远。 This blog post(IIRC 的灵感来自我的一个问题)可能会让你感兴趣。
【解决方案3】:

由于函数是 F# 中的一等对象,因此您可以将它们传递给其他对象 - 这提供了比前向引用更好(并且仍然不可变)的解决方案。

let dependentFunction f nums = nums |> Seq.map f

let ``function I want to explain that depends on add`` nums =
    dependentFunction (fun x -> x + 1.) nums

此外,在大多数情况下,您应该能够使用 currying(部分函数应用程序)来进一步简化代码,但 seq&lt;'T&gt; 的类型推断在 F# 中有点奇怪,因为它通常用作灵活类型(类似于C# 4.0 中的协方差)。举例说明:

// This doesn't work because of type inference on seq<'T>,
// but it should work with most other types.
let ``function I want to explain that depends on add`` =
    dependentFunction (fun x -> x + 1.)

最后,在 F# 中使用 refmutable 的一个好的经验法则是,如果您只分配一次值(初始化它),那么可能有一种更简洁、更实用的编写方式该代码(将值作为函数参数传递(如上)和lazy 是两种这样的方法)。显然,这条规则也有例外,但即便如此,也应该非常谨慎地使用它们。

【讨论】:

  • 查看对 Ramon Snir 的回答。我知道我可以做到这一点,但它太侵入性了,因为它改变了function I want to explain that depends on add 的签名。
  • 不,不是——function I want to explain that depends on add 的签名在您的第一个示例和我的第一个示例中都是 seq&lt;float&gt; -&gt; seq&lt;float&gt;
  • 这只是因为您将function I want to explain that depends on add 重命名为dependentFunction。否则,您的解决方案与 Ramon 相同,需要将函数作为参数传递。
【解决方案4】:

正如我所说,这是错误的(你应该发表一篇博客文章或 FPish 帖子,说明你为什么这样做),但这是我的看法:

let ``function I want to explain that depends on add``
      (add : float -> float) = nums |> Seq.map add

let add = (+) 1.

let ``function I want to explain that depends on add`` = ``function I want to explain that depends on add`` add

【讨论】:

  • 对不起,我应该在原始问题中指定它。我知道我可以通过将函数作为参数传递来解决它,但这在我的场景中太麻烦了。我真的很想在问题文本中转发声明一个函数(又名function I want to explain that depends on add 不应该带参数)。
  • 使用 C 语言?这在 ML 语言家族中是不可能的。 不可能,而且不应该
  • 我知道语言不允许。我正在寻找一种类似于我建议的技巧来伪造它。顺便说一句,我不确定前向函数声明在概念上有什么问题,无法在您这边引起如此强烈的反应。干杯。
  • @user965221:我同意 Ramon 的观点,即在使用函数之前定义函数(以及更多模块/类型)可以更轻松地管理依赖关系。我认为依赖管理是大多数大型项目的核心挑战。
  • @user965221:我指的是空间中的“之前”,而不是时间。由于 F# 不支持前向声明,因此解决方案资源管理器中的项目文件代表抽象层,从顶部的最低层到底部的最高层。这是一个比 C# 项目更简单的概念模型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-21
  • 1970-01-01
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多