【问题标题】:Haskell function that accepts function or value, then calls function or returns valueHaskell 函数接受函数或值,然后调用函数或返回值
【发布时间】:2015-05-15 03:38:13
【问题描述】:

如何在 Haskell 中编写类型声明和函数,该函数接受一个函数(本身不接受参数)一个值。当给定一个函数时,它会调用该函数。当给定一个值时,它会返回该值。

[编辑]为了提供更多的上下文,我主要是好奇如何在 Haskell 中解决这个问题而不需要一点点旋转:Designing function f(f(n)) == -n

肖恩

【问题讨论】:

  • 你不能,除非你使用(某种类型的同构)Either,这样两种类型都被一个单一的类型封装。更重要的是,您不应该这样做:Haskell 并非旨在让您将不同的类型传递给同一个函数。
  • @dfeuer 我选择将自己限制在我认为合理的解决方案中......
  • f(f(7)) 调用f,结果为f(7)。它从不调用f(f)。 Haskell 版本将是 f (f 7)f (f (f 7)),而不是 f f 7
  • “本身不带参数的函数”在 Haskell 中不是一个东西。没有副作用,“当我运行它时会给我一个值的东西,不需要信息”和只有返回值之间没有语义差异。对于懒惰,甚至没有已经完成所有计算工作的操作差异。
  • @leftaroundabout A 在表示上与 A^1 同构(这通常也适用于具有单位和指数的代数结构),但在操作上可能不同。当然,在严格的语言中它甚至更加不同,转换 A -> A^1 通常用于创建“thunk”,利用它们的指称等价。

标签: haskell functional-programming


【解决方案1】:

我很好奇如何在 Haskell 中解决这个问题而无需费力:Designing function f(f(n)) == -n

这其实很容易解决:

when :: (a -> Bool) -> (a -> a) -> a -> a
when p f x = if p x then f x else x

f :: Integer -> Integer
f = (+) <$> when even negate <*> signum

我们如何得出这个?考虑:

f (f n) = (-n) -- (0) - from the interview question

f x     = y    -- (1) - assumption

f y     = (-x) -- (2) - from (0) and (1), f (f x) = (-x)

f (-x)  = (-y) -- (3) - from (0) and (2), f (f y) = (-y)

f (-y)  = x    -- (4) - from (0) and (3), f (f (-x)) = x

现在,如果您看到这些等式的左侧,您会注意到有四种情况:

  1. f x
  2. f y
  3. f (-x)
  4. f (-y)

注意函数f的域分为正数和负数,x(-x),以及y(-y)。假设xy 一起构成正数集,(-x)(-y) 一起构成负数集。

正数集合分为properdisjoint两个子集xy。我们如何将一组正数分成两个适当的不相交子集?奇数和偶数都是不错的选择。因此,假设x 是正奇数的集合,y 是正偶数的集合。

使用奇数和偶数的另一个优点是,当求反时,奇数保持奇数,偶数保持偶数。因此,(-x) 是负奇数的集合,(-y) 是负偶数的集合。

现在,再次考虑这四种情况。请注意,仅当数字为偶数时符号才会改变:

  1. f x = y(符号不变)。
  2. f y = (-x)(符号更改)。
  3. f (-x) = (-y)(符号不变)。
  4. f (-y) = x(符号更改)。

因此,我们只在偶数时取反(即when even negate)。

接下来,我们需要将奇数转换为偶数,反之亦然。最简单的方法是在数字中加或减一。但是,应注意结果数字不是0。考虑0的特殊情况:

f 0    = z    -- (a) - assumption

f z    = (-0) -- (b) - from (0) and (a), f (f 0) = (-0)

f (-0) = (-z) -- (c) - from (0) and (b), f (f z) = (-z)

(-0)   = 0    -- (d) - reflexivity

f (-0) = f 0  -- (e) - from (d)

(-z)   = z    -- (f) - from (a) and (c) and (e)

z      = 0    -- (g) - from (d) and (f)

f 0    = 0    -- (h) - from (a) and (g)

因此,f n = 0if and only ifn = 0。所以让我们考虑01(-1) 的邻居。这两个都是奇数。因此,它们没有被否定。但是,它们确实需要转换为偶数(0 除外)。因此,1 被转换为2(-1) 被转换为(-2)

因此,对于奇数,我们只需将数字的符号添加到数字本身。

现在,考虑偶数。我们知道:

f 1    = 2    -- (A)

f (-1) = (-2) -- (B)

因此:

f 2    = (-1) -- (C), from (0) and (A), f (f 1) = (-1)

f (-2) = 1    -- (D), from (0) and (B), f (f (-1)) = 1

我们知道偶数总是被否定的。因此,2 首先变为(-2),反之亦然。设原来的偶数为n。因此,首先我们negate n,然后添加signum n

evenF n    = negate n    + signum n

evenF 2    = negate 2    + signum 2
           = (-2)        + 1
           = (-1)

evenF (-2) = negate (-2) + signum (-2)
           = 2           + (-1)
           = 1

evenF 0    = negate 0    + signum 0
           = 0           + 0
           = 0

因此,对于奇数和偶数,我们将原始数字的符号添加到when even negate。因此,f 定义为:

f :: Integer -> Integer
f = (+) <$> when even negate <*> signum

希望对您有所帮助。

【讨论】:

  • 哇!极好的。谢谢你。 ʘ‿ʘ
【解决方案2】:

您不能编写具有两个不同签名的函数(除非您使用类型类,但类型类不适合这个问题)。您必须以一种让您将函数和非函数值视为同一类型的方式来解决这个问题。有两个明显的选择:

  1. 使用求和类型。

    f :: Either (Int -> Char) Char -> Char
    f (Left g) = g 1
    f (Right c) = c
    
  2. 使用const 将您的非函数值转换为忽略其参数的函数:

    f = ($ 42)
    f chr         --> '*'
    f (const 'a') --> 'a'
    

但是,由于这是一个非常不合时宜的要求,我怀疑这是XY problem

【讨论】:

  • 肯定是 XY 问题,无论如何感谢您的帮助!
【解决方案3】:

可以这样做:

data FunctionOrValue a
    = Function (() -> a)
    | Value a

getValue :: FunctionOrValue a -> a
getValue (Function f) = f ()
getValue (Value x) = x

不过这有点傻。

听起来您正在尝试手动延迟值,但由于 Haskell 是惰性的,通常不需要这样做。

【讨论】:

    【解决方案4】:

    基于您发布的面试问题的答案:

    f n = if (abs fracN) > 1 then 1/fracN else - (1/fracN)
      where
        fracN = realToFrac n
    

    问题指定输入是一个int;它没有指定结果也必须是 int。

    编辑:如果您必须返回一个 Int,请注意该问题允许您指定一系列可能的输入。我使用 1073741823 的限制(带符号的 32 位 int 最大值的一半),这样我就可以这样写:

    fint :: Int -> Int
    fint 0 = 0
    fint n = if (abs n) <= rangeVal then n+addend else -(n-addend)
      where
        rangeVal = 1073741823
        negator = if n < 0 then -1 else 1
        addend = negator*rangeVal  
    

    【讨论】:

    • 这很酷。好奇如果要求 Int 结果,你会如何写这个?
    • 你启发了我去寻找一个没有范围的Integer -&gt; Integer 的解决方案。有可能!
    • @luqui 你能发布你找到的没有范围的解决方案吗?
    • 我找到了和@seanomlor 一样的(只是表达方式有点不同)
    【解决方案5】:

    Haskell(恕我直言)中的一个好处是值和没有参数返回值的函数之间没有区别(感谢惰性和纯度)。或者,如果您愿意,每个值实际上都是一个没有参数的函数,将在需要时进行评估。因此,无需担心此类问题。没有f()这样的东西,只有f

    例如,你可以写

    x = 3 :: Int 
    f = head [] :: Int -- should blow up but doesn't
    
    head [x, f] -- note that f and x have the same type
    > 3  -- doesn't blow up on f, because f is not called
    
    head [f] -- blows up, when trying to print f
    

    【讨论】:

    • 值不是“没有参数的函数”。那是绝对错误的。 Values and functions are different things altogether。惰性和引用透明度(纯度)是与函数和值正交的概念。是的,head [] 不会因为懒惰而崩溃,但这并不能使它成为“没有参数的函数”,因为它根本不是函数。这是一个未经评估的表达。此外,f = ... 既不是函数也不是值。这只是一个名称绑定。最后,Haskell 确实具有无效功能。它们只是 IO 操作。 3=D
    • 此外,我根本看不出您的回答如何解决了 OP 的问题。因此,它应该是评论而不是答案。
    • @AaditMShah 这是您对包含副作用的空函数的定义。我同意从技术上讲,“值”不是(-&gt;) 的实例,因此不是这样的功能,但IO 值也不是。有行动,没有功能。如果你同意haskell中存在nullary function,那么value和nullary function在语义上是等价的:这是a -&gt; b -&gt; c的泛化是一个有2个args的函数,a -&gt; b,1个arg,a:0。
    • ... 这种等价性在其他语言中不成立,例如在 python 中,您可以拥有 x = 3def f(): 3,它们的行为会有所不同。在 Haskell 中,没有等价于 def f(): 3。我认为这就是 OP 所混淆的。
    猜你喜欢
    • 2021-02-07
    • 1970-01-01
    • 1970-01-01
    • 2021-08-09
    • 2021-10-18
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    • 2015-04-11
    相关资源
    最近更新 更多