1. 11
  1. 4

    If you’re working in a less strongly-typed language than Rust, it’s a good idea to reach for a higher-order function in these cases

    I’m not clear on why higher-order functions are for less strongly-typed languages. Seems like the main benefit strategies have over HOFs is that you can restrict the API to only “strategy functions”, but that can be encoded in the type system too.

    1. 1

      My reasoning there is that if you are accepting an object as a strategy in a duck-typed language, you then get the “excitement” of duck typing in addition to needing to null-check the argument. Consider:

      def hailstone_numbers(recorder, n):
          ...
      
          if recorder is not None:
              try:
                  recorder.record(n)
              except AttributeError:
                  pass
      

      With a HOF, you only need the external null-check:

      def hailstone_numbers(record, n):
          ...
      
          if record is not None:
              record(n)
      

      Obviously, this is a matter of taste. :)

    2. 2

      Hm, I’d say the example is more specific: it is “internal iteration”, rather than general strategy. I usually code those as

      fn halistone_numbers(n: u128, sink: &mut dyn FnMut(u128))
      

      It probably is important to note, for pedagogical purposes, that in this case external iteration works fine:

      let halistone = std::iter::successors(Some(n), |x| if x == 1 { None } else { Some(if x % 2 == 0 { x / 2 } else { 3*x + 1 })})
      
      1. 2

        I’m aware there are a ton of alternative implementations here – I chose it as an example because it’s easy to understand, with a little more complexity than “fizzbuzz.” If it helps, imagine it’s actually weather simulations.

        1. 1

          This is exactly what I’m thinking. You are constructing finite lists, so construct the list then operate on it.

          let hailstone_count = hailstone(n).length
          let hailstone_max = hailstone(n).fold(max)
          

          If you cannot construct the whole list, or reconstruct it, then deal with it as a streaming problem and just combine a bunch of traversals on the stream.

          With this your hailstone function doesn’t have to know something about how is it going to be consumed, and therefore is simpler and more generic.

          1. 1

            I don’t thin finitness plays a role here. iter::successors would work just fine for an infinite list as well.