当 "factoring" 一个 Haskell 函数变成两个具有各种中介类型的函数时,如何避免重复?

How to avoid repetition when "factoring" a Haskell function into two functions with various intermediary types?

考虑以下 Haskell 代码:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}

import Data.Singletons.TH

singletons [d| data SimpleType = Aaa | Bbb | Ccc | Ddd
                                 deriving (Read)
             |]

-- each SimpleType value has an associated type
type family Parsed (t :: SimpleType) :: * where
  Parsed Aaa = [Int]
  Parsed Bbb = Maybe Int
  Parsed Ccc = (Int, Int)
  Parsed Ddd = Int

forth :: SSimpleType t -> Int -> Parsed t
forth SAaa x = [x,x*2,x*3]
forth SBbb x = Just x
forth SCcc x = (1337, x)
forth SDdd x = x

back :: SSimpleType t -> Parsed t -> Int
back SAaa [_, y, _] = y + 5
back SBbb (Just y) = y - 7
back SCcc (y1, y2) = y1 + y2
back SDdd y = y * 2

helper b = back b . forth b

go :: SimpleType -> Int -> Int
go Aaa = helper SAaa
go Bbb = helper SBbb
go Ccc = helper SCcc
go Ddd = helper SDdd

main = do
  -- SimpleType value comes at run-time
  val <- readLn
  putStrLn $ show $ go val 100

是否可以在定义go时避免重复?换句话说,有没有办法写这样的东西:

go val = helper (someMagicFunction val)

您可以使用 toSing from SingKindSimpelType 的值转换为 SomeSing SimpleType 的值,这是 Sing SimpleType 周围的存在量化包装器。然后您可以解包该值以获得 Sing SimpleType,然后您可以将其传递给 backforth:

go :: SimpleType -> Int -> Int
go val x =
  case toSing val of
    SomeSing s -> back s $ forth s x

SingKind 的实例由您正在使用的 singletons 拼接为您生成(以及许多其他内容)。

请注意,虽然单分支 case 要求成为 let,但无法编译:

go val x =
  let (SomeSing s) = toSing val
  in back s $ forth s x

这是被禁止的,因为 let 可以递归,并且由于展开 GADT 可能会将新类型引入上下文,这可能会导致创建无限类型。另一方面,case 分支不能递归,所以这是可行的。 (此解释归功于@HTNW)

但是辅助函数也可以:

go val x = helper $ toSing val
    where
      helper (SomeSing s) = back s $ forth s x