runCommand env cmd state = ... retrieveState = ... saveState state = ... main :: IO () main = do args <- getArgs let (actions, nonOptions, errors) = getOpt Permute options args opts <- foldl (>>=) (return startOptions) actions when (null nonOptions) $ printHelp >> throw NotEnoughArguments command <- fromError $ parseCommand nonOptions currentTerm <- getCurrentTerm let env = Environment { envCurrentTerm = currentTerm , envOpts = opts } saveState =<< runCommand env command =<< retrieveState
::
(and also ignore type
, class
, instance
and newtype
). Some swear types help them understand the code. If you are completely new, things like Int
and String
may help, but LayoutClass
like LayoutClass
and MonadError
not. Do not worry about them.fabc
translated to f(a, b, c)
. Haskell omits brackets and commas. One consequence of this is that sometimes we need parentheses for the arguments: fa (b1 + b2) c
translated to f(a, b1 + b2, c)
.a + b
are quite common, and Haskelers do not like brackets, the dollar symbol is used to avoid them: f $ a + b
equivalent to f (a + b)
and is translated f(a + b)
. You can think of $
giant left parenthesis, which automatically closes at the end of the line (and you don’t have to write anymore))))), hurray!) In particular, you can nest them, and each will create a nesting level: f $ gx $ hy $ a + b
is equivalent to f (gx (hy (a + b)))
and is translated as f(g(x,h(y,a + b))
(although some consider it a bad practice).<$>
(with angle brackets). You can consider it the same as $
. <*>
Also occurs - pretend that it is a comma, and f <$> a <*> b
translated to f(a, b)
.x `f` y
translates to f(x,y)
. The thing between apostrophes is a function, usually binary, and arguments on the right and left. doThisThing abc = ...
==> def doThisThing(a, b, c): ...
let
acts as an assignment operator: let a = b + c in ...
==> a = b + c ...
a <- createEntry x
==> a = createEntry(x)
createEntry x
has side effects. More precisely, it means that the expression is monadic. But this is all a witch. Don't pay attention to it yet.)do
return
used to control execution.)f . g $ a + b
f . g $ a + b
translated to f(g(a + b))
. In fact, in a Python program, you are more likely to see something like the following: x = g(a + b) y = f(x)
But Haskell programmers are allergic to unnecessary variables.=<<
, >>=
, <=<
and >=>
. Simply, these are some more ways to get rid of intermediate variables: doSomething >>= doSomethingElse >>= finishItUp
==> x = doSomething() y = doSomethingElse(x) finishItUp(y)
z <- finishItUp =<< doSomethingElse =<< doSomething
==> x = doSomething() y = doSomethingElse(x) z = finishItUp(y)
doSomething
, doSomethingElse
and finishItUp
: this will give a hint that it “flows” by “fish”. If you do this, you can read <=<
and >=>
same way (in fact, they perform the composition of functions, like .
). Read >>
like a semicolon (i.e., no assignment): doSomething >> doSomethingElse
==> doSomething() doSomethingElse()
map
, fold
(and its variants), filter
, composition operator .
, fish operator ( =<<
, etc). This often happens with numeric operators: (+3)
translated to lambda x: x + 3
.when (x == y) $ doSomething x
, read as "Soon x
is equal to y
, call doSomething
with argument x
".when(x == y, doSomething(x))
(here doSomething will be called anyway). Actually, it will be more accurate when(x == y, lambda: doSomething x)
, but it may be more convenient to consider when
construction of a language.if
and case
are keywords. They work the way you expect.case
keyword and the backslash (which declares lambda: \x -> x
translated into lambda x: x
).case
is a pretty nice feature, but it's hard to explain in this post. Perhaps the simplest approximation is the if..elif..else
chain with variable assignments: case moose of Foo xyz -> x + y * z Bar z -> z * 3
==> if isinstance(moose, Foo): x = moose.x # ! y = moose.y z = moose.z return x + y * z elif isinstance(moose, Bar): z = moose.z return z * 3 else: raise Exception("Pattern match failure!")
with
. They work like context management in Python: withFile "foo.txt" ReadMode $ \h -> do ...
==> with open("foo.txt", "r") as h: ...
withFile
is a function. Yes, you can define your own.)throw
, catch
, catches
, throwIO
, finally
, handle
and all other similar functions work exactly as you would expect. This, however, can look funny, because these are all functions, not keywords, with all that it implies. For example: trySomething x `catch` \(e :: IOException) -> handleError e
=== catch (trySomething x) (\(e :: IOException) -> handleError e)
==> try: trySomething(x) except IOError as e: handleError(e)
Nothing
, you can think of it as None
. So isNothing x
checks that x is None
. What is the opposite of Nothing? Just
. For example, isJust x
verifies that x is not None
.Just
and None
processing in the right order. One of the most common cases: maybe someDefault (\x -> ...) mx
==> if mx is None: x = someDefault else: x = mx ...
maybe (error "bad value!") (\x -> ...) x
==> if x is None: raise Exception("bad value!")
data NoNames = NoNames Int Int data WithNames = WithNames { firstField :: Int, secondField :: Int }
NoNames
will be represented in Python with a tuple (1, 2)
, and WithNames
will be represented by the following class: class WithNames: def __init__(self, firstField, secondField): self.firstField = firstField self.secondField = secondField
NoNames 2 3
translated to (2, 3)
, and WithNames 2 3
or WithNames { firstField = 2, secondField = 3 }
to WithNames(2, 3)
.field x
translates to x.field
. How to write x.field = 2
? Well, actually, you can't do that. Although you can copy: return $ x { field = 2 }
==> y = copy(x) y.field = 2 return y
[ x * y | x <- xs, y <- ys, y > 2 ]
==> [ x * y for x in xs for y in ys if y > 2 ]
do x <- xs y <- ys guard (y > 2) return (x * y)
[1, 2, 3]
- and in fact a list of three elements. A colon, as in x:xs
, means creating a list with x
in front and xs
at the end ( cons
, for Lisp fans.) ++
- concatenation of lists, !!
- appeal by index. Reverse slash means lambda
. If you see a symbol that you don’t understand, try searching it in Hoogle (yes, it works with symbols!).liftIO
, lift
, runX
(for example, runState
), unX
(for example, unConstructor
), fromJust
, fmap
, const
, evaluate
, exclamation mark before the argument ( f !x
) , seq
, lattice character (eg I# x
). runCommand env cmd state = ... retrieveState = ... saveState state = ... main :: IO () main = do args <- getArgs let (actions, nonOptions, errors) = getOpt Permute options args opts <- foldl (>>=) (return startOptions) actions when (null nonOptions) $ printHelp >> throw NotEnoughArguments command <- fromError $ parseCommand nonOptions currentTerm <- getCurrentTerm let env = Environment { envCurrentTerm = currentTerm , envOpts = opts } saveState =<< runCommand env command =<< retrieveState
def runCommand(env, cmd, state): ... def retrieveState(): ... def saveState(state): ... def main(): args = getArgs() (actions, nonOptions, errors) = getOpt(Permute(), options, args) opts = **mumble** if nonOptions is None: printHelp() raise NotEnoughArguments command = parseCommand(nonOptions) currentTerm = getCurrentTerm() env = Environment(envCurrentTerm=currentTerm, envOpts=opts) state = retrieveState() result = runCommand(env, command, state) saveState(result)
foldl (>>=) (return startOptions) action
: implements the chain of duty pattern. Hell yes.Source: https://habr.com/ru/post/132969/
All Articles