Find the length of a frequency-change cycle (Advent of Code 2018)
I'm not convinced that your algorithm is very different, but since it'll take me more than a comment to outline it, I figured I might as well do a quick code review.

I've also done some name switching -- in general I tried to keep my comments referring to the same named functions as yours, but if you see an unfamiliar name you can reference my sample solution. I probably used the name I gave in it.

Small things

input

Splitting input into a bunch of different functions is good, but there are some changes I'd make.

I would rewrite removeLeadingPlus as a more general function that isn't partial (I'm pretty sure your implementation errors on the empty string -- not that it matters that much since read will error on an empty string too)

removeLeadingPlus :: String -&gt; String
removeLeadingPlus ('+':s) = s
removeLeadingPlus s = s


Note that both cleanNumbers and toNum can be defined without the helper variable strs/string as so

cleanNumbers = map removeLeadingPlus
toNum = map read


I also think that

toNum (cleanNumbers (splitNumbers contents))


is a bit hard on the eyes (though if you're coming from Lisp, I'd understand your inclination). You've correctly identified that input can be realized as the composition of a few functions, so why not define it as such?

input :: String -&gt; [Integer]
input = toNum . cleanNumbers . lines
    where 
        cleanNumbers = map removeLeadingPlus
        toNum = map read
        removeLeadingPlus ('+':s) = s
        removeLeadingPlus s = s


And we can go one step further. Now that we've realized that toNum and cleanNumbers are both just maps, we can redefine input as

input :: String -&gt; [Integer]
input = map (read . removeLeadingPlus) . lines
    where 
        removeLeadingPlus ('+':s) = s
        removeLeadingPlus s = s


Maybe this looks less clean to you. I think that

input = map (read . removeLeadingPlus) . lines


and

input contents = map (read . removeLeadingPlus) $ lines contents


are both fine and pretty readable.

accumulate

I like this function and I think it's a better approach to obtaining the frequencies (your inefficiency is still in duplicate). However, accumulate looks suspiciously similar to a function in Prelude.

accumulate takes in a list of integers and returns all of the prefix sums of them. So it is essentially foldl of every prefix of the input list.

You might be familiar with scanl which is a combinator that does this.

*DayOnePartTwo&gt; accumulate [1,2,3,4]
[1,3,6,10]
*DayOnePartTwo&gt; scanl (+) 0 [1,2,3]4
[0,1,3,6,10]


It even gives us the desired starting value of 0 (which you presently hardcode into duplicate).

So we can write a much shorter definition of accumulate as

accumulate :: [Integer] -&gt; [Integer]
accumulate = scanl (+) 0


I also think a better name is freqs since I think accumulate is vague.

firstCyclicDuplicate

This can also be realized as the composition of a bunch of functions

firstCyclicDuplicate :: [Integer] -&gt; Maybe Integer
firstCyclicDuplicate = duplicate . accumulate . cycle


Again, up to personal preference.

duplicate

I would rename this to firstDuplicate to be consistent with firstCyclicDuplicate.

Indentation

I'm not a Haskell pro, but your indentation seems a bit off and my syntax highlighting agrees with it (it doesn't highlight the function declarations when they're indented). I would keep everything top level unindented. Also my personal preference is indenting by two spaces, so that's what I've done in my sample solution.

Imports

Your imports are unnecessary as far as I can tell (my code compiles without them). If you thought you needed the imports for the types, I think they're in Prelude.

Use of Maybe in duplicate

I like the use of Maybe here and I suggested the same to dagda1, but I realize now that its use, while stylistically good, is somewhat pointless.

Since you're operating on an infinite list of frequencies, your code will run forever (or until the stack blows). Even though you have

        dup [] _ = Nothing


you'll never pattern match an empty list since you're running this function on an infinite one.

Stylistically, I think using Maybe is better than having a partial function, but I just wanted to point out that having pure functions doesn't necessarily save you from an infinite loop.

Efficiency

Your implementation is, for all intents and purposes, the same algorithm as dagda1's in the other post.

They chose to accumulate the frequencies inside of their function to find the duplicate, whereas you've decoupled the two. Due to laziness I would argue the approaches are the same.

Again, the inefficiency comes down to the use of lists over sets in duplicate. This is a great problem to demonstrate why elem is something you should be wary of using and why linked lists make poor sets.

Your issue is in your duplicate lookup

            if elem x visited


as elem is an O(n) operation. You can read the Efficiency section on my answer to the other post for a bit more details.

The solution is the same, and I'll copy paste my response for convenience.

Introducing Data.Set

Data.Set provides unordered sets with O(log n) lookup and insert time. Yeah, it's O(log n), but the total runtime for me when I checked the implementation was less than a second.

I'm including a sample implementation of day 1 below that I've tested and confirmed on my inputs if you want to compare. Here are some pointers if you want to fix it yourself


You can keep your code mostly the same (although I'd recommend making the style changes I suggested)
You will want to use two functions from Data.Set: member and insert.
Your only need to change duplicate, specifically your use of data structure (see section header, hint hint)


Sample solution

module DayOnePartTwo where

import Data.Set (Set)
import qualified Data.Set as S

inputFileName = "input.txt"

input :: String -&gt; [Integer]
input = map (read . removeLeadingPlus) . lines
  where
    removeLeadingPlus ('+':s) = s
    removeLeadingPlus s = s

freqs :: [Integer] -&gt; [Integer]
freqs = scanl (+) 0

firstDuplicate :: [Integer] -&gt; Maybe Integer
firstDuplicate list = dup list S.empty
  where
    dup (x:xs) visited =
      if x `S.member` visited
        then Just x
        else dup xs (S.insert x visited)
    -- This will never be reached on the cycled list
    dup [] _ = Nothing

firstCyclicDuplicate :: [Integer] -&gt; Maybe Integer
firstCyclicDuplicate = firstDuplicate . freqs . cycle

main :: IO ()
main = do
  contents &lt;- input &lt;$&gt; readFile inputFileName
  case firstCyclicDuplicate contents of
    Just a -&gt; print a
    Nothing -&gt; print "There is no first duplicate"