代码之家  ›  专栏  ›  技术社区  ›  Keith Pinson sumit vedi

基于Haskell中的字符串映射证明print函数的穷举性

  •  1
  • Keith Pinson sumit vedi  · 技术社区  · 6 年前

    当我有一个“enum”类型时,也就是说,一个代数数据类型,其中没有任何一个案例包装任何其他数据,我通常喜欢在映射到字符串的基础上投射一个解析器/打印机,以确保在我更改代码时解析器和打印机保持同步。例如,在Idris中:

    data RPS = Rock | Paper | Scissors
    
    Eq RPS where
      Rock == Rock = True
      Paper == Paper = True
      Scissors == Scissors = True
      _ == _ = False
    
    StrMap : List (RPS, String)
    StrMap =
      [ (Rock, "rock")
      , (Paper, "paper")
      , (Scissors, "scissors")
      ]
    

    我可以实现如下打印功能:

    print' : RPS -> Maybe String
    print' rps = lookup rps StrMap
    

    问题是,我不想要 Maybe 在这里我想在编译时保证,我已经涵盖了我的所有案例,就像我在 RPS ,在那里,我就可以开始做筋疲力尽检查了 print : RPS -> string .在Idris中,我知道如何用证据证明这一点(你难道不喜欢Idris!):

    print_exhaustive : (rps : RPS) -> IsJust (print' rps)
    print_exhaustive Rock = ItIsJust
    print_exhaustive Paper = ItIsJust
    print_exhaustive Scissors = ItIsJust
    
    justGetIt : (m : Maybe a) -> IsJust m -> a
    justGetIt (Just y) ItIsJust = y
    
    print : RPS -> String
    print rps with (isItJust (print' rps))
      | Yes itIs = justGetIt (print' rps) itIs
      | No itsNot = absurd $ itsNot $ print_exhaustive rps
    

    在我看来,这太棒了。我能够在代码中的一个地方声明枚举大小写与其关联字符串之间的相关性,并且我可以有一个 print 函数和a parse 函数都是用它编写的( 作语法分析 函数在这里被省略,因为它与问题无关,但实现起来并不重要)。不仅如此,我还说服了打字员 print : RPS -> String 我想要的签名不是假的,并且避免了使用任何部分函数!这就是我喜欢的工作方式。

    然而,在工作中,我的大部分代码都在F#中,而不是在Idris中,所以我最终要做的是使用FsCheck通过基于属性的测试来准证明穷举性。这还不算太糟,但是

    1. 基于属性的测试没有与 StrMap ; 它在另一个组件中。我喜欢尽可能多地将我的不变量与它们所指的搭配起来。
    2. 如果您添加了一个案例,但忘记了更改案例,那么在遇到失败之前,您可以在构建过程中取得更大进展 StrMap ; 如果你只是坐在那里重新编译,就像我经常做的那样,你会错过它,直到你真正运行测试。
    3. 我必须使用非穷举函数来实现它,因为类型系统较弱。这混淆了我试图教给同事的东西的清晰性;我一直在努力让他们相信全功能编程的荣耀。

    我们刚刚在Haskell启动了一个新项目,我遇到了这样的情况。当然我可以使用快速检查和 fromJust 实施F#战略,这应该没问题。

    但我想知道的是:既然Haskell社区和生态系统以F#的社区和生态系统所没有的方式强调了Curry Howard通信,既然最近添加了各种新奇的扩展来支持依赖类型的使用,那么我就不能改为遵循Idris策略吗?有人告诉我,如果我启用了足够多的扩展(并且愿意引入足够多的冗长内容等),我应该能够将我在Idris中编写的任何内容翻译成Haskell,而不会失去类型安全性。我不知道这是不是真的,但如果是真的,我想知道打开什么扩展,编写什么样的代码,以便在Haskell中遵循我的Idris策略。同样值得注意的是:我可以相信我的Idris策略并不是用这种语言实现它的最简单/优雅的方式。

    如何将此Idris代码翻译为Haskell以实现 print :: RPS -> String 不调用任何部分函数?

    1 回复  |  直到 6 年前
        1
  •  4
  •   Cactus    6 年前

    如果 你愿意相信这个人 Enum Bounded 枚举类型的实例,这样就可以枚举 RPS “宇宙”的使用 [minBound..maxBound] .这意味着你可以 开始 从总函数 print :: RPS -> String ,并将其制成表格进行计算 parse 从中:

    print :: RPS -> String
    
    parse :: String -> Maybe RPS
    parse = \s -> lookup s tab
      where 
        tab = [(print x, x) | x <- [minBound..maxBound]]
    
    推荐文章