Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of the ArgMin and ArgMax type synonyms in Data.Semigroup?

The base library in Haskell has the following type synonyms in Data.Semigroup:

type ArgMin a b = Min (Arg a b)

type ArgMax a b = Max (Arg a b) 

Here are links to the haddocks: ArgMin and ArgMax

What is the purpose of these two type synonyms? Where can they be used effectively?

It might be helpful to include an explanation of what the argmin and argmax functions do in mathematics, and how that is related to these type synonyms.


Here's a little extra information so you don't have to jump to Hackage.

Here's the definition of Arg:

-- | 'Arg' isn't itself a 'Semigroup' in its own right, but it can be
-- placed inside 'Min' and 'Max' to compute an arg min or arg max.
data Arg a b = Arg a b

Its doc string suggests that ArgMin and ArgMax can be placed inside of Min and Max to compute an arg min or an arg max.

Min and Max look like the following:

newtype Min a = Min { getMin :: a }

The Semigroup instance is interesting:

instance Ord a => Semigroup (Min a) where
  (<>) = coerce (min :: a -> a -> a)

It looks like it is using min as (<>).

We can look at what the Ord instance looks like for Arg, since it is relevant here:

instance Ord a => Ord (Arg a b) where
  Arg a _ `compare` Arg b _ = compare a b
  min x@(Arg a _) y@(Arg b _)
    | a <= b    = x
    | otherwise = y
  max x@(Arg a _) y@(Arg b _)
    | a >= b    = x
    | otherwise = y

This appears to only run the comparison on the first type argument to Arg.

like image 270
illabout Avatar asked Nov 20 '20 12:11

illabout


People also ask

What does ArgMin stand for?

argmin is argument of the minimum so it is in general the set of values where the function attains the minimum. The simplest example is. argminxf(x) is the value of x for which f(x) attains its minimum.

What is semigroup in Haskell?

From HaskellWiki. The Semigroup represents a set with an associative binary operation. This makes a semigroup a superset of monoids. Semigoups have no other restrictions, and are a very general typeclass.

What is argmax in C++ with example?

For example, given a function g () that takes the argument x, the argmax operation of that function would be described as follows: The argmax function returns the argument or arguments ( arg) for the target function that returns the maximum ( max) value from the target function.

What is the difference between $arg max and $arg min?

$arg min$ (or $arg max$) return the input for minimum (or maximum) output. For example: The global minmum of $f(x)$ is $min(f(x)) approx$ -2, while the $arg min(f(x)) approx$ 4.9 .

Is argmax one word or two words?

Typically, “ argmax ” is written as two separate words, e.g. “ arg max “. For example: It is also common to use the arg max function as an operation without brackets surrounding the target function. This is often how you will see the operation written and used in a research paper or textbook.

Why is the argmax 5 in Python?

The argmax () is 5 because g returns the largest value (25) when 5 is provided, not because 5 is the largest argument. Typically, “ argmax ” is written as two separate words, e.g. “ arg max “. For example: It is also common to use the arg max function as an operation without brackets surrounding the target function.


3 Answers

I suppose it's one of those things that exist in Haskell because the theoretical concept exists. I'm not sure if these types have much practical use, but they do illustrate just how extensive the concepts of semigroups and monoids are in relation to programming.

Imagine, for example, that you need to pick the longest of two names, name1 and name2, both of them String values. You can use the Semigroup instance of ArgMax for that:

Prelude Data.Semigroup> Max (Arg (length name1) name1) <> Max (Arg (length name2) name2)
Max {getMax = Arg 5 "Alice"}

After that, it's just a question of unwrapping "Alice" from its container.

As Willem Van Onsem points out in the comments, you can use ArgMax and ArgMin to pick the maximum or minimum item, according to some attribute of the item, but still keeping the original item around.

like image 184
Mark Seemann Avatar answered Oct 10 '22 10:10

Mark Seemann


The purpose of them is to implement things like minimumOn:

minimumOn :: (Ord b, Foldable f) => (a -> b) -> f a -> Maybe a
minimumOn f = fmap (getArg  . getMin)
            . getOption
            . foldMap (Option . Just . Min . (Arg =<< f))
            --                         ^^^^^^^^^^
            --                           ArgMin
  where
    getArg (Arg _ x) = x

While this implementation might look a little convoluted, it's often helpful to implement things using general concepts like monoids. For instance, in this case, it is straightforward to adapt the above code to compute the min and max in a single pass.

like image 28
oisdk Avatar answered Oct 10 '22 09:10

oisdk


I reach for ArgMin / ArgMax when:

  • I want to compute (a function of) the minimum/maximum of some values according to a comparison function

  • The comparison is costly or unwieldy to recompute, so I want to cache its result; and/or

  • I want to do it monoidally with foldMap instead of with an explicit/specialised minimumBy / maximumBy or sortOn, to leave it flexible to changes in the future such as a different monoid or parallelisation

Here’s an adaptation of a recent real-world example from my job, findNextWorkerQueue, which takes a map from workers to tasks and finds the worker with the earliest first task, e.g. given this input:

  • Worker 1:

    • Time 10: Task A
    • Time 12: Task B
    • Time 14: Task C
  • Worker 2:

    • Time 5: Task D
    • Time 10: Task E
    • Time 15: Task F
  • Worker 3:

    • Time 22: Task G
    • Time 44: Task H

It would produce a start time of 5, and a work queue describing worker 2, with a first task of D, and subsequent tasks of E & F.

{-# LANGUAGE ScopedTypeVariables #-}

import Data.Map       (Map)
import Data.Semigroup (Arg(..), Min(..), Option(..))
import Data.Sequence  (Seq(Empty, (:<|)))

import qualified Data.Map as Map

-- An enumeration of computation units for running tasks.
data WorkerId = …

-- The timestamp at which a task runs.
type Time = Int

-- Some kind of task scheduled at a timestamp.
data Scheduled task = Scheduled
  { schedAt   :: !Time
  , schedItem :: !task
  }

-- A non-empty sequence of work assigned to a worker.
data WorkQueue task = WorkQueue
  { wqId    :: !WorkerId
  , wqFirst :: !(Scheduled task)
  , wqRest  :: !(Seq (Scheduled task))
  }

-- | Find the lowest worker ID with the first scheduled task,
-- if any, and return its scheduled time and work queue.
findNextWorkerQueue
  :: forall task
  .  Map WorkerId (Seq (Scheduled task))
  -> Maybe (Time, WorkerQueue task)
findNextWorkerQueue
  = fmap getTimeAndQueue . getOption
  . foldMap (uncurry minWorkerTask) . Map.assocs
  where

    minWorkerTask
      :: WorkerId
      -> Seq (Scheduled task)
      -> Option (Min (Arg (Time, WorkerId) (WorkQueue task)))
    minWorkerTask wid tasks = Option $ case tasks of
      Empty -> Nothing
      t :<| ts -> Just $ Min $ Arg
        (schedTime t, wid)
        WorkQueue { wqId = wid, wqFirst = t, wqRest = ts }

    getTimeAndQueue
      :: Min (Arg (Time, WorkerId) (WorkQueue task))
      -> (Time, WorkQueue task)
    getTimeAndQueue (Min (Arg (time, _) queue))
      = (time, queue)

(Note that this is using Option to support GHC 8.6; in GHC ≥8.8, Maybe has an improved Monoid instance depending on Semigroup instead of Monoid, so we can use it with Min without imposing a Bounded constraint. The time signatures are just for clarity here.)

like image 2
Jon Purdy Avatar answered Oct 10 '22 10:10

Jon Purdy