【问题标题】:Trying to understand the Choice type in F#试图理解 F# 中的选择类型
【发布时间】:2015-03-22 19:38:26
【问题描述】:

我一直在努力理解 Scott Wlaschin 的 RoP 文章中的代码:

http://fsharpforfunandprofit.com/posts/railway-oriented-programming-carbonated/

他在 F# 中使用了 Choice1Of2 和 Choice2Of2 类型。当我遇到以下情况时,我试图通过调试它们来了解如何利用这些东西:

module TestModule
open Microsoft.VisualStudio.TestTools.UnitTesting

// generic union type (like Choice1Of2, I think)
type Things<'a> =
    | Thing of 'a

// explicit union type (for comparison)
type Numbers =   
    | Integer of int

[<TestClass>]
type Tests() =

    // method to make a Choice1Of2 (from the article)
    let makeChoice (a : string) : Choice<string, 'a> = 
        Choice1Of2 a

    [<TestMethod>]
    member public this.WhyYouNoHaveItemValueAndStuff() =      
        let choice1 = Thing "test"          // debug = Thing "this"
        let choice2 = Integer 3             // debug = Integer 3
        let choice3 = makeChoice "test"     // debug = Choice1Of2 w/Item = "test"
        let choice4 = Choice1Of2 "test"     // debug = Tests.choice4@24 ???

        // bogus test stuff below here
        let choices = (choice1, choice2, choice3, choice4)
        Assert.IsNotNull(choices)

为什么我直接做choice1Of2(choice4)时,得到的调试结果和choice 3不一样。为什么要用方法做choice3才能得到和choice1 & 2一样的结果?

编辑:

似乎将choice4更改为:

let choice4 : Choice<string, Object> = Choice1Of2 "test" 

解决了。我完全不清楚为什么我需要那个。作业的右侧清楚地表明正在设置什么类型。

【问题讨论】:

  • 不确定类型推断到底在做什么,但可能会被这些值的未定义类型混淆。两者的运行时类型都是Choice&lt;string, obj&gt;,它们的用法似乎相同。在choice3choice4 上使用: Choice&lt;string, int&gt; 之类的类型注释,它们在调试信息中看起来也相同。
  • @Vandroiy 感谢回复。这确实迫使正确的行为。有必要还是很奇怪。
  • 在我看来,这并不是关于正确的行为,而是关于您在调试器中观察到的内容。我的猜测是,您所看到的是choice4 具有不完全推断的类型,并且不完全推断的类型的内部表示与具有泛型参数的类型的内部表示不同。除非您能找到在代码中(与在调试器中不同)对这些绑定进行不同评估或类型检查的情况,否则我认为这只不过是 VS 实现细节。
  • @NateC-K 够公平的。我想你可能是对的,事情就是这样。我还在学习 f#,我只是假设有一些我不知道的东西。谢谢
  • 迟到的反应,我遇到了 F# Choice,然后我意识到它与来自 Scala/Haskell 的类型相同:这是 scala 的文档:scala-lang.org/api/current/scala/util/Either.html。如果您搜索either 函数式编程,您应该会找到很多信息。它无处不在。我有点喜欢更好的Either,因为它迫使你认为 Right 始终是成功的路径,而 Left 是错误的分支。但是,Choice 更抽象。

标签: generics f# discriminated-union


【解决方案1】:

Choice类型的定义如下

 type Choice<'a, 'b> =
      | Choice1Of2 of 'a
      | Choice2Of2 of 'b

因此,当您像在 choice4 中那样构造 Choice 类型的实例时,您只使用其中一个分支,这实际上会留下一个漏洞(描述 'b 的类型),事实上,调试器必须填充在运行时,它甚至无法确定该类型实际上是Choice&lt;'a,'b&gt;,因此您将获得一些由 FSharpFunc 表示的临时类型。与类型推断机制几乎相同的方式将报告Choice&lt;string, 'a&gt;,其中'a 表示孔,直到实例匹配,然后强制您键入另一侧。提供内联类型签名,例如

 let choice4 : Choice<string, bool> = Choice1Of2 "test"

意味着您正在填补漏洞并为调试器提供足够的信息以正确表示类型。

EDIT(参见 cmets):choice3 表示为 Choice1Of2&lt;string,obj&gt;,因为 obj 被认为是 Top(最通用的类​​型)。这是在用尽所有其他选项时使用的类型推断机制回退类型。如果我们添加一些代码,例如

let result =
    match choice3 with
    | Choice1Of2 t -> t.GetHashCode()
    | Choice2Of2 t -> t  

那么我们将得到Choice&lt;string, int&gt;,因为GetHashCode() 的类型是int,因此第二个匹配子句的结果必须是int,对于result let 表达式的类型始终如一。

【讨论】:

  • 我不认为这就是全部解释。问题是'a 也代表一个“洞”。我怀疑答案是编译器仍在等待,看看它是否会遇到一些其他信息,允许它更具体地指定类型参数,并且它对“尚未填充的孔”的表示与它不同用于“不会被填充的洞”。
  • 但是您已经通过实例化 Choice1Of2 "test" 将 'a 限制为字符串。所以这不再是一个洞。
  • 我说的是另一个'a,即Choice&lt;string, 'a&gt; 中的'a。但是现在我已经在 VS 中获得了这段代码,我看到 choice4 的输入方式与我期望的一样 (Choice&lt;string, 'a&gt;),而我发现它是 choice3 有点令人惊讶。 (我也是 F# 的新手,但我使用过 OCaml。)我不确定它在什么时候决定 choice3 的第二个参数应该是 obj
  • 如果没有其他信息,类型推断将始终推断回 obj。这是顶级类型。如果您添加了match choice3 with | Choice1Of2 t -&gt; t.GetHashCode() | Choice2Of2 t -&gt; t,那么这会将类型推断为 Choice 因为类型推断机制现在有足够的信息。
  • 但这仍然不能解释究竟是什么关于 choice3 使它选择使用默认类型 (obj),而不是像使用它一样不指定第二个类型参数choice4。我猜这与 F# 类型方法与值的差异有关。
猜你喜欢
  • 2018-08-20
  • 2021-04-27
  • 2019-08-18
  • 1970-01-01
  • 2021-08-17
  • 1970-01-01
  • 1970-01-01
  • 2019-11-22
  • 1970-01-01
相关资源
最近更新 更多