user7935: On Ethereum, I'm able to run and test a Fibonacci function by writing a Solidity smart-contract with a fib method, compiling to the EVM, deploying and running offline by using any EVM implementation, such as Sputnick:
function fib(uint n) public constant returns(uint result) {
  if (n <= 1) {
    return 1;
  } else {
    return Fib.fib(n - 1) + Fib.fib(n - 2);
  }
}

On Cardano, I'm aware I can use Haskell to create contracts, which is then compiled to Plutus. But, despite having spent some time googling, I'm still not sure how to achieve that. How do I run and test a "fib" function offline on Cardano?

user2537: What do you want to do with the output of your fib-function ? Also with "offline" you probably mean off-chain. If you want to compute the Fibonacci sequence off-chain you can use pure Haskell. There are many examples like this one:
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

This is just an inefficient naive way. Other solutions can be found here: https://wiki.haskell.org/The_Fibonacci_sequence
And then in your contract:
fibonacci :: forall w s. FibParams -> Contract w s Text ()
fibonacci param = do
    pkh <- ownPaymentPubKeyHash
    ... --do stuff with f
    unbalTx <- ...
    ledgerTx <- submitUnbalancedTx $ Constraints.adjustUnbalancedTx unbalTx
    awaitTxConfirmed $ getCardanoTxId ledgerTx
    Contract.logInfo @String $ "submitted tx"
  where
    f = fib (param num)

For testing you need to look into runEmulatorTraceIO.

user2335: That is indeed possible. The following will compile a fib function to plutus and execute it given the default cost model.
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE DeriveAnyClass        #-}
{-# LANGUAGE DeriveGeneric         #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE NoImplicitPrelude     #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}

module Onchain where

import           Cardano.Api                          (PlutusScriptV2,
                                                       writeFileTextEnvelope)
import           Cardano.Api.Shelley                  (PlutusScript (..))
import           Codec.Serialise
import qualified Data.ByteString.Lazy                 as LBS
import qualified Data.ByteString.Short                as SBS
import           Data.Functor                         (void)
import qualified Ledger.Typed.Scripts                 as Scripts
import           Plutus.V1.Ledger.Api                 as PlutusV1
import           Plutus.V1.Ledger.Value               as PlutusV1
import qualified Plutus.V2.Ledger.Api                 as PlutusV2
import qualified Plutus.V2.Ledger.Contexts            as PlutusV2
import qualified PlutusTx
import           PlutusTx.Prelude                     as P 
import           Prelude                              (IO, print, map) 

import           PlutusCore.Evaluation.Machine.ExBudgetingDefaults (defaultCostModelParams)

{-# INLINEABLE fib #-}
fib :: Integer -> Integer
fib n = go n (0,1)
  where
    go x (a, b) | x==0      = a
                | otherwise = go (x-1) (b, a+b)

fibScript :: Script
fibScript = PlutusV2.fromCompiledCode ($$(PlutusTx.compile [|| fib ||]))

serialisedScript :: PlutusV2.SerializedScript
serialisedScript = SBS.toShort . LBS.toStrict $ serialise fibScript

getEvalCtx :: MonadError CostModelApplyError m => m EvaluationContext
getEvalCtx = case defaultCostModelParams of
              Nothing -> Err.throwError CMInternalReadError
              Just n  -> mkEvaluationContext n

runScript :: MonadError CostModelApplyError m => Integer -> m (LogOutput, Either EvaluationError ExBudget)
runScript n = getEvalCtx >>= (\c -> return (evaluateScriptCounting a b c serialisedScript [I n]))
              where
                a :: PlutusV1.ProtocolVersion
                a = ProtocolVersion 7 0
                b :: PlutusV1.VerboseMode
                b = Quiet


7935: <p>On Ethereum, I'm able to run and test a Fibonacci function by writing a Solidity smart-contract with a <code>fib</code> method, compiling to the EVM, deploying and running offline by using any EVM implementation, such as Sputnick:</p>
<pre><code>function fib(uint n) public constant returns(uint result) {
  if (n &lt;= 1) {
    return 1;
  } else {
    return Fib.fib(n - 1) + Fib.fib(n - 2);
  }
}
</code></pre>
<p>On Cardano, I'm aware I can use Haskell to create contracts, which is then compiled to Plutus. But, despite having spent some time googling, I'm still not sure how to achieve that. How do I run and test a &quot;fib&quot; function offline on Cardano?</p>
 2537: <p>What do you want to do with the output of your fib-function ? Also with &quot;offline&quot; you probably mean off-chain. If you want to compute the Fibonacci sequence off-chain you can use pure Haskell. There are many examples like this one:</p>
<pre><code>fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
</code></pre>
<p>This is just an inefficient naive way. Other solutions can be found here: <a href="https://wiki.haskell.org/The_Fibonacci_sequence" rel="nofollow noreferrer">https://wiki.haskell.org/The_Fibonacci_sequence</a></p>
<p>And then in your contract:</p>
<pre><code>fibonacci :: forall w s. FibParams -&gt; Contract w s Text ()
fibonacci param = do
    pkh &lt;- ownPaymentPubKeyHash
    ... --do stuff with f
    unbalTx &lt;- ...
    ledgerTx &lt;- submitUnbalancedTx $ Constraints.adjustUnbalancedTx unbalTx
    awaitTxConfirmed $ getCardanoTxId ledgerTx
    Contract.logInfo @String $ &quot;submitted tx&quot;
  where
    f = fib (param num)
</code></pre>
<p>For testing you need to look into <code>runEmulatorTraceIO</code>.</p>
 2335: <p>That is indeed possible. The following will compile a fib function to plutus and execute it given the default cost model.</p>
<pre class="lang-haskell prettyprint-override"><code>{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE DeriveAnyClass        #-}
{-# LANGUAGE DeriveGeneric         #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE NoImplicitPrelude     #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}

module Onchain where

import           Cardano.Api                          (PlutusScriptV2,
                                                       writeFileTextEnvelope)
import           Cardano.Api.Shelley                  (PlutusScript (..))
import           Codec.Serialise
import qualified Data.ByteString.Lazy                 as LBS
import qualified Data.ByteString.Short                as SBS
import           Data.Functor                         (void)
import qualified Ledger.Typed.Scripts                 as Scripts
import           Plutus.V1.Ledger.Api                 as PlutusV1
import           Plutus.V1.Ledger.Value               as PlutusV1
import qualified Plutus.V2.Ledger.Api                 as PlutusV2
import qualified Plutus.V2.Ledger.Contexts            as PlutusV2
import qualified PlutusTx
import           PlutusTx.Prelude                     as P 
import           Prelude                              (IO, print, map) 

import           PlutusCore.Evaluation.Machine.ExBudgetingDefaults (defaultCostModelParams)

{-# INLINEABLE fib #-}
fib :: Integer -&gt; Integer
fib n = go n (0,1)
  where
    go x (a, b) | x==0      = a
                | otherwise = go (x-1) (b, a+b)

fibScript :: Script
fibScript = PlutusV2.fromCompiledCode ($$(PlutusTx.compile [|| fib ||]))

serialisedScript :: PlutusV2.SerializedScript
serialisedScript = SBS.toShort . LBS.toStrict $ serialise fibScript

getEvalCtx :: MonadError CostModelApplyError m =&gt; m EvaluationContext
getEvalCtx = case defaultCostModelParams of
              Nothing -&gt; Err.throwError CMInternalReadError
              Just n  -&gt; mkEvaluationContext n

runScript :: MonadError CostModelApplyError m =&gt; Integer -&gt; m (LogOutput, Either EvaluationError ExBudget)
runScript n = getEvalCtx &gt;&gt;= (\c -&gt; return (evaluateScriptCounting a b c serialisedScript [I n]))
              where
                a :: PlutusV1.ProtocolVersion
                a = ProtocolVersion 7 0
                b :: PlutusV1.VerboseMode
                b = Quiet
</code></pre>
