Как обмениваться данными между функциями в Elm (или Haskell) - PullRequest
0 голосов
/ 27 октября 2018

Я хотел бы создать http-клиент в elm для внешнего API. В scala, которая представляет собой микс OO / FP, я бы выразил это (забыв об асинхронности на данный момент) просто как:

class Client(url: String) {
    def getFoo(): String = ???
}

но в Эльме я немного потерян. Очевидное решение - передать url напрямую в функцию

module Client

getFoo : String -> String

но это чрезвычайно болезненно использовать, потому что это усложняет каждый вызов, поэтому нагрузка растет как с количеством определенных функций, так и с числом вызовов этих функций.

Я пытался использовать записи с функциями, такими как:

type alias Client = { getFoo: String }

createClient : String -> Client

но похоже на плохую имитацию ООП. AFAIU это решается с помощью функторов в Ocaml и объектов в ООП.

Какой канонический способ сделать это в Elm (или в Haskell, если Elm здесь не хватает какой-то особенной функции)?

1 Ответ

0 голосов
/ 27 октября 2018

Помните, что ОО-метод, вызывающий , является не чем иным, как синтаксическим сахаром для предоставления дополнительного this / self аргумента функции:

--  OO                       ┃      functional/procedural
Client c = ...;              │     c = ... :: Client
...                          │     ...
main() {print(c.getFoo());}  │     main = print(getFoo c)

Таким образом, вполне возможно и часто полезно идти этим путем как на процедурном языке, таком как C, так и на языке FP.

data Client {
    url :: String
  , ...
  }

getFoo :: Client -> String
getFoo (Client{url = u}) = ...

Да, это требует от вас явной передачи объекта Client, но это не обязательно плохо - при условии, что вы правильно различаете типы, может быть совершенно очевидно, что нужно передать как аргумент чего функция, и этот подход на самом деле масштабируется лучше, чем методы OO, потому что вы можете иметь несколько объектов в качестве аргументов, и каждая функция может принимать только те, которые ей нужны.

Конечно, есть ситуация, когда у вас есть целый набор функций, которым всем нужен один и тот же объект, и вы хотели бы, чтобы это происходило под капотом без явной передачи его везде. Это можно сделать, скрыв его в типе result .

type Reader c r = c -> r

getFoo :: Reader Client String
getBar :: Reader Client Int
getBaz :: Reader Client Double

Эта читалка монады может использоваться со стандартными монадными комбинаторами:

 quun = (`runReader`c) $ do
   foo <- getFoo     -- `c` argument implicitly passed
   bar <- getBar
   baz <- getBaz
   return (calcQuun foo bar (2*baz))

Этот подход особенно полезен, если у вас также есть мутация в ваших методах, как это обычно происходит в ОО. С явной передачей это становится очень громоздким, так как вам нужно работать с обновленными копиями и быть осторожным, чтобы передавать правильную версию каждой функции. С монадой это обрабатывается автоматически, как если бы это была настоящая мутация.


Я не обращаю внимания на наследство здесь. Если вы вызываете метод через указатель суперкласса, есть дополнительный поиск в vtable, но он может быть смоделирован как просто другое поле в типе записи, которое сообщает вам, к какому подклассу принадлежит этот объект.

...