2元组的特殊地位是什么?

fla*_*awr 5 haskell tuples

阅读真实世界HaskellTypeclassopedia我得到的印象是2元组(a,b)在Haskell中可以扮演非常特殊的角色.

我遇到的第一个用途是lookup使用2元组列表作为字典.

然后我也遇到了这样((,) e)一个事实:它是一个仿函数的实例(但没有其他的n元组),这在上面的例子中很有意义(key,value).

现在最新的案例 - 我实际想要问的一个案例 - 在Typeclassopedia的第4.3章中.在那里它说这((,) a)是一个Applicative if a是一个幺半群的实例.你什么时候真正利用它?您使用Applicative实例的应用程序是(a,b)什么?

Zet*_*eta 5

没有什么能阻止我们编写三元组或任意n元组的实例:

instance Functor ((,,) a b) where
  fmap f (x,y,z) = (x,y,f z)

instance (Monoid a, Monoid b) => Applicative ((,,) a b) where
  pure z              = (mempty,        mempty,          z)
  (a,b,f) <*> (x,y,z) = (a `mappend` x, b `mappend` y, f z)
Run Code Online (Sandbox Code Playgroud)

因此,对于任何n元组都可以实现实例,因此对不是特殊的.但由于这些实例必须写在某处,所以我们应该走多远.例如,Monoid实例最多定义为5元组.将它们写成10元组当然是可能的,但我们只是在那时复制样板代码.

话虽如此,对是特殊的,因为它们的集合提供了描述关系的自然方式.一个例子是字典,它涉及一个术语及其定义:

dictionary :: [(String, String)]
dictionary =
  [("cat", "animal that likes strings; not Strings, though")
  ,("dog", "animal that likes you; yes you")
  ,("foo", "a strange word used by programmers in examples")
  ]
Run Code Online (Sandbox Code Playgroud)

显示该关系的另一种方式(如果所有对的第一部分都是唯一的)将是

partialDictionaryEntry :: String -> String
Run Code Online (Sandbox Code Playgroud)

如果我们将输入域限制为"cat","dog""foo",或

dictionaryEntry :: String -> Maybe String
Run Code Online (Sandbox Code Playgroud)

这正是\s -> lookup s dictionary将要发生的事情.使用对,你可以建模任何其他n元组:

(a,b,z)   = ((a,b),z)
(a,b,c,z) = ((a,b,c),z) = (((a,b),c),z)
Run Code Online (Sandbox Code Playgroud)

从这个意义上讲,它们是提供此功能的最小容器.我们可以从对中构建所有其他元组类型.哎呀,理论上我们甚至不需要编写我们的Applicative ((,,) a b)实例,因为它已经(,) (a,b)Monoid实例而提供了.

话虽这么说,为什么它甚至有一个Applicative实例?这是最简单的Writer实现:

log :: (Show a) => a -> (String, a)
log x = (show x ++ "\n", x)

fivePlusThree = (+) <$> log 3 <*> log 5

main = do
  let (logs, result) = fivePlusThree
  putStrLn logs
  print result
Run Code Online (Sandbox Code Playgroud)
3
5

8
Run Code Online (Sandbox Code Playgroud)

这提供了一种向函数或值添加附加信息的简单方法,尽管您可能会使用Writer它及其writer方法,因为它们使用起来更加舒适并且提供了严格的变体.