使 'if' 无点函数
make function with 'if' point-free
我在Haskell有一个任务(不,这不是我的作业,我正在为考试学习)。
任务是:
Write point-free function numocc
which counts occurrences of element in given lists. For example: numocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]
= [1, 2, 0]
这是我的代码:
addif :: Eq a => a -> Int -> a -> Int
addif x acc y = if x == y then acc+1 else acc
count :: Eq a => a -> [a] -> Int
count = flip foldl 0 . addif
numocc :: Eq a => a -> [[a]] -> [Int]
numocc = map . count
numocc
和 count
是 'point-free',但它们使用的函数 addif
不是。
我不知道如何才能无积分地执行 addif
功能。有什么办法可以让 if
语句免分?也许有一个不使用 if
?
的技巧
一个技巧是导入其中一个 if
functions, e.g. Data.Bool.bool 1 0
(also found in Data.Bool.Extras
).
一个更神秘的技巧是使用 Foreign.Marshal.Utils.fromBool
,这正是您在这里需要的。或者同样的事情,不那么神秘:fromEnum
(感谢@bheklilr)。
但我认为最简单的技巧就是避免数自己,只应用标准 length
function after filter
ing 作为数字。
为什么不
numocc x
= map (length . filter (== x))
= map ((length .) (filter (== x)) )
= map (((length .) . filter) (== x))
= map (((length .) . filter) ((==) x))
= map (((length .) . filter . (==)) x)
= (map . ((length .) . filter . (==))) x
= (map . (length .) . filter . (==)) x
然后是微不足道的 eta 收缩。
我会利用这样一个事实,即您可以使用 fromEnum
:
轻松地将 Bool
转换为 Int
addif x acc y = acc + fromEnum (x == y)
现在您可以开始应用常用技巧来使其无积分
-- Go prefix and use $
addif x acc y = (+) acc $ fromEnum $ (==) x y
-- Swap $ for . when dropping the last argument
addif x acc = (+) acc . fromEnum . (==) x
等等。我不会剥夺让它免费的所有乐趣,尤其是当有工具可以为您做到这一点时。
或者,你可以写一个像
这样的函数
count x = sum . map (fromEnum . (==) x)
这几乎是免费的,并且有一些技巧可以让你更接近,尽管它们很快就会变得非常讨厌:
count = fmap fmap fmap sum map . fmap fmap fmap fromEnum (==)
这里我认为使用 fmap
而不是 (.)
实际上看起来更好,尽管你可以将每个 fmap
替换为 (.)
并且它会完全相同代码。本质上,(fmap fmap fmap)
将单个参数和两个参数函数组合在一起,如果您改为给它命名 .:
,您可以将其写为
count = (sum .: map) . (fromEnum .: (==))
细分:
> :t fmap fmap fmap sum map
Num a => (a -> b) -> [a] -> b
所以它需要一个从b
到一个数字a
的函数,一个b
的列表,和return一个a
,而不是太糟糕了。
> :t fmap fmap fmap fromEnum (==)
Eq a => a -> a -> Int
而这个类型可以写成Eq a => a -> (a -> Int)
,这是一个需要注意的重要事项。这使得此函数的 return 类型与 fmap fmap fmap sum map
的输入与 b ~ Int
相匹配,因此我们可以将它们组合起来以获得类型为 Eq a => a -> [a] -> Int
.
的函数
使用 Enum
实例 Bool
,可以构建一个 pointfree 替代品,如果可以在更一般的情况下使用:
chk :: Bool -> (a,a) -> a
chk = ([snd,fst]!!) . fromEnum
使用chk
我们可以定义不同版本的addIf
:
addIf' :: Eq a => a -> a -> Int -> Int
addIf' = curry (flip chk ((+1),id) . uncurry (==))
现在我们可以简单地替换 addIf'
中的 chk
:
addIf :: Eq a => a -> a -> Int -> Int
addIf = curry (flip (([snd,fst]!!) . fromEnum) ((+1),id) . uncurry (==))
我认为您正在寻找 Data.Bool
的 bool
,它自 4.7.0.0 (2014–04–08) 以来就存在。
incif :: (Eq a, Enum counter) => a -> a -> counter -> counter
incif = ((bool id succ) .) . (==)
额外的 .
允许 ==
在将表达式传递给 bool
之前采用两个参数。
由于参数顺序不同,需要这样使用incif
:
(flip . incif)
(将 整合到 incif
留作 reader 的练习。[翻译:这不是微不足道的,我还不知道知道怎么做。;])
请记住,在 Haskell 列表推导式中,if 条件句可用于结果子句或末尾。但是,最重要的是,不带 if 的守卫可以用来过滤结果。我正在使用 zip 中的对。第二个是列表编号。当列表的元素与常量 (k) 进行比较时,它保持不变。
您的结果 [1,2,0] 不包括列表编号 1、2 或 3,因为从结果列表中总和的位置可以明显看出。这里的结果不会添加每个列表中的出现次数,而是为每个列表列出它们。
nocc k ls = [ z | (y,z) <- zip ls [1..length ls], x <- y, k == x]
nocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]
[1,2,2] -- 读取为 [1,2,0] 或列表 1 中的 1、列表 2 中的 2 和列表 3 中的 0
我在Haskell有一个任务(不,这不是我的作业,我正在为考试学习)。
任务是:
Write point-free function
numocc
which counts occurrences of element in given lists. For example:numocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]
=[1, 2, 0]
这是我的代码:
addif :: Eq a => a -> Int -> a -> Int
addif x acc y = if x == y then acc+1 else acc
count :: Eq a => a -> [a] -> Int
count = flip foldl 0 . addif
numocc :: Eq a => a -> [[a]] -> [Int]
numocc = map . count
numocc
和 count
是 'point-free',但它们使用的函数 addif
不是。
我不知道如何才能无积分地执行 addif
功能。有什么办法可以让 if
语句免分?也许有一个不使用 if
?
一个技巧是导入其中一个 if
functions, e.g. Data.Bool.bool 1 0
(also found in Data.Bool.Extras
).
一个更神秘的技巧是使用 Foreign.Marshal.Utils.fromBool
,这正是您在这里需要的。或者同样的事情,不那么神秘:fromEnum
(感谢@bheklilr)。
但我认为最简单的技巧就是避免数自己,只应用标准 length
function after filter
ing 作为数字。
为什么不
numocc x
= map (length . filter (== x))
= map ((length .) (filter (== x)) )
= map (((length .) . filter) (== x))
= map (((length .) . filter) ((==) x))
= map (((length .) . filter . (==)) x)
= (map . ((length .) . filter . (==))) x
= (map . (length .) . filter . (==)) x
然后是微不足道的 eta 收缩。
我会利用这样一个事实,即您可以使用 fromEnum
:
Bool
转换为 Int
addif x acc y = acc + fromEnum (x == y)
现在您可以开始应用常用技巧来使其无积分
-- Go prefix and use $
addif x acc y = (+) acc $ fromEnum $ (==) x y
-- Swap $ for . when dropping the last argument
addif x acc = (+) acc . fromEnum . (==) x
等等。我不会剥夺让它免费的所有乐趣,尤其是当有工具可以为您做到这一点时。
或者,你可以写一个像
这样的函数count x = sum . map (fromEnum . (==) x)
这几乎是免费的,并且有一些技巧可以让你更接近,尽管它们很快就会变得非常讨厌:
count = fmap fmap fmap sum map . fmap fmap fmap fromEnum (==)
这里我认为使用 fmap
而不是 (.)
实际上看起来更好,尽管你可以将每个 fmap
替换为 (.)
并且它会完全相同代码。本质上,(fmap fmap fmap)
将单个参数和两个参数函数组合在一起,如果您改为给它命名 .:
,您可以将其写为
count = (sum .: map) . (fromEnum .: (==))
细分:
> :t fmap fmap fmap sum map
Num a => (a -> b) -> [a] -> b
所以它需要一个从b
到一个数字a
的函数,一个b
的列表,和return一个a
,而不是太糟糕了。
> :t fmap fmap fmap fromEnum (==)
Eq a => a -> a -> Int
而这个类型可以写成Eq a => a -> (a -> Int)
,这是一个需要注意的重要事项。这使得此函数的 return 类型与 fmap fmap fmap sum map
的输入与 b ~ Int
相匹配,因此我们可以将它们组合起来以获得类型为 Eq a => a -> [a] -> Int
.
使用 Enum
实例 Bool
,可以构建一个 pointfree 替代品,如果可以在更一般的情况下使用:
chk :: Bool -> (a,a) -> a
chk = ([snd,fst]!!) . fromEnum
使用chk
我们可以定义不同版本的addIf
:
addIf' :: Eq a => a -> a -> Int -> Int
addIf' = curry (flip chk ((+1),id) . uncurry (==))
现在我们可以简单地替换 addIf'
中的 chk
:
addIf :: Eq a => a -> a -> Int -> Int
addIf = curry (flip (([snd,fst]!!) . fromEnum) ((+1),id) . uncurry (==))
我认为您正在寻找 Data.Bool
的 bool
,它自 4.7.0.0 (2014–04–08) 以来就存在。
incif :: (Eq a, Enum counter) => a -> a -> counter -> counter
incif = ((bool id succ) .) . (==)
额外的 .
允许 ==
在将表达式传递给 bool
之前采用两个参数。
由于参数顺序不同,需要这样使用incif
:
(flip . incif)
(将 整合到 incif
留作 reader 的练习。[翻译:这不是微不足道的,我还不知道知道怎么做。;])
请记住,在 Haskell 列表推导式中,if 条件句可用于结果子句或末尾。但是,最重要的是,不带 if 的守卫可以用来过滤结果。我正在使用 zip 中的对。第二个是列表编号。当列表的元素与常量 (k) 进行比较时,它保持不变。 您的结果 [1,2,0] 不包括列表编号 1、2 或 3,因为从结果列表中总和的位置可以明显看出。这里的结果不会添加每个列表中的出现次数,而是为每个列表列出它们。
nocc k ls = [ z | (y,z) <- zip ls [1..length ls], x <- y, k == x]
nocc 1 [[1, 2], [2, 3, 2, 1, 1], [3]]
[1,2,2] -- 读取为 [1,2,0] 或列表 1 中的 1、列表 2 中的 2 和列表 3 中的 0