3

我对替代前奏很感兴趣。我知道有很多选择:

  1. https://hackage.haskell.org/packages/#cat:Prelude
  2. https://guide.aelve.com/haskell/alternative-preludes-zr69k1hc

我知道他们中的很多人修复的一件简单的事情是文本,另一件是在类似head错误的函数中,当你可能更喜欢它们更安全时。

但是,当我尝试使用这些替代方法时head,hmm 中的行为似乎完全破坏了该功能,并且对我来说看起来不像是一种改进。这里有些例子:

序幕

Prelude> head [1]
1
Prelude> head []
*** Exception: Prelude.head: empty list

基础

Foundation> head [1]

<interactive>:6:6: error:
    • Couldn't match expected type ‘NonEmpty c’
                  with actual type ‘[Integer]’
    • In the first argument of ‘head’, namely ‘[1]’
      In the expression: head [1]
      In an equation for ‘it’: it = head [1]
    • Relevant bindings include
        it :: foundation-0.0.21:Foundation.Collection.Element.Element c
          (bound at <interactive>:6:1)
Foundation> head []

<interactive>:7:6: error:
    • Couldn't match expected type ‘NonEmpty c’ with actual type ‘[a0]’
    • In the first argument of ‘head’, namely ‘[]’
      In the expression: head []
      In an equation for ‘it’: it = head []
    • Relevant bindings include
        it :: foundation-0.0.21:Foundation.Collection.Element.Element c
          (bound at <interactive>:7:1)

安全的

Safe> head []

<interactive>:22:1: error: Variable not in scope: head :: [a0] -> t

优雅的前奏曲

ClassyPrelude> head [1]

<interactive>:24:6: error:
    • Couldn't match expected type ‘NonNull mono’
                  with actual type ‘[Integer]’
    • In the first argument of ‘head’, namely ‘[1]’
      In the expression: head [1]
      In an equation for ‘it’: it = head [1]
    • Relevant bindings include
        it :: Element mono (bound at <interactive>:24:1)

回避

Relude> head [1]

<interactive>:27:6: error:
    • Couldn't match expected type ‘NonEmpty a’
                  with actual type ‘[Integer]’
    • In the first argument of ‘head’, namely ‘[1]’
      In the expression: head [1]
      In an equation for ‘it’: it = head [1]
    • Relevant bindings include it :: a (bound at <interactive>:27:1)

里约

RIO> head [1]

<interactive>:7:1: error:
    Variable not in scope: head :: [Integer] -> t

序曲

Protolude> head [1]
Just 1
Protolude> head []
Nothing

这看起来不错——它也适用于尾巴,对吧?

Protolude> tail [1]

<interactive>:12:1: error:
    • Variable not in scope: tail :: [Integer] -> t
    • Perhaps you meant ‘tails’ (imported from Protolude)

Protolude> tails [1]
[[1],[]]

Protolude> tails []
[[]]

好吧,这不完全是一个替代品。

我错过了什么,为什么这会更好,为什么这些函数会在它们失败的情况下被定义?

4

2 回答 2

12

在大多数情况下,引入它们是因为它们在编译时而不是运行时失败。

问题Prelude.head不在于(仅)它可能会失败。它必须这样,因为没有办法获取一个列表[a]并总是产生一个元素a,因为输入列表可能是空的。没有简单的解决方法可以直接替代,需要进行彻底的改变。

更安全且可以说更好的前奏可以通过以下方式之一解决此问题:

  • remove head,这样程序员就不会使用危险的工具。head在编译时,任何使用都会失败。不是很好,但还可以。

  • 限制输入类型,例如head :: NonEmptyList a -> a. 这将是可用的,但程序员必须调整代码以保证输入列表确实是非空的。仅仅传递一个非空列表对编译器来说是行不通的——编译器想要一个证明,这是正确的。消息是之前的代码会出现编译错误,这将有助于程序员发现程序中需要修复的部分。

  • 限制输出类型,例如head :: [a] -> Maybe a. 这可以很好地使用,但程序员将需要处理不同的结果类型,并处理所有潜在Nothing的 s。同样,编译时错误将帮助程序员确定需要修复的地方。

在任何情况下,程序员都必须修改代码。没有办法解决它。但是,一旦解决了编译时错误,就可以保证程序head: empty list在运行时永远不会产生错误。

于 2019-03-14T20:57:31.797 回答
2

我是relude作者之一,我可以提供一些关于为什么为、和函数relude选择这种行为的动机。headtaillastinit

标准以如下方式Prelude定义:head

head :: [a] -> a

替代前奏通常定义head如下:

head :: [a] -> Maybe a

但是,relude改为使用以下类型签名来实现它:

head :: NonEmpty a -> a

这种设计决定使库对初学者不太友好(人们可能不会期望这种类型的 head 函数),但另一方面它使接口更加类型安全。

另一个原因:如果你有 type 的功能head :: [a] -> Maybe a,你不能head :: NonEmpty a -> a用你的 Maybeised 版本来表达head. 但是,如果你有head它的作品,NonEmpty它很容易实现headreturn Maybe arelude甚至有这样的功能viaNonEmpty

viaNonEmpty :: (NonEmpty a -> b) -> ([a] -> Maybe b)

在此处查看带有示例的文档:

于 2019-09-21T19:38:31.910 回答