Don't fool me with your functional programming

Functional programming adherents like to entice beginners with promises of perfect code expressiveness, 100% correctness, ease of support and ease of refactoring, and sometimes they even predict the highest performance. However, experienced developers know that this does not happen. Programming is hard work, and “magic pills” do not exist.



On the other hand, elements of a functional programming style have already penetrated into industrial programming languages ​​such as Swift and Kotlin. The developers of these languages ​​are well acquainted with functional programming, so they were able to use it “in the small”, providing for many, though not all, of the necessary components. The farther - the more parts of FI are introduced into industrial nuclear weapons, and the better and more fully the support is implemented.



It is useful to be able to program in a functional style in order to simplify our work, and now we will see how to use it!





Vitaly Bragilevsky is a teacher of FP, the theory of algorithms and computation, the author of the book “Haskell in Depth” and a member of the Haskell 2020 committees and the supervisory committee of the GHC compiler.



Short story



Functional programming stories are not always true. Often people talk about AF like this.



“Once upon a time in a faraway galaxy, programming was simple, straightforward, and good. Algorithms and data structures are programmed and everything is fine - no problems!



Then came the Sith scary people who decided that all programs consist of classes: '' Object-oriented programming for everyone! Everything needs to be written only by such methods. ''



When they gained strength, it became impossible to develop software - a lot of problems appeared. To save the unfortunate suffering developers, there is functional programming. ”



The main thing in salvation is to draw a beautiful and meaningless picture.







When you apply everything in the picture, happiness, peace and tranquility will come, and the problems of software development will be solved.



The story is beautiful, but in reality everything is different. This is how I represent functional and industrial programming.







Functional programming is the laboratory of Faraday - the birthplace of ideas that are then applied in industrial programming.


In a conversation about industrial programming languages, the concepts of whether they are functional or not, whether they use FP or not, should not be raised!



The main mission of the FP is to convey good elements to mainstream languages.


To want to transfer everyone to functional rails is the same as forcing scientists to conduct experiments like Faraday - it makes no sense. No one is currently conducting experiments on electricity with such obsolete methods.



Let's look at examples.



John McCarthy Function



John McCarthy is one of the creators of Lisp.







Fortran was the main programming language 65 years ago. It has a conditional statement:



        IF (I.NE.0) GOTO 40
        STMT-1
        STMT-2
40      STMT-3

      
      





I 0, «40». 



, , . , XIF(M, N1,N2)



, , : N1 N2.



: «… » , .


— N1 N2. , ?



:



M==0 ? N1 : N2
      
      





. . , , - .



?



— . 1977 . « ?». — — , .







— , .



- :



c := 0 
for i := 1 step 1 until n do
    c := c + a[i]*b[i]
      
      





— .



. FP.



Def DotP = (Insert +) o (ApplyToAll x) o Transpose
      
      





o



, , , .



— — , , .



. .





, - Java . , , , .







, , :





, .





:



fun countLinesInFiles(fnames: List<String>): Int
    = fnames.map { File(it).readLines().size }
        .sum()
      
      





Kotlin, , . ?



, , . — «map» — . , : , . it — . — sum().



, map



Kotlin, , . , . — readLines



.



Haskell



, Haskell.



countLines :: FilePath -> IO Int
countLines = fmap (length . lines) . readFile 

countLinesInFiles :: [FilePath] -> IO Int
countLinesInFiles = fmap sum . traverse countLines
      
      





, , . Haskell , , .



. , : , Int — . 



. «IO», Haskell , -. « », Int, IO Int. , .



. — Haskell. — FilePath, IO Int — . : , traverse — , «countLines».



. «. » ( o



) — . , .



«f. g» — , «x» «f(g(x))». , . , . .





.


, , . — , .



UML-. : , , , , .







Swift



, , - . Swift , , .



protocol Shape {
    func area() -> Double
    func diameter() -> Double
}

class Circle: Shape {
    var r = 0.0
    init(radius: Double) {
        r = radius
    }
    func area() -> Double {
        return 3.14 * r * r
    }
    func diameter() -> Double {
        return 2 * r
    }
}

class Square: Shape {
    var s = 0.0
    init(size: Double) {
        s = size
    }
    func area() -> Double {
        return s * s
    }
    func diameter() -> Double {
        return sqrt(2) * s
    }
}
      
      





, .



: , , – , . .



, , .



func testShape(shape: Shape) {
    print(shape.area())
    print(shape.diameter())
}
      
      





. .



testShape(shape: Circle(radius: 5))
testShape(shape: Square(size: 5))

78.5
10.0
25.0
7.0710678118654755
      
      





, , , , - . – , .



Haskell



Haskell, , Swift.



data Shape = Circle Double | Square Double

area :: Shape -> Double
area (Circle r) = 3.14 * r * r
area (Square s) = s * s

diameter :: Shape -> Double
diameter (Circle r) = 2* r
diameter (Square s) = sqrt 2 * s
      
      





. , « » — . «» . .



— , , . Haskell — . , .



:



> area (Circle 5)
78.5
> diameter (Circle 5)
10.0
> area (Square 5)
25.0
 diameter (Square 5)
7.0710678118654755
      
      





— — , .



, Swift Haskell . , , , . , , — , — , , .

Circle Square
area * *
diameter * *
— , — . — , .


, : ? . .



, :





— .





, Swift.



Haskell…



data Shape = Circle Double | Square Double
      
      





… Swift .



enum Shape { 
    case Circle(radius: Double) 
    case Square(size: Double)
}
      
      





. «» , Swift «enum», . , .



Haskell…



area :: Shape -> Double 
    area (Circle r) = 3.14 * r * r 
    area (Square s) = s * s
      
      





… Swift .



extension Shape {
    var area: Double {
        switch self {
            case .Circle(let r): return 3.14 * r * r 
            case .Square(let s): return s * s
        }
    }
}
      
      





, — .



Kotlin:



sealed class Shape {
    data class Circle(val radius: Double): Shape() 
    data class Square(val size: Double): Shape()
}
fun area(sh: Shape): Double {
    return when (sh) {
        is Shape.Circle -> 3.14 * sh.radius * sh.radius 
        is Shape.Square -> sh.size * sh.size 
    }
}
      
      





, — .



, Kotlin Swift ? , , , . , -, .





. , , , , , .

Circle Square x
area * * ?
diameter * * ?
f ? ? ?


, , , - — . . : — . , , .

Circle Square x
area * * + ()
diameter * * + ()
f + () + () ?


. .



. . — - , — , . — .





— — . 20 , .





, « », . . , — , .





, .



, : , , , . , .







Haskell. , , , «» ?



, «» — .



— «Composite». . , .



, Kotlin — .



val a = "Hello " 
val b = "world"
val c = a + b
      
      





, : «, ? ». , — - .



— HTTP-. Kotlin :



val a = HttpReq("f","v1") 
val b = HttpReq("f2","v2")
val c = a + b
      
      





Haskell , . HTTP- — . HTTP-. 



. . - . — , — , . «optional chaining», , Swift.



if let johnsStreet = john.residence?.address?.street
{ /* SUCCESS */ } else { /* FAILURE */ }
      
      





, . «john» — .





: , , . — . , else — . .



Swift . , , , : .



Haskell, .



case residence john >>= address >>= street of
    Just johnsStreet -> -- SUCCESS
    Nothing -> -- FAILURE
      
      





,  «>>=» — . .



Swift «optional chaining» . Kotlin . Haskell , «>>=». 



:





. . , . , — , .



, — .


«» . — , . «», . « » .



— ?



. , — .



, — . Haskell «Build Systems a la Carte» . Distinguished Paper Award International Conference on Functional Programming (ICFP) - 2018 . Build-, .



Build- Build- — . .



data Store i k v
      
      





:





— . — , . . 



. , , . , - -, , .



«Task» — .



newtype Task c k v = Task { run :: forall f. c f => (k -> f v) -> f v }
      
      





Haskell, . , — «f», . , .



? «k -> fv» — - . . . Build-.



— :



type Tasks c k v = k -> Maybe (Task c k v) 
      
      





, , . «Maybe» — - «optional». , - - , .



Build-:



type Build c i k v = Tasks c k v -> k -> Store i k v
    -> Store i k v
      
      





. k, . — . , .



.





. — Excel — . Excel — . , - . Make Excel - , .





.



— Haskell . , .



, . - — . , , . , .





, Haskell:



h :: [a] -> Int
h = length . filter p . map g . map f
      
      





— . - , :





:



— , , … ! !



, .



Fusion



«fusion», . 



«fusion»? -: :



c := 0
foreach (x : xs)
    if p(g(f(x)))
        then c := c + 1
      
      





, (!), (!). , , 2-3 .



, , , . — .


, .



GPU? !



Haskell, :



dotp :: Acc (Vector Float) -> Acc (Vector Float) -> Acc (Scalar Float) 
dotp xs ys = fold (+) 0 (zipWith (*) xs ys)
      
      





, Haskell: 2 , , . , Haskell GPU. GPU, : CUDA, Open CL — , . Accelerate Haskell — .



CUDA  100 , , . ! , . , .





— . ? a, b, c D.



f :: A -> B -> C -> D
f a b c = ...
      
      





, — . , :





, . , , - , . — , , .



, , Scala -.



f :: A -> (B , C) -> D
f a p = …
    f :: A -> B -> C -> D
    f a :: (B, C) -> D
    f a p :: D
      
      





: « , . , . , — ». .



, .





. Haskell 15 QuickCheck, .



, , :



reverse :: String -> String
reverse = ...
      
      





, . , .



> quickCheck (\s -> reverse (reverse s) == s)
    +++ OK, passed 100 tests.
      
      





, . , 100, , , .



, , . . Haskell 15 . . , Swift Kotlin .





, ?



abs :: Int -> Int 
abs x = if x < 0 then 0 - x else x

head :: [a] -> a --      ! 
head (x:_) = x 

example :: Int -> Int
example x = if abs x < 0 then head [] else x
      
      





, .



head :: [a] -> a --      ! 
head (x:_) = x 
      
      





, head



, — .



«head», ? , LiquidHaskell . LiquidHaskell — Haskell-.



{-@ abs :: Int -> { n : Int | 0 <= n } @-} 
abs :: Int -> Int 
abs x = if x < 0 then 0 - x else x

{-@ head :: { xs : [a] | 1 <= len xs } -> a @-} 
head :: [a] -> a --      ! 
head (x:_) = x 

{-@ head :: { xs : [a] | 1 <= len xs } -> a @-} 
head :: [a] -> a --      ! 
head (x:_) = x 
      
      





, abs , n.



{-@ abs :: Int -> { n : Int | 0 <= n } @-} 
abs :: Int -> Int 
abs x = if x < 0 then 0 - x else x
      
      





, .



{-@ head :: { xs : [a] | 1 <= len xs } -> a @-} 
head :: [a] -> a --      ! 
head (x:_) = x 
      
      





, , . x x.



{-@ example :: x : Int -> { y : Int | x == y } @-} 
example :: Int -> Int
example x = if abs x < 0 then head [] else x
      
      





, . , . , - . - - , , Eiffel, - .



?



-:



type MyAPI = Get '[PlainText] String
    :<|> "conf" :> Get '[JSON] String
    :<|> "year" :> Get '[JSON] Int
    :<|> "like" :> Get '[JSON] Bool
myAPI :: Server MyAPI 
    = root :<|> year :<|> conf :<|> like
      
      





Haskell Servant, API . , url , , , JSON. , : String», Int», Bool.



.



root :: Handler String				
root = pure "Welcome to my REST API" 	
conf :: Handler String				
conf = pure "AppsConf"	   		
year :: Handler Int
year = pure 2019 conf
like :: Handler Bool
like = pure True
      
      





— , .



, . , , . — .





— . , . .



« Haskell». 2018 . , . Haskell. 600 , .



«Haskell in Depth» . , , . «slbragilevsky» 42%.



Saint AppsConf , Introductory- , . , , . , iOS, Android, - — . , , .



telegram-, .



All Articles