Haskell 可以迭代应用非自同态吗?

Haskell 可以迭代应用非自同态吗?,haskell,template-haskell,Haskell,Template Haskell,在Haskell中,如果我想将自同态a->a重复应用于a类型的值,我可以使用iterate 如果一个函数不是自同态,但它的泛型足以正确处理它的返回类型,那该怎么办 例如,考虑Just::a->可能是a;我会写字 Just . Just . Just ... 我想要多少次都行。有没有一种方法可以在短时间内用类似 iterate' 3 Just :: a -> Maybe (Maybe (Maybe a)) 或者我们需要依赖类型之类的东西来实现这一点吗?如果您在编译时知道这个数字,您可以使

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

如果一个函数不是自同态,但它的泛型足以正确处理它的返回类型,那该怎么办

例如,考虑
Just::a->可能是a
;我会写字

Just . Just . Just ...
我想要多少次都行。有没有一种方法可以在短时间内用类似

iterate' 3 Just :: a -> Maybe (Maybe (Maybe a))

或者我们需要依赖类型之类的东西来实现这一点吗?

如果您在编译时知道这个数字,您可以使用template 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 | fa | f(fa)| f(fa))|……
。要使用结果,您需要在
Iter
上递归。对于某些类型的构造函数
f
,函数的形式必须是
a->fa
,因此您可能需要进行一些新的类型包装才能到达那里。所以这两种方式都有点痛苦。

对您提出的语法稍加修改即可:
迭代'@3 Just
,而不是
迭代''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))
此代码使用两种不同的类型级别自然值:来自
GHC.TypeLits
的内置
Nat
和来自
Data.type.Natural
的经典Peano数字。前者需要提供nice
iterate'@3
语法,后者需要执行递归(发生在
iterate
类中)。我使用
Data.Type.Natural.Builtin
将文字转换为相应的花生数字


[1] 但是,如果给定一种使用迭代值的特定方式(例如,如果您事先知道您只想
显示这些值
),则您可能可以调整此代码以使其适用于
n
的动态值。
iterate'
类型中没有任何东西需要静态已知的
Nat
;唯一的挑战是证明迭代的结果满足您需要的约束。

您可以在没有模板Haskell或类型级别NAT的情况下实现这一点。您正在构建的这种可变深度递归类型实际上非常适合自由单子的模型。当计数器达到
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)

如果您的函子恰好也是单子,则可以使用折叠递归结构

> 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
是一种自同态,但它是Kleisli分类中的一种自同态。)

你可以用模板Haskell来实现这一点。@WillemVanOnsem你是否有类似于我所问的东西的链接你可能正在寻找免费的单子,虽然这并不完全是你所描述的。不幸的是,模板Haskell相当复杂。可以说是Haskell编写了Haskell语法。因此,您可以“生成”一个特定的
iterate'::Int->Name->Exp
函数,然后
iterate'
将“生成”,Haskell代码用于此。
> 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"