【问题标题】:How can I parameterise my Haskell functions?如何参数化我的 Haskell 函数?
【发布时间】:2013-12-28 23:22:50
【问题描述】:

我希望我的术语在这里是正确的 - 如果不是,请随时编辑任何内容。

我在撰写关于组合博弈论的论文时使用 Haskell 作为辅助语言 - 即,我的所有函数都只是处理数字并帮助我为我正在研究的游戏找到解决方案。

我为一个具体的、完整的“棋盘大小”(想想棋盘、5x5 等)编写了所有函数。我想扩展到任何大小的学习板,因此通过包含一个整数参数来修改所有相关函数。 例如

type Size = Int

squares :: Size -> [Square]
squares size = [ (x,y) | x <- [1..size],  
                         y <- [1..size]]

然而,这已经开始变得混乱了。通常我认为与大小无关的函数在访问需要大小的函数时必须提供大小。

这很快就会导致这样的行:

reaching size = (losing size) \\\ (setA size)

takeUDmir _  []       = []
takeUDmir size (x:xs) = [x] ++ takeUDmir size (xs \\\ mirUD size x)

rotate size s = concatMap (mirUD size) (mirLR size s)

(请注意,函数的内容实际上并不重要,我只是想展示它变得多么失控。)

我对使用 Haskell 以及一般的函数式编程非常有信心,但我不知道如何才能删除所有这些 size 引用,而仅仅依靠其他设置大小的东西每个需要使用的功能。

我想我可能正在寻找一个单子,但我不知道。

【问题讨论】:

  • 也许您只需要在绑定大小的 where 子句中定义所有函数?
  • @augustss:我想到了这一点,但我认为会有一种更优雅的方式来解决它。
  • 感谢所有回复和 cmets - 解决此问题的方法比我预期的要多。希望这个答案可以帮助其他人。

标签: design-patterns haskell monads state-monad


【解决方案1】:

现在是提取Reader monad 的最佳时机——它抽象了一些全局可用的只读配置数据的概念。

data Config = Config { size :: Int, ... }

type MyMonad = Reader Config

fun :: MyMonad Result
fun = funNeedingTheSize <$> asks size <*> pure arg1 <*> pure arg2

runMyMonad :: Config -> MyMonad a -> a
runMyMonad = flip runReader

【讨论】:

  • 10/10 for Reader monad 使用 Applicative 语法。嗯。
  • 谢谢!看来这确实是我正在寻找的。我了解 monad 的概念,但我希望我需要一段时间才能真正弄清楚如何以适当的风格重写我的函数。
  • 从实际使用size的所有函数开始。这些必须重写以使用ask(或者asks,如果你使用我上面使用的Config数据结构样式),因此将被包裹在你的Reader monad结构中。如果在这些函数“之上”有很多层不依赖于大小的计算,那么它可以用fmap 分层,如果有些地方你必须结合多个“不纯”(依赖于大小)的计算,你可以使用 monadic或应用样式组合器。
【解决方案2】:

我将在这里充实 Augustss 的建议。如果您只需要定义一次size,那么您可以在本地绑定中构建所有依赖机制。这有效地允许您在 size 的特定定义的上下文中创建许多函数,然后稍后使用所有这些函数,只需选择一次 size。它特别适合RecordWildCards

{-# LANGUAGE RecordWildCards -#}

data Methods =
  Methods { meth1 :: Int -> Int
          , meth2 :: Int -> Int
          ...
          }

mkMethods :: Int -- ^ size
          -> Methods
mkMethods size = 
  Methods { meth1 = \i -> size + i
          , meth2 = \i -> size - i
          ...
          }

...

someFn :: Int -> Result
someFn size = ... meth1 ... meth2 ...
  where Methods {..} = mkMethods size

这实际上是模拟 ML 模块的有限方法。

【讨论】:

    【解决方案3】:

    Reader monad 的替代方案是 Implicit Parameters

    如果您尝试在递归调用中更改参数的值,这些语义通常会很微妙。

    但是,它们确实具有避免以单子样式重写代码的需要,这是一种相当侵入性的更改,会降低可读性。

    我的观点是,它们在像您这样的情况下是完美的,在这种情况下,它们只需在调用链的顶层设置一次作为“配置”信息,并且不会在该链内更改。

    如果你有任何类型签名,它们仍然会侵入你的类型签名,但如果你不提供签名,它们会像类型类一样被自动推断出来。

    如果您在任何地方将它们用于size,您上面的代码 sn-ps 将看起来像这样带有隐式参数:

    type Size = Int
    
    squares :: (?size :: Size) => [Square]
    squares = [ (x,y) | x <- [1..?size],  
                        y <- [1..?size]]
    
    reaching = losing \\\ setA
    
    takeUDmir []     = []
    takeUDmir (x:xs) = [x] ++ takeUDmir (xs \\\ mirUD x)
    
    rotate s = concatMap mirUD (mirLR s)
    

    可能在某些函数中,显式传递值更合适 - 这取决于size 参数对于该特定函数的重要性。

    要实际“实例化”参数,请使用let

    let ?size = 5 in squares
    

    【讨论】:

      【解决方案4】:

      解决此问题的另一种方法是使用reflection 库。这绝对是一种复杂的方法,应该非常小心地使用。 Austin Seipp available at FP Complete 有一个关于它的教程资源,用于实现它,但这可能很麻烦。

      reflection 的简化形式是Given 类型类。为此,我们只需在我们需要一些全局配置值的地方用 given 注释我们的函数。

      meth1 = 1 + given
      meth2 = given - 1
      

      ImplicitParams 一样,我们会注意到我们在类型签名中使用了Given

      meth1 :: (Num a, Given a) => a
      meth2 :: (Num a, Given a) => a
      

      这使我们可以静态消除它们并确保所有正确的配置信息都已使用give传递

      give :: a -> (Given a => r) -> r
      

      如果我们有someComplexCalculation :: Given Config =&gt; Result,我们可以通过提供Config 来解决这个问题。

      give config someComplexCalculation :: Result
      

      并且类型确保我们不会让这个缺失的配置传播到顶部。


      Reifies/reflect/reify 机制概括了这个属性,但这样做却失去了一些便利性和简单性。如果您必须传递多个重叠的配置,则需要使用Given,系统不可能知道哪个givens 应该被重叠区域下传递的配置替换,Reifies 用@解决了这个问题987654343@ 参数很像 ST monad 的 s 参数。

      Reifies 的杀手级应用程序将运行时配置的类型类传递给表达式。通常你不能覆盖类型类,但Reifies 提供了一种技巧。有关详细信息,请参阅 Austin Seipp 的底部教程。

      【讨论】:

      • 你也可以用give的签名为隐式参数写一些东西——还有什么方法可以颠覆结果吗? give :: a -&gt; ((?given :: a) =&gt; r) -&gt; r ; give v c = let ?given = v in c
      • 嗯,你说得对。我误解了隐式参数是如何工作的。我会调整比较点。
      • @GaneshSittampalam 你认为隐式参数可以在任何地方使用,否则会使用反射库吗? (他们解决的是同一个问题吗?)如果是这样,那将是一个很好的观察,我认为。
      • 我不完全确定。 Oleg and Ken Shan's paper的6.2节有详细对比;我的解读是,他们认为他们都解决了同一个问题,但隐式参数由于其他原因是危险的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-11
      • 2023-02-21
      • 2016-09-30
      • 1970-01-01
      相关资源
      最近更新 更多