从采用闭集作为输入的函数导出优化的 case-of 表达式

Blu*_*ula 1 haskell

我有一组封闭的值:

data Value = A | B | C | D | E ...
  deriving (Eq, Ord, Show)
Run Code Online (Sandbox Code Playgroud)

以及代表它们顺序的数据结构:

order :: [[Value]]
order = [
  [ B ],
  [ A, D ],
  [ C ],
  ...
  ]
Run Code Online (Sandbox Code Playgroud)

我需要将 Value 的订单转换为Int. 我可以这样做:

prec' :: [[Value]] -> Value -> Int
prec' [] _ = 0
prec' (vs : rest) v = if v `elem` vs
  then 1 + length rest
  else prec' rest v

prec :: Value -> Int
prec = prec' order
Run Code Online (Sandbox Code Playgroud)

然而,这prec具有复杂性 O(n)。

我想要的是一个非常轻量级且优化的函数,如下所示:

prec :: Value -> Int
prec = \case
  A -> 2
  B -> 3
  C -> 1
  D -> 2
  E -> 0
  ...
Run Code Online (Sandbox Code Playgroud)

但我当然不想手动编写它,否则有与存储的信息不一致的风险order。Haskell 编译器应该能够轻松地自行导出该函数,因为它的输入是一个闭集。

如何让 GHC 生成类似 的最新定义的函数prec

Li-*_*Xia 8

解决方案1:使用Template Haskell 生成您想要的代码。

解决方案 2(在下面进行扩展):(Ab)使用简化器。

简化的主要障碍是 GHC 不会内联递归函数。一种解决方法是通过类型类进行递归。

-- Intuitively unroll :: Nat -> (a -> a) -> (a -> a)
-- but the nat is now a type-level parameter.
class Unroll (n :: Nat) where
  unroll :: (a -> a) -> (a -> a)

instance Unroll 0 where
  unroll = id

instance {-# OVERLAPPABLE #-} Unroll (n-1) => Unroll n where
  unroll f = f . unroll @(n-1) f
Run Code Online (Sandbox Code Playgroud)

这允许您定义以下展开前 n 次迭代的定点运算符:

unrollfix :: forall n a. Unroll n => (a -> a) -> a
unrollfix f = unroll @n f (fix f)
Run Code Online (Sandbox Code Playgroud)

然后,您需要使用 编写所有递归函数fix,并替换fixunrollfix。您还必须洒一些INLINE实用程序。

elemfix

elem :: forall a. Eq a => a -> [a] -> Bool
elem = fix go
  where
    go elem_ x [] = False
    go elem_ x (y : ys) = x == y || elem_ x ys
Run Code Online (Sandbox Code Playgroud)

elemunrollfix

{-# INLINE uelem #-}
uelem :: forall n a. (Unroll n, Eq a) => a -> [a] -> Bool
uelem = unrollfix @n go
  where
    go elem_ x [] = False
    go elem_ x (y : ys) = x == y || elem_ x ys
Run Code Online (Sandbox Code Playgroud)

length(略),和prec'

prec'fix

prec' :: forall a. Eq a => [[a]] -> a -> Int
prec' = fix go
  where
    go prec_ [] v = 0
    go prec_ (vs : rest) v = if elem v vs
      then 1 + length rest
      else prec_ rest v
Run Code Online (Sandbox Code Playgroud)

prec'unrollfix

prec' :: forall n a. (Unroll n, Eq a) => [[a]] -> a -> Int
prec' = unrollfix @n go
  where
    go prec_ [] v = 0
    go prec_ (vs : rest) v = if uelem @n v vs
      then 1 + ulength @n rest
      else prec_ rest v
    {-# INLINE go #-}
Run Code Online (Sandbox Code Playgroud)

最后,将n参数设置为足够高的值以实现简化。

prec :: Value -> Int
prec v = prec' @5 order v
Run Code Online (Sandbox Code Playgroud)

完整代码:

{-# LANGUAGE AllowAmbiguousTypes, DataKinds, MultiParamTypeClasses, ScopedTypeVariables, TypeApplications, UndecidableInstances #-}
{-# OPTIONS_GHC -ddump-simpl #-}
module A (Value(..), prec) where

import GHC.TypeNats
import Data.Function (fix)
import GHC.Exts

class Unroll (n :: Nat) where
  unroll :: (a -> a) -> (a -> a)

instance Unroll 0 where
  unroll = id

instance {-# OVERLAPPABLE #-} Unroll (n-1) => Unroll n where
  unroll f = f . unroll @(n-1) f

unrollfix :: forall n a. Unroll n => (a -> a) -> a
unrollfix f = unroll @n f (fix f)

data Value = A | B | C | D | E
  deriving Eq

order :: [[Value]]
order = [[A], [B, C], [D], [E]] 

{-# INLINE uelem #-}
uelem :: forall n a. (Unroll n, Eq a) => a -> [a] -> Bool
uelem = unrollfix @n go
  where
    go elem_ x [] = False
    go elem_ x (y : ys) = x == y || elem_ x ys
    {-# INLINE go #-}

{-# INLINE ulength #-}
ulength :: forall n a. Unroll n => [a] -> Int
ulength = unrollfix @n go
  where
    go length_ [] = 0
    go length_ (_ : xs) = 1 + length_ xs
    {-# INLINE go #-}

prec' :: forall n a. (Unroll n, Eq a) => [[a]] -> a -> Int
prec' = unrollfix @n go
  where
    {-# INLINE go #-}
    go prec_ [] v = 0
    go prec_ (vs : rest) v = if uelem @n v vs
      then 1 + ulength @n rest
      else prec_ rest v

prec :: Value -> Int
prec v = prec' @5 order v
Run Code Online (Sandbox Code Playgroud)

生成的核心(使用-ddump-simpl选项)(查看展开,而不是主要定义):

\ (v_aQC [Occ=Once1!] :: Value) ->
                 case v_aQC of {
                   __DEFAULT -> GHC.Types.I# 3#;
                   A -> GHC.Types.I# 4#;
                   D -> GHC.Types.I# 2#;
                   E -> GHC.Types.I# 1#
                 }
Run Code Online (Sandbox Code Playgroud)