Делать функцию с "если" без точек
У меня есть задача в Haskell (нет, это не моя домашняя работа, я учусь на экзамене).
Задача:
Write point-free function numocc
, которая подсчитывает вхождения элементов в заданные списки. Например: 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
являются "точечными", но они используют функцию addif
, которая не является.
Я не знаю, как я могу сделать функцию addif
без точек. Можно ли сделать оператор if
без указания точки? Может быть, есть трюк, который не использует if
?
Ответы
Ответ 1
Я бы использовал тот факт, что вы можете легко преобразовать Bool
в Int
с помощью fromEnum
:
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
s и возвращает a
, не так уж плохо.
> :t fmap fmap fmap fromEnum (==)
Eq a => a -> a -> Int
И этот тип можно записать как Eq a => a -> (a -> Int)
, что важно отметить. Это заставляет тип возвращаемой функции соответствовать входу fmap fmap fmap sum map
с помощью b ~ Int
, поэтому мы можем создать для них функцию типа Eq a => a -> [a] -> Int
.
Ответ 2
почему не
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-сжатие.
Ответ 3
Один трюк заключается в том, чтобы импортировать одну из функций if
, например. Data.Bool.bool 1 0
(также найдено в Data.Bool.Extras
).
Более загадочным трюком было бы использовать Foreign.Marshal.Utils.fromBool
, который делает именно то, что вам нужно здесь. Или то же самое, менее загадочное: fromEnum
(спасибо @bheklilr).
Но я думаю, что самым простым трюком было бы просто не считать себя, а просто применить стандартную функцию length
после filter
ing для номера.
Ответ 4
Используя экземпляр Enum
для Bool
, можно построить замену с нулевой точкой, если это можно использовать в более общих случаях:
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 (==))
Теперь мы можем просто заменить chk
на addIf'
:
addIf :: Eq a => a -> a -> Int -> Int
addIf = curry (flip (([snd,fst]!!) . fromEnum) ((+1),id) . uncurry (==))
Ответ 5
Я думаю, что вы ищете Data.Bool
s 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
оставлено как упражнение для читателя. [Перевод: его нетривиально, и я еще не знаю как.;])