While browsing the Caml Light library for programming examples, I stumbled across the following code, taken from the Caml Light queue.ml file:
type 'a queue_cell =
Nil
| Cons of 'a * 'a queue_cell ref
;;
type 'a t =
{ mutable head: 'a queue_cell;
mutable tail: 'a queue_cell }
;;
let add x = function
{ head = h; tail = Nil as t } -> (* if tail = Nil then head = Nil *)
let c = Cons(x, ref Nil) in
h <- c; t <- c
| { tail = Cons(_, ref newtail) as oldtail } ->
let c = Cons(x, ref Nil) in
newtail <- c; oldtail <- c
;;
This implementation of FIFO data structures puzzles me. I get the general idea, to keep a pointer to the last entry in the structure, so that appending at the end is possible. This makes perfect sense to me. However, it's the syntax of how this is done that bugs me.
Consider the following:
| { tail = Cons(_, ref newtail) as oldtail } ->
let c = Cons(x, ref Nil) in
newtail <- c; oldtail <- c
I have a problem with types here. By the type definition, newtail should be of type 'a queue cell, since it's retrieved using Cons(_, ref newtail) in the pattern matching: if I understand correctly, this would mean that newtail binds the value pointed by the second member of the tail record field (which originally is a reference).
So what does the newtail <- c means? If I try to replace this statement by (fun x -> x <- c) newtail, I get The identifier x is not mutable., whereas the code sounds perfectly similar to the original variant to me.
Would rewriting these few lines to read as follows mean the same?
| { tail = Cons(_, newtail) as oldtail } ->
let c = Cons(x, ref Nil) in
newtail := c; oldtail <- c
Taking the question one step further, what does the following code actually do?
type t = Nil | Node of (t ref);;
type box = {mutable field: t};;
let poke = function
| {field = Node(ref n)} -> n <- Nil
| {field = Nil} -> ()
;;
let test = {field = Node(ref (Node(ref Nil)))};;
poke test;;
test;;
Is it the same to write
{field = Node(n)} -> n := Nil
and
{field = Node(ref n)} -> n <- Nil
?
Even stranger: the following code returns The value identifier a is unbound.
let a = Nil;;
a <- Nil;; (* The value identifier a is unbound. *)
Could someone take the time to clarify the use of <- for me? The various examples here are pretty puzzling to me...
Thanks!
EDIT: This was originally posted to the Caml Mailing list, but I thought the post didn't make it, so I posted it here. It appears that the posting did work; sorry for that: the link to the mailing list answer (which its original author also posted here), is https://sympa-roc.inria.fr/wws/arc/caml-list/2011-01/msg00190.html.
See my answer on the caml list
Why ask the same question twice in different places ? This only leads to a duplication of efforts, with knowledgeable people wasting their time to answer you.
If you want to do that, please at least post cross-references (from your stackoverflow post to the list archive, and vice versa[1]), so that people can check that it hasn't been answered yet in the other place.
[1] yes, you can have cyclic cross-references, as the stackoverflow post is mutable!
The semantics of mutable fields and references has changed a lot (for good)
between Caml Light and Objective Caml. Beware that this code is Caml Light
specific -- and if you want to learn Caml, you should rather be using
Objective Caml, which is the implementation that is still maintained.
In Objective Caml, only records fields are mutable. References are a derived
concept, the type 'a ref is defined as :
type 'a ref = { mutable contents : 'a }
You change a mutable field with the syntax foo.bar <- baz (where "bar" is
a record field, and foo and baz are any expression, foo being of a record
type)
In Caml Light, record fields are mutable, but sum type fields (variants) are
mutable as well; mutable variant fields are however not used here. See
http://caml.inria.fr/pub/docs/manual-caml-light/node4.6.html for
documentation.
In Caml Light, a record may return a mutable location, akin to a lvalue in
C-like languages. For example, with the mutable variant
type foo = Foo of mutable int
you may write:
let set_foo (f : foo) (n : int) =
match f with
| Foo loc ->
loc <- n
"foo <- bar" is used here to assign a value "bar" to a lvalue "foo" bound in
a mutable pattern.
In your example, two mutable patterns are used :
| { tail = Cons(_, ref newtail) as oldtail } ->
oldtail is a mutable pattern denoting the mutable "tail" field of the
record
(ref newtail) is a specific syntax, a pattern on references. It binds a
mutable pattern "newtail" corresponding to the location of the reference
In other words, in Caml Light you can write the ":=" operator as such:
let prefix := r v =
match r with
| ref loc ->
loc <- v
Hope that helps.
.
Edit:
About the strange error message: I think that internally, Caml Light maintain a list of "value identifiers" in the scope, which come from pattern matching a mutable field (record or variant). When they see a foo <- bar expression, they look in that environment to find the corresponding location. Such environment is local to the expression, it never escapes. In particular at toplevel it is empty, and the errors tells you that no "value identifier" (mutable pattern) exists in the scope.
There is another thing: the namespace of value identifiers and usual identifiers are not distinct. When you match a value identifier, Caml Light adds to the scope the value identifier (mutable), but also the corresponding identifier with the matched rvalue. This can be quite confusing as you may mutate the location, but the value won't change :
#match ref 1 with (ref x) -> (x <- 2; x);;
- : int = 1
#match ref 1 with (ref x) as a -> (x <- 2; !a);;
- : int = 2
The (value) identifier will shadow any older identifier (value identifier or not)
#let x = 1 in let (ref x) = ref 2 in x;;
- : int = 2
(If you didn't know, let pattern = e1 in e2 is equivalent to match e1 with pattern -> e2 (except for the type system))
As the syntaxic classes for identifiers and value identifiers are the same, a non-mutable identifier will also shadow a value identifier, giving birth to a different error:
#let (ref x) = ref 2 in let x = 1 in x <- 3;;
Toplevel input:
>let (ref x) = ref 2 in let x = 1 in x <- 3;;
> ^^^^^^
The identifier x is not mutable.
In OCaml, the <- operator mutates mutable fields or object instance variables (references are mutated with :=). However, there are other things going on like the ref in your pattern matching that are unfamiliar to me. I think that it is signalling to Caml Light to match the cell as a reference (analogous to lazy in pattern matches in OCaml), resulting in a variable that is viable as the left-hand side of <- for mutation. Passing the variable into the function passes the value of the variable, which is not mutable, and therefore the function cannot mutate it.
So: it looks like matching the new tail as ref newtail establishes newtail as a sugared name such that evaluating newtail is transformed to !newtail' (where newtail' is some internal name representing the reference itself) and newtail <- foo transforms to newtail' := foo.
I don't actually know Caml Light, though, and I am unfamiliar with this sugaring if it even exists in OCaml (the code you provided does not compile in OCaml) but that's what it looks like is happening to me.
Related
So I have a record type with mutable field:
type mpoint = { mutable x:int ; mutable y: int };;
let apoint = { x=3 ; y=4};;
And I have a function that expects a 'ref' and does something to its contents.
For example:
let increment x = x := !x+1;;
val increment : int ref -> unit = <fun>
Is there a way to get a 'reference' from a mutable field so that I can pass it to the function. I.e. I want to do something like:
increment apoint.x;; (* increment value of the x field 'in place' *)
Error: This expression has type int but an expression was expected of type
int ref
But the above doesn't work because apoint.x returns the value of the field not its 'ref'. If this was golang or C++ maybe we could use the & operator to indicate we want the address instead of the value of the field: &apoint.x.
(How) can we do this in Ocaml?
PS: Yes, I know its probably more common to avoid using side-effects in this way. But I promise, I am doing this for a good reason in a context where it makes more sense than this simplified/contrived example might suggest.
There's no way to do exactly what you ask for. The type of a reference is very specific:
# let x = ref 3
val x : int ref = {contents = 3}
A reference is a record with one mutable field named contents. You can't really fabricate this up from an arbitrary mutable field of some other record. Even if you are willing to lie to the type system, a field of a record is not represented at all the same as a record.
You could declare your fields as actual references:
type mpoint = { x: int ref; y: int ref; }
Then there is no problem, apoint.x really is a reference. But this representation is not as efficient, i.e., it takes more memory and there are more dereferences to access the values.
If an API is designed in an imperative style it will be difficult to use in OCaml. That's how I look at it anyway. Another way to say this is that ints are small. The interface should perhaps accept an int and return a new int, rather than accepting a reference to an int and modifying it in place.
Jeffrey Scofield explained why this can't be done in ocaml from the point of the type system.
But you can also look at it from the point of the GC (garbage collector). In ocaml internally everything is either a trivial type (int, bool, char, ...) that is stored as a 31/63 bit value or a pointer to a block of memory. Each block of memory has a header that describes the contents to the GC and has some extra bits used by GC.
When you look at a reference internally it is a pointer to the block of memory containing the record with a mutable contents. Through that pointger the GC can access the header and know the block of memory is still reachable.
But lets just assume you could pass apoint.y to a function taking a reference. Then internally the pointer would point to the middle of apoint and the GC would fail when it tries to access the header of that block because it has no idea at what offset to the pointer the header is located.
Now how to work around this?
One way that was already mentioned is to use references instead of mutable. Another way would be to use a getter and setter:
# type 'a mut = (unit -> 'a) * ('a -> unit);;
type 'a mut = (unit -> 'a) * ('a -> unit)
# type mpoint = { mutable x:int ; mutable y: int };;
type mpoint = { mutable x : int; mutable y : int; }
# let mut_x p = (fun () -> p.x), (fun x -> p.x <- x);;
val mut_x : mpoint -> (unit -> int) * (int -> unit) = <fun>
# let mut_y p = (fun () -> p.y), (fun y -> p.y <- y);;
val mut_y : mpoint -> (unit -> int) * (int -> unit) = <fun>
If you only want to incr the variable you can pass an incrementer function instead of getter/setter. Or any other collection of helper functions. A getter/setter pait is just the most generic interface.
You can always copy temporarily the content of field, call the function on that, and back again:
let increment_point_x apoint =
let x = ref apoint.x in
increment x;
apoint.x <- !x
Certainly not as efficient (nor elegant) as it could, but it works.
It is impossible to do exactly what the question asks for (#JeffreyScofield explains why, so I won't repeat that). Some workarounds have been suggested.
Here is another workaround that might work if you can change the implementation of the increment function to use a 'home made' ref type. This comes very close to what was asked for.
Instead of having it take a 'built-in' reference, we can define our own type of reference. The spirit of a 'reference' is something you can set and get. So we can characterise/represent it as a combination of a get and set function.
type 'a ref = {
set: 'a -> unit;
get: unit -> 'a;
};;
type 'a ref = { set : 'a -> unit; get : unit -> 'a; }
We can define the usual ! and := operators on this type:
let (!) cell = cell.get ();;
val ( ! ) : 'a ref -> 'a = <fun>
let (:=) cell = cell.set;;
val ( := ) : 'a ref -> 'a -> unit = <fun>
The increment function's code can remain the same even its type 'looks' the same (but it is subtly 'different' as it is now using our own kind of ref instead of built-in ref).
let increment cell = cell := !cell + 1;;
val increment : int ref -> unit = <fun>
When we want a reference to a field we can now make one. For example a function to make a reference to x:
let xref pt = {
set = (fun v -> pt.x <- v);
get = (fun () -> pt.x);
};;
val xref : mpoint -> int ref = <fun>
And now we can call increment on the x field:
increment (xref apoint);;
- : unit = ()
I came across a pattern which I believe can be expressed more elegantly:
I have two functions f1,f2 :: Int -> Int (their impl. is not relevant), and a process :: Int -> Int which does the following:
if f1 x produces x1 different from x, then repeat the process with x1
otherwise, if f2 x produces x2 different from x, then repeat the process with x2
finally, stop the process and return x
My case ... of implementation is the following:
f1 :: Int -> Int
f1 = undefined
f2 :: Int -> Int
f2 = undefined
process :: Int -> Int
process x =
case f1 x of
x ->
case f2 x of
x -> x
x' -> process x'
x' -> process x'
which produces the following warnings:
so.hs:13:17: warning: [-Woverlapping-patterns]
Pattern match is redundant
In a case alternative: x' -> ...
|
13 | x' -> process x'
| ^^^^^^^^^^^^^^^^
so.hs:14:9: warning: [-Woverlapping-patterns]
Pattern match is redundant
In a case alternative: x' -> ...
|
14 | x' -> process x'
| ^^^^^^^^^^^^^^^^
Can anyone shed some light as to what patterns are overlapping, and how to implement process more elegantly?
There is no way to write a pattern for "a value equal to the one I have stored in a variable x". This is because pattern matching is the primary way variables are created in Haskell.
process :: Int -> Int
process x =
Here x is a pattern. It's a very simple pattern, since it just matches any possible value for the argument to process, but you could have written a more structured pattern, or even multiple equations for process matching different patterns for that argument. And within the scope of that pattern match (the entire RHS of process), you have x as a local variable referring to the matched value.
case f1 x of
x ->
Here x is once again a pattern, and once again it is a very simple pattern matching any possible value that was inspected by the case expression. Then you have x as a new local variable referring to the matched value within the scope of the match (everything the RHS of the -> arrow); and because you have created two local variables with the same name x, the most local one shadows the other in the scope where they both apply (so you have no way of referring to the original x in the RHS of the -> arrow, only the new x that is the result of f applied to the original x).
If you think the pattern x in the case expression ought to mean "match a value equal to x", then why should the pattern x in the function argument mean "match anything and call it x"? You can't have your cake and eat it too1.
Haskell makes the rule very simple: a variable appearing in a pattern is always the creation of a new variable to refer to the value that matched the pattern. It is never a reference to an existing variable to check if the matched value is equal to it. Only constructors will be "checked to see if they match"; variables are just bound to whatever is there2.
The same applies to your inner case, where you meant to test the result to see if it was still x but actually just created yet another x shadowing both of the outer x variables.
This is why the compiler complains about your other pattern matches being redundant. Pattern are checked in order, and the first pattern in each case already matches anything (and calls it x), so the second match in each case will never even be tried.
So, since pattern matching can never test whether a value is equal to a variable, you just need to use a construct other than pattern matching! if ... then ... else ... would work fine. You could also use a guard on a pattern.
2 At least not if you want to be able to tell what a pattern means locally, without examining all containing scopes including the entire module and all imports. A hypothetical language could decide the meaning of the pattern based on whether there's already a variable of that name in scope, but I think Haskell makes right call here. Unexpected shadowing sometimes causes tricky bugs, but at least some sign of them is always local. It would be a nightmare if you could change a pattern from a catch-all to an equality check by introducing a global scope variable with the same name (possibly not even in the same module or even package!).
2 This is actually the core reason we have the syntactic distinction between constructors starting with a capital letter and variables starting with a lowercase letter! The language designers wanted it to be easy to tell at a glance which words are constructors to be matched and which are variables to be bound, without having to consider all the constructor names in scope.
Following Ben's advice, I wrote the following:
process :: Int -> Int
process x
| x /= x1 = process x1
| x /= x2 = process x2
| otherwise = x
where
x1 = f1 x
x2 = f2 x
I followed this tutorial to implement a quasi quoted DSL, and I now want to support non-linear patterns in a quoted pattern. That will allow a repeated binder in a pattern to assert the equality of the matched data. For example, one can then write eval [expr| $a + $a|] = 2 * eval a. I modified antiExprPat as follows:
antiExpPat (MetaExp s) =
Just (do b <- lookupValueName s
let n = mkName s
p0 = VarP n
p1 <- (viewP [|(== $(varE n))|] [p|True|])
let res = case b of Nothing -> p0
_ -> p1
return res)
antiExpPat _ = Nothing
The idea is to use lookupValueName to check if the anti-quoted name s is in scope. If not, then just create a binder with the same name. Otherwise, create a view pattern (== s) -> True that asserts the matched data equals to the data already bound to s. Essentially, I want to convert the quoted pattern [expr| $a + $a |] to the Haskell pattern (Add a ((== a) -> True)).
But that didn't work. The resulting Haskell pattern is Add a a, which means lookupValueName never thinks a is in scope. Am I misunderstanding how lookupValueName works? Or is there a better way to implement non linear patterns here?
The full code is here if you want to play with it. In short, I'm making a quasi quoter to match on Java source.
Update 1:
As #chi pointed out, lookupValueName only checks for the splice's context, whereas I need to check for the splice's content. Any idea how to proceed with that?
Update 2:
So I bit the bullet and threaded the set of in-scope names with a state monad, and traversed the parse tree with transformM which replaces every in-scope meta-variable x with ((== x) -> True):
dataToPatQ (const Nothing `extQ` ...) (evalState (rename s) DS.empty)
...
rename :: Language.Java.Syntax.Stmt -> State (DS.Set String) Language.Java.Syntax.Stmt
rename p = transformM rnvar p
where rnvar (MetaStmt n) = do s <- get
let res = if DS.member n s
then (SAssertEq n)
else (MetaStmt n)
put (DS.insert n s)
return res
rnvar x = return x
It got the right result on the inputs I have, but I have no idea if it is correct, especially given transformM traverses the tree bottom-up so inner meta-variables may be added to the set first.
Suppose I write a function
f [x, y] = x + y
f [x, y, z] = z - x - y
This is filled out by the compiler with an extra line saying something like
f _ = error "pattern match failed"
If f is not exported, and I know it's only applied properly, and the function is performance-critical, I may want to avoid having an extra pattern in the production code. I could rewrite this rather unnaturally something like
f l = assert (atLeastTwo l) $
let (x,r1) = (unsafeHead l, unsafeTail l) in
let (y,r2) = (unsafeHead r1, unsafeTail r1) in
case r2 of
[] -> x + y
(z,r3) -> assert (r3 == []) $ z - x - y
What I'd like to do is write the original function definition with an extra line:
f _ = makeDemonsComeOutOfMyNose "This error is impossible."
The descriptively named magical function would be compiled as error when assertions or inferred safe Haskell are enabled, and marked as unreachable (rendering the pattern match unsafe) when assertions are disabled. Is there a way to do this, or something similar?
Edit
To address jberryman's concerns about whether there is a real performance impact:
This is a hypothetical question. I suspect that in more complicated cases, where there are multiple "can't happen" cases, there is likely to be a performance benefit—at the least, error cases can use extra space in the instruction cache.
Even if there isn't a real performance issue, I think there's also an expressive distinction between an assertion and an error. I suspect the most flexible assertion form is "this code should be unreachable", perhaps with an argument or three indicating how seriously the compiler should take that claim. Safety is relative—if a data structure invariant is broken and causes the program to leak confidential information, that's not necessarily any less serious than an invalid memory access. Note that, roughly speaking, assert p x = if p then x else makeDemonsFlyOutOfMyNose NO_REAL_DEMONS_PLEASE "assertion failed", but there's no way to define the demon function in terms of assert.
GHC is clever enough to optimize the unused pattern match away. Here's a simple program.
module Foo (foo) where
data List a = Nil | Cons a (List a)
link :: List a -> List a -> List a
link Nil _ = error "link: Nil"
link (Cons a _) xs = Cons a xs
l1 = Cons 'a' (Cons 'b' Nil)
foo = link l1
This is a very contrived example, but it demonstrates the case where GHC can prove that link (or in your case f) is being called on a statically known constructor (or can otherwise prove which pattern match will succeed via inlining, simplifying etc.)
And here's the Core output:
foo1 :: Char
foo1 = C# 'a'
foo :: List Char -> List Char
foo = \ (ds :: List Char) -> Cons foo1 ds
The error case doesn't show up anywhere in the Core for Foo. So you can be assured that in cases like this, there is absolutely no performance difference incurred by having an extra unused pattern match.
In the following code, the last phrase I can put an in in front. Will it change anything?
Another question: If I decide to put in in front of the last phrase, do I need to indent it?
I tried without indenting and hugs complains
Last generator in do {...} must be an expression
import Data.Char
groupsOf _ [] = []
groupsOf n xs =
take n xs : groupsOf n ( tail xs )
problem_8 x = maximum . map product . groupsOf 5 $ x
main = do t <- readFile "p8.log"
let digits = map digitToInt $concat $ lines t
print $ problem_8 digits
Edit
Ok, so people don't seem to understand what I'm saying. Let me rephrase:
are the following two the same, given the context above?
1.
let digits = map digitToInt $concat $ lines t
print $ problem_8 digits
2.
let digits = map digitToInt $concat $ lines t
in print $ problem_8 digits
Another question concerning the scope of bindings declared in let: I read here that:
where Clauses.
Sometimes it is convenient to scope bindings over several guarded equations, which requires a where clause:
f x y | y>z = ...
| y==z = ...
| y<z = ...
where z = x*x
Note that this cannot be done with a let expression, which only scopes over the expression which it encloses.
My question: so, the variable digits shouldn't be visible to the last print phrase. Do I miss something here?
Short answer: Use let without in in the body of a do-block, and in the part after the | in a list comprehension. Anywhere else, use let ... in ....
The keyword let is used in three ways in Haskell.
The first form is a let-expression.
let variable = expression in expression
This can be used wherever an expression is allowed, e.g.
> (let x = 2 in x*2) + 3
7
The second is a let-statement. This form is only used inside of do-notation, and does not use in.
do statements
let variable = expression
statements
The third is similar to number 2 and is used inside of list comprehensions. Again, no in.
> [(x, y) | x <- [1..3], let y = 2*x]
[(1,2),(2,4),(3,6)]
This form binds a variable which is in scope in subsequent generators and in the expression before the |.
The reason for your confusion here is that expressions (of the correct type) can be used as statements within a do-block, and let .. in .. is just an expression.
Because of the indentation rules of haskell, a line indented further than the previous one means it's a continuation of the previous line, so this
do let x = 42 in
foo
gets parsed as
do (let x = 42 in foo)
Without indentation, you get a parse error:
do (let x = 42 in)
foo
In conclusion, never use in in a list comprehension or a do-block. It is unneccesary and confusing, as those constructs already have their own form of let.
First off, why hugs? The Haskell Platform is generally the recommended way to go for newbies, which comes with GHC.
Now then, on to the letkeyword. The simplest form of this keyword is meant to always be used with in.
let {assignments} in {expression}
For example,
let two = 2; three = 3 in two * three
The {assignments} are only in scope in the corresponding {expression}. Regular layout rules apply, meaning that in must be indented at least as much as the let that it corresponds to, and any sub-expressions pertaining to the let expression must likewise be indented at least as much. This isn't actually 100% true, but is a good rule of thumb; Haskell layout rules are something you will just get used to over time as you read and write Haskell code. Just keep in mind that the amount of indentation is the main way to indicate which code pertains to what expression.
Haskell provides two convenience cases where you don't have to write in: do notation and list comprehensions (actually, monad comprehensions). The scope of the assignments for these convenience cases is predefined.
do foo
let {assignments}
bar
baz
For do notation, the {assignments} are in scope for any statements that follow, in this case, bar and baz, but not foo. It is as if we had written
do foo
let {assignments}
in do bar
baz
List comprehensions (or really, any monad comprehension) desugar into do notation, so they provide a similar facility.
[ baz | foo, let {assignments}, bar ]
The {assignments} are in scope for the expressions bar and baz, but not for foo.
where is somewhat different. If I'm not mistaken, the scope of where lines up with a particular function definition. So
someFunc x y | guard1 = blah1
| guard2 = blah2
where {assignments}
the {assignments} in this where clause have access to x and y. guard1, guard2, blah1, and blah2 all have access to the {assignments} of this where clause. As is mentioned in the tutorial you linked, this can be helpful if multiple guards reuse the same expressions.
In do notation, you can indeed use let with and without in. For it to be equivalent (in your case, I'll later show an example where you need to add a second do and thus more indentation), you need to indent it as you discovered (if you're using layout - if you use explicit braces and semicolons, they're exactly equivalent).
To understand why it's equivalent, you have to actually grok monads (at least to some degree) and look at the desugaring rules for do notation. In particular, code like this:
do let x = ...
stmts -- the rest of the do block
is translated to let x = ... in do { stmts }. In your case, stmts = print (problem_8 digits). Evaluating the whole desugared let binding results in an IO action (from print $ ...). And here, you need understanding of monads to intuitively agree that there's no difference between do notations and "regular" language elements describing a computation resulting in monadic values.
As for both why are possible: Well, let ... in ... has a broad range of applications (most of which have nothing to do with monads in particular), and a long history to boot. let without in for do notation, on the other hand, seems to be nothing but a small piece of syntactic sugar. The advantage is obvious: You can bind the results of pure (as in, not monadic) computations to a name without resorting to a pointless val <- return $ ... and without splitting up the do block in two:
do stuff
let val = ...
in do more
stuff $ using val
The reason you don't need an extra do block for what follows the let is that you only got a single line. Remember, do e is e.
Regarding your edit: digit being visible in the next line is the whole point. And there's no exception for it or anything. do notation becomes one single expression, and let works just fine in a single expression. where is only needed for things which aren't expressions.
For the sake of demonstration, I'll show the desugared version of your do block. If you aren't too familiar with monads yet (something you should change soon IMHO), ignore the >>= operator and focus on the let. Also note that indentation doesn't matter any more.
main = readFile "p8.log" >>= (\t ->
let digits = map digitToInt $ concat $ lines t
in print (problem_8 digits))
Some beginner notes about "are following two the same".
For example, add1 is a function, that add 1 to number:
add1 :: Int -> Int
add1 x =
let inc = 1
in x + inc
So, it's like add1 x = x + inc with substitution inc by 1 from let keyword.
When you try to suppress in keyword
add1 :: Int -> Int
add1 x =
let inc = 1
x + inc
you've got parse error.
From documentation:
Within do-blocks or list comprehensions
let { d1 ; ... ; dn }
without `in` serves to introduce local bindings.
Btw, there are nice explanation with many examples about what where and in keyword actually do.