在 haskell 中使用替代前奏曲
Using alternative preludes in haskell
我对其他 Preludes 很感兴趣。我知道有很多选择:
- https://hackage.haskell.org/packages/#cat:Prelude
- https://guide.aelve.com/haskell/alternative-preludes-zr69k1hc
我知道他们中的很多人修复的一个简单的事情是文本,另一个是像 head
这样的函数,当你可能更喜欢它们更安全时,这个错误很难解决。
但是,当我尝试使用这些替代方案时,head
中的行为,嗯,似乎完全破坏了功能,对我来说看起来不像是改进。以下是一些示例:
序曲
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 []
[[]]
嗯,这不完全是直接替代品。
为什么这更好,为什么定义了这些函数,如果它们即将失败,我还缺少什么?
在大多数情况下,引入它们是因为它们在编译时而不是运行时失败。
Prelude.head
的问题不仅仅在于它会失败。这是它 必须 ,因为无法获取列表 [a]
并始终生成元素 a
,因为输入列表可能为空。没有简单的替代品可以解决,需要彻底改变。
一个更安全、可以说更好的前奏可以通过以下方式之一解决这个问题:
删除head
,这样程序员就不会使用危险的工具了。 head
的任何使用都会在编译时失败。不太好,但还可以。
限制输入类型,例如head :: NonEmptyList a -> a
。这将是可用的,但程序员必须调整代码以保证输入列表确实是非空的。仅仅传递一个非空列表对编译器来说是行不通的——编译器需要一个 proof,这是正确的。 好消息是以前的代码将充满编译错误,这将帮助程序员找出程序中需要修复的部分。
限制输出类型,例如head :: [a] -> Maybe a
。这可以很好地使用,但程序员需要处理不同的结果类型,并处理所有潜在的 Nothing
。同样,编译时错误将帮助程序员确定需要修复的地方。
无论如何,程序员都得修改代码。没有办法解决它。但是,一旦编译时错误得到解决,程序将保证在运行时永远不会产生 head: empty list
错误。
我是 relude
作者之一,我可以提供一点动机,说明为什么 relude
为 head
、tail
、[=18 选择此行为=] 和 init
函数。
标准 Prelude
按以下方式定义 head
:
head :: [a] -> a
另类前奏通常定义head
如下:
head :: [a] -> Maybe a
但是,relude
使用以下类型签名来实现它:
head :: NonEmpty a -> a
这个设计决定使库对初学者不太友好(人们可能不期望这种类型的 head 函数),但另一方面它使接口更加类型安全。
另一个原因:如果你有类型 head :: [a] -> Maybe a
的函数,你不能使用你的 head
的 Maybeised 版本来表达 head :: NonEmpty a -> a
。但是,如果您有 head
与 NonEmpty
一起使用,那么实现 head
和 returns Maybe a
就很容易了。 relude
甚至具有 viaNonEmpty
函数:
viaNonEmpty :: (NonEmpty a -> b) -> ([a] -> Maybe b)
在此处查看带有示例的文档:
我对其他 Preludes 很感兴趣。我知道有很多选择:
- https://hackage.haskell.org/packages/#cat:Prelude
- https://guide.aelve.com/haskell/alternative-preludes-zr69k1hc
我知道他们中的很多人修复的一个简单的事情是文本,另一个是像 head
这样的函数,当你可能更喜欢它们更安全时,这个错误很难解决。
但是,当我尝试使用这些替代方案时,head
中的行为,嗯,似乎完全破坏了功能,对我来说看起来不像是改进。以下是一些示例:
序曲
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 []
[[]]
嗯,这不完全是直接替代品。
为什么这更好,为什么定义了这些函数,如果它们即将失败,我还缺少什么?
在大多数情况下,引入它们是因为它们在编译时而不是运行时失败。
Prelude.head
的问题不仅仅在于它会失败。这是它 必须 ,因为无法获取列表 [a]
并始终生成元素 a
,因为输入列表可能为空。没有简单的替代品可以解决,需要彻底改变。
一个更安全、可以说更好的前奏可以通过以下方式之一解决这个问题:
删除
head
,这样程序员就不会使用危险的工具了。head
的任何使用都会在编译时失败。不太好,但还可以。限制输入类型,例如
head :: NonEmptyList a -> a
。这将是可用的,但程序员必须调整代码以保证输入列表确实是非空的。仅仅传递一个非空列表对编译器来说是行不通的——编译器需要一个 proof,这是正确的。 好消息是以前的代码将充满编译错误,这将帮助程序员找出程序中需要修复的部分。限制输出类型,例如
head :: [a] -> Maybe a
。这可以很好地使用,但程序员需要处理不同的结果类型,并处理所有潜在的Nothing
。同样,编译时错误将帮助程序员确定需要修复的地方。
无论如何,程序员都得修改代码。没有办法解决它。但是,一旦编译时错误得到解决,程序将保证在运行时永远不会产生 head: empty list
错误。
我是 relude
作者之一,我可以提供一点动机,说明为什么 relude
为 head
、tail
、[=18 选择此行为=] 和 init
函数。
标准 Prelude
按以下方式定义 head
:
head :: [a] -> a
另类前奏通常定义head
如下:
head :: [a] -> Maybe a
但是,relude
使用以下类型签名来实现它:
head :: NonEmpty a -> a
这个设计决定使库对初学者不太友好(人们可能不期望这种类型的 head 函数),但另一方面它使接口更加类型安全。
另一个原因:如果你有类型 head :: [a] -> Maybe a
的函数,你不能使用你的 head
的 Maybeised 版本来表达 head :: NonEmpty a -> a
。但是,如果您有 head
与 NonEmpty
一起使用,那么实现 head
和 returns Maybe a
就很容易了。 relude
甚至具有 viaNonEmpty
函数:
viaNonEmpty :: (NonEmpty a -> b) -> ([a] -> Maybe b)
在此处查看带有示例的文档: