是否有可能迭代非内同态的应用?

时间:2018-06-11 20:14:09

标签: haskell template-haskell

在Haskell中,如果我想重复将endomorphism a -> a应用于a类型的值,我可以使用iterate

一个不是endomorphisms的函数怎么样,但通用到足以在返回类型上正常工作呢?

考虑例如Just :: a -> Maybe a;我可以写

Just . Just . Just ...

我想要多次。有没有办法用

之类的东西写这篇文章
iterate' 3 Just :: a -> Maybe (Maybe (Maybe a))

或者我们需要类似依赖类型的东西来做这件事吗?

3 个答案:

答案 0 :(得分:13)

可以对您提出的语法进行微调:iterate' @3 Just而不是iterate' 3 Just

这是因为结果类型取决于数字,因此数字必须是类型文字,而不是值文字。正如您所正确注意的那样,使用任意数字执行此操作将需要依赖类型[1],Haskell没有这种类型。

{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE TypeFamilies, KindSignatures, DataKinds,
    FlexibleInstances, UndecidableInstances, ScopedTypeVariables,
    FunctionalDependencies, TypeApplications, RankNTypes, FlexibleContexts,
    AllowAmbiguousTypes #-}

import qualified GHC.TypeLits as Lit

-- from type-natural
import Data.Type.Natural
import Data.Type.Natural.Builtin

class Iterate (n :: Nat) (f :: * -> *) (a :: *) (r :: *)
  | n f a -> r
  where
  iterate_peano :: Sing n -> (forall b . b -> f b) -> a -> r

instance Iterate 'Z f a a where
  iterate_peano SZ _ = id
instance Iterate n f (f a) r => Iterate ('S n) f a r where
  iterate_peano (SS n) f x = iterate_peano n f (f x)

iterate'
  :: forall (n :: Lit.Nat) f a r .
     (Iterate (ToPeano n) f a r, SingI n)
  => (forall b . b -> f b) -> a -> r
iterate' f a = iterate_peano (sToPeano (sing :: Sing n)) f a

如果你在ghci中加载它,你可以说

*Main> :t iterate' @3 Just
iterate' @3 Just :: a -> Maybe (Maybe (Maybe a))
*Main> iterate' @3 Just True
Just (Just (Just True))

此代码使用两种不同的类型级自然:来自Nat的内置GHC.TypeLits和来自Data.Type.Natural的经典Peano数字。前者需要提供良好的iterate' @3语法,后者需要执行递归(发生在Iterate类中)。我使用Data.Type.Natural.Builtin将文字转换为相应的Peano数字。

[1]但是,给定一种消耗迭代值的特定方法(例如,如果您事先知道您只想show它们),您可能会调整此代码以使其工作动态值为niterate'类型中没有任何内容需要静态知道的Nat;唯一的挑战是证明迭代的结果满足您所需的约束。

答案 1 :(得分:9)

你可以使用模板haskell,如果你知道编译时的数字(但除非数字非常大,否则我认为它不值得麻烦)。如果您在编译时尚未知道该数字,则需要正确建模返回类型,我们可以使用非常规类型进行建模:

data Iter f a = Iter0 a | IterS (Iter f (f a))

iterate' :: Int -> (forall x. x -> f x) -> a -> Iter f a
iterate' 0 f x = Iter0 x
iterate' n f x = IterS (iterate' (n-1) f (f x))

Iter本质上是一种表达数据类型a | f a | f (f a) | f (f (f a)) | ...的方式。要使用结果,您需要在Iter上递归。对于某些类型构造函数a -> f a,函数必须是f形式,因此您可能需要执行一些newtype包装才能实现。所以......无论如何都是一种痛苦。

答案 2 :(得分:1)

你可以在没有模板 Haskell 或类型级 Nats 的情况下做到这一点。您正在构建的这种可变深度递归类型实际上非常适合自由 monad 的模型。我们可以使用 unfold 包中的 free 函数来构建 Free 结构并在我们的计数器达到 0 时短路。

-- This extension is enabled so we can have nice type annotations
{-# Language ScopedTypeVariables #-}

import Control.Monad.Free (Free)
import qualified Control.Monad.Free as Free

iterate' :: forall f a. Functor f => Int -> (a -> f a) -> a -> Free f a
iterate' counter0 f x0 = Free.unfold run (counter0, x0)
  where

    -- If counter is 0, short circuit with current result
    -- Otherwise, continue computation with modified counter
    run :: (Int, a) -> Either a (f (Int, a))
    run (0      , x) = Left x
    run (counter, x) = Right (countDown counter <$> f x)

    countDown :: Int -> a -> (Int, a)
    countDown counter x = (counter - 1, x)

现在,很容易为任何 Functor 创建和消化这些类型的值。

> iterate' 3 Just True
Free (Just (Free (Just (Free (Just (Pure True))))))

> let f i = if i == 1 then Left "abort" else Right (i+1)

> iterate' 0 f 0
Pure 0
> iterate' 1 f 0
Free (Right (Pure 1))
> iterate' 2 f 0
Free (Right (Free (Left "abort")))

如果您的 Functor 也恰好是一个 Monad,您可以使用 retract 来折叠递归结构。

> Free.retract (iterate' 3 Just True)
Just True
> Free.retract (iterate' 0 f 0)
Right 0
> Free.retract (iterate' 1 f 0)
Right 1
> Free.retract (iterate' 2 f 0)
Left "abort"

我建议您阅读 Control.Monad.Free 的文档,以便您了解如何创建/使用这些结构。


(顺便说一句,a -> Maybe a 一个内同态,但它是Maybe的Kleisli范畴中的一个内同态。)