In Functional Programming, what is a functor?

后端 未结 17 587
孤独总比滥情好
孤独总比滥情好 2020-11-28 17:23

I\'ve come across the term \'Functor\' a few times while reading various articles on functional programming, but the authors typically assume the reader already understands

相关标签:
17条回答
  • 2020-11-28 17:54

    In a comment to the top-voted answer, user Wei Hu asks:

    I understand both ML-functors and Haskell-functors, but lack the insight to relate them together. What's the relationship between these two, in a category-theoretical sense?

    Note: I don't know ML, so please forgive and correct any related mistakes.

    Let's initially assume that we are all familiar with the definitions of 'category' and 'functor'.

    A compact answer would be that "Haskell-functors" are (endo-)functors F : Hask -> Hask while "ML-functors" are functors G : ML -> ML'.

    Here, Hask is the category formed by Haskell types and functions between them, and similarly ML and ML' are categories defined by ML structures.

    Note: There are some technical issues with making Hask a category, but there are ways around them.

    From a category theoretic perspective, this means that a Hask-functor is a map F of Haskell types:

    data F a = ...
    

    along with a map fmap of Haskell functions:

    instance Functor F where
        fmap f = ...
    

    ML is pretty much the same, though there is no canonical fmap abstraction I am aware of, so let's define one:

    signature FUNCTOR = sig
      type 'a f
      val fmap: 'a -> 'b -> 'a f -> 'b f
    end
    

    That is f maps ML-types and fmap maps ML-functions, so

    functor StructB (StructA : SigA) :> FUNCTOR =
    struct
      fmap g = ...
      ...
    end
    

    is a functor F: StructA -> StructB.

    0 讨论(0)
  • 2020-11-28 17:56

    There are three different meanings, not much related!

    • In Ocaml it is a parametrized module. See manual. I think the best way to grok them is by example: (written quickly, might be buggy)

      module type Order = sig
          type t
          val compare: t -> t -> bool
      end;;
      
      
      module Integers = struct
          type t = int
          let compare x y = x > y
      end;;
      
      module ReverseOrder = functor (X: Order) -> struct
          type t = X.t
          let compare x y = X.compare y x
      end;;
      
      (* We can order reversely *)
      module K = ReverseOrder (Integers);;
      Integers.compare 3 4;;   (* this is false *)
      K.compare 3 4;;          (* this is true *)
      
      module LexicographicOrder = functor (X: Order) -> 
        functor (Y: Order) -> struct
          type t = X.t * Y.t
          let compare (a,b) (c,d) = if X.compare a c then true
                               else if X.compare c a then false
                               else Y.compare b d
      end;;
      
      (* compare lexicographically *)
      module X = LexicographicOrder (Integers) (Integers);;
      X.compare (2,3) (4,5);;
      
      module LinearSearch = functor (X: Order) -> struct
          type t = X.t array
          let find x k = 0 (* some boring code *)
      end;;
      
      module BinarySearch = functor (X: Order) -> struct
          type t = X.t array
          let find x k = 0 (* some boring code *)
      end;;
      
      (* linear search over arrays of integers *)
      module LS = LinearSearch (Integers);;
      LS.find [|1;2;3] 2;;
      (* binary search over arrays of pairs of integers, 
         sorted lexicographically *)
      module BS = BinarySearch (LexicographicOrder (Integers) (Integers));;
      BS.find [|(2,3);(4,5)|] (2,3);;
      

    You can now add quickly many possible orders, ways to form new orders, do a binary or linear search easily over them. Generic programming FTW.

    • In functional programming languages like Haskell, it means some type constructors (parametrized types like lists, sets) that can be "mapped". To be precise, a functor f is equipped with (a -> b) -> (f a -> f b). This has origins in category theory. The Wikipedia article you linked to is this usage.

      class Functor f where
          fmap :: (a -> b) -> (f a -> f b)
      
      instance Functor [] where      -- lists are a functor
          fmap = map
      
      instance Functor Maybe where   -- Maybe is option in Haskell
          fmap f (Just x) = Just (f x)
          fmap f Nothing = Nothing
      
      fmap (+1) [2,3,4]   -- this is [3,4,5]
      fmap (+1) (Just 5)  -- this is Just 6
      fmap (+1) Nothing   -- this is Nothing
      

    So, this is a special kind of a type constructors, and has little to do with functors in Ocaml!

    • In imperative languages, it is a pointer to function.
    0 讨论(0)
  • 2020-11-28 17:56

    Rough Overview

    In functional programming, a functor is essentially a construction of lifting ordinary unary functions (i.e. those with one argument) to functions between variables of new types. It is much easier to write and maintain simple functions between plain objects and use functors to lift them, then to manually write functions between complicated container objects. Further advantage is to write plain functions only once and then re-use them via different functors.

    Examples of functors include arrays, "maybe" and "either" functors, futures (see e.g. https://github.com/Avaq/Fluture), and many others.

    Illustration

    Consider the function constructing the full person's name from the first and last names. We could define it like fullName(firstName, lastName) as function of two arguments, which however would not be suitable for functors that only deal with functions of one arguments. To remedy, we collect all the arguments in a single object name, which now becomes the function's single argument:

    // In JavaScript notation
    fullName = name => name.firstName + ' ' + name.lastName
    

    Now what if we have many people in an array? Instead of manually go over the list, we can simply re-use our function fullName via the map method provided for arrays with short single line of code:

    fullNameList = nameList => nameList.map(fullName)
    

    and use it like

    nameList = [
        {firstName: 'Steve', lastName: 'Jobs'},
        {firstName: 'Bill', lastName: 'Gates'}
    ]
    
    fullNames = fullNameList(nameList) 
    // => ['Steve Jobs', 'Bill Gates']
    

    That will work, whenever every entry in our nameList is an object providing both firstName and lastName properties. But what if some objects don't (or even aren't objects at all)? To avoid the errors and make the code safer, we can wrap our objects into the Maybe type (se e.g. https://sanctuary.js.org/#maybe-type):

    // function to test name for validity
    isValidName = name => 
        (typeof name === 'object') 
        && (typeof name.firstName === 'string')
        && (typeof name.lastName === 'string')
    
    // wrap into the Maybe type
    maybeName = name => 
        isValidName(name) ? Just(name) : Nothing()
    

    where Just(name) is a container carrying only valid names and Nothing() is the special value used for everything else. Now instead of interrupting (or forgetting) to check the validity of our arguments, we can simply reuse (lift) our original fullName function with another single line of code, based again on the map method, this time provided for the Maybe type:

    // Maybe Object -> Maybe String
    maybeFullName = maybeName => maybeName.map(fullName)
    

    and use it like

    justSteve = maybeName(
        {firstName: 'Steve', lastName: 'Jobs'}
    ) // => Just({firstName: 'Steve', lastName: 'Jobs'})
    
    notSteve = maybeName(
        {lastName: 'SomeJobs'}
    ) // => Nothing()
    
    steveFN = maybeFullName(justSteve)
    // => Just('Steve Jobs')
    
    notSteveFN = maybeFullName(notSteve)
    // => Nothing()
    

    Category Theory

    A Functor in Category Theory is a map between two categories respecting composition of their morphisms. In a Computer Language, the main Category of interest is the one whose objects are types (certain sets of values), and whose morphisms are functions f:a->b from one type a to another type b.

    For example, take a to be the String type, b the Number type, and f is the function mapping a string into its length:

    // f :: String -> Number
    f = str => str.length
    

    Here a = String represents the set of all strings and b = Number the set of all numbers. In that sense, both a and b represent objects in the Set Category (which is closely related to the category of types, with the difference being inessential here). In the Set Category, morphisms between two sets are precisely all functions from the first set into the second. So our length function f here is a morphism from the set of strings into the set of numbers.

    As we only consider the set category, the relevant Functors from it into itself are maps sending objects to objects and morphisms to morphisms, that satisfy certain algebraic laws.

    Example: Array

    Array can mean many things, but only one thing is a Functor -- the type construct, mapping a type a into the type [a] of all arrays of type a. For instance, the Array functor maps the type String into the type [String] (the set of all arrays of strings of arbitrary length), and set type Number into the corresponding type [Number] (the set of all arrays of numbers).

    It is important not to confuse the Functor map

    Array :: a => [a]
    

    with a morphism a -> [a]. The functor simply maps (associates) the type a into the type [a] as one thing to another. That each type is actually a set of elements, is of no relevance here. In contrast, a morphism is an actual function between those sets. For instance, there is a natural morphism (function)

    pure :: a -> [a]
    pure = x => [x]
    

    which sends a value into the 1-element array with that value as single entry. That function is not a part of the Array Functor! From the point of view of this functor, pure is just a function like any other, nothing special.

    On the other hand, the Array Functor has its second part -- the morphism part. Which maps a morphism f :: a -> b into a morphism [f] :: [a] -> [b]:

    // a -> [a]
    Array.map(f) = arr => arr.map(f)
    

    Here arr is any array of arbitrary length with values of type a, and arr.map(f) is the array of the same length with values of type b, whose entries are results of applying f to the entries of arr. To make it a functor, the mathematical laws of mapping identity to identity and compositions to compositions must hold, which are easy to check in this Array example.

    0 讨论(0)
  • 2020-11-28 17:58

    The best answer to that question is found in "Typeclassopedia" by Brent Yorgey.

    This issue of Monad Reader contain a precise definition of what a functor is as well as many definition of other concepts as well as a diagram. (Monoid, Applicative, Monad and other concept are explained and seen in relation to a functor).

    http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf

    excerpt from Typeclassopedia for Functor: "A simple intuition is that a Functor represents a “container” of some sort, along with the ability to apply a function uniformly to every element in the container"

    But really the whole typeclassopedia is a highly recommended reading that is surprisingly easy. In a way you can see the typeclass presented there as a parallel to design pattern in object in the sense that they give you a vocabulary for given behavior or capability.

    Cheers

    0 讨论(0)
  • 2020-11-28 18:01

    KISS: A functor is an object that has a map method.

    Arrays in JavaScript implement map and are therefore functors. Promises, Streams and Trees often implement map in functional languages, and when they do, they are considered functors. The map method of the functor takes it’s own contents and transforms each of them using the transformation callback passed to map, and returns a new functor, which contains the structure as the first functor, but with the transformed values.

    src: https://www.youtube.com/watch?v=DisD9ftUyCk&feature=youtu.be&t=76

    0 讨论(0)
提交回复
热议问题